From 55bda99415d1e5f39552b80ef020f1ac70985755 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:47:12 +0100 Subject: [PATCH 1/7] divi io model independent, get_region_id also --- cpp/memilio/CMakeLists.txt | 1 + cpp/memilio/io/parameters_io.h | 120 ++++++++++++++++++++++ cpp/models/ode_secir/parameters_io.cpp | 42 +------- cpp/models/ode_secir/parameters_io.h | 11 +- cpp/models/ode_secirts/parameters_io.h | 90 +--------------- cpp/models/ode_secirvvs/parameters_io.cpp | 47 --------- cpp/models/ode_secirvvs/parameters_io.h | 16 +-- 7 files changed, 125 insertions(+), 202 deletions(-) create mode 100644 cpp/memilio/io/parameters_io.h diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index e8b58aded5..ab8fea25fc 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -39,6 +39,7 @@ add_library(memilio io/history.h io/mobility_io.h io/mobility_io.cpp + io/parameters_io.h io/result_io.h io/result_io.cpp io/epi_data.h diff --git a/cpp/memilio/io/parameters_io.h b/cpp/memilio/io/parameters_io.h new file mode 100644 index 0000000000..ad895e6183 --- /dev/null +++ b/cpp/memilio/io/parameters_io.h @@ -0,0 +1,120 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Henrik Zunker +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef MEMILIO_IO_PARAMETER_H +#define MEMILIO_IO_PARAMETER_H + +#include "memilio/config.h" + +#ifdef MEMILIO_HAS_JSONCPP + +#include "memilio/io/epi_data.h" +#include "memilio/io/result_io.h" +#include "json/value.h" +#include +#include + +namespace mio +{ +/** + * @brief Gets the region ID (county, state, or district) of an EpiDataEntry. + * + * If none are available, it defaults to 0 which is representing the whole country. + * + * @tparam EpiDataEntry The type of the data entry. + * @param data_entry The (RKI) data entry to extract the region ID from. + * @return The region ID as integer, or 0 if no specific region information is available. + */ +template +int get_region_id(const EpiDataEntry& data_entry) +{ + return data_entry.county_id ? data_entry.county_id->get() + : (data_entry.state_id ? data_entry.state_id->get() + : (data_entry.district_id ? data_entry.district_id->get() : 0)); +} + +/** + * @brief Extracts the number of individuals in critical condition (ICU) for each region + * on a specified date from the provided DIVI data. + * + * @tparam FP Floating point type (default: double). + * + * @param[in] divi_data Vector of DIVI data entries containing date, region, and ICU information. + * @param[in] vregion Vector of region IDs for which the data is computed. + * @param[in] date Date for which the ICU data is computed. + * @param[out] vnum_icu Output vector containing the number of ICU cases for each region. + * + * @return An IOResult indicating success or failure. + */ +template +IOResult compute_divi_data(const std::vector& divi_data, const std::vector& vregion, Date date, + std::vector& vnum_icu) +{ + auto max_date_entry = std::max_element(divi_data.begin(), divi_data.end(), [](auto&& a, auto&& b) { + return a.date < b.date; + }); + if (max_date_entry == divi_data.end()) { + log_error("DIVI data is empty."); + return failure(StatusCode::InvalidValue, "DIVI data is empty."); + } + auto max_date = max_date_entry->date; + if (max_date < date) { + log_error("DIVI data does not contain the specified date."); + return failure(StatusCode::OutOfRange, "DIVI data does not contain the specified date."); + } + + for (auto&& entry : divi_data) { + auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { + return r == 0 || r == get_region_id(entry); + }); + auto date_df = entry.date; + if (it != vregion.end() && date_df == date) { + auto region_idx = size_t(it - vregion.begin()); + vnum_icu[region_idx] = entry.num_icu; + } + } + + return success(); +} + +/** + * @brief Reads DIVI data from a file and computes the ICU data for specified regions and date. + * + * @tparam FP Floating point type (default: double). + * + * @param[in] path Path to the file containing DIVI data. + * @param[in] vregion Vector of region IDs for which the data is computed. + * @param[in] date Date for which the ICU data is computed. + * @param[out] vnum_icu Output vector containing the number of ICU cases for each region. + * + * @return An IOResult indicating success or failure. + */ +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); +} + +} // namespace mio + +#endif //MEMILIO_HAS_JSONCPP + +#endif //MEMILIO_IO_PARAMETER_H diff --git a/cpp/models/ode_secir/parameters_io.cpp b/cpp/models/ode_secir/parameters_io.cpp index 68923adb90..47f45b0ef8 100644 --- a/cpp/models/ode_secir/parameters_io.cpp +++ b/cpp/models/ode_secir/parameters_io.cpp @@ -30,6 +30,7 @@ GCC_CLANG_DIAGNOSTIC(ignored "-Wmaybe-uninitialized") #include "ode_secir/parameters_io.h" #include "memilio/io/epi_data.h" +#include "memilio/io/parameters_io.h" #include "memilio/io/io.h" #include "memilio/utils/stl_util.h" #include "memilio/utils/date.h" @@ -42,15 +43,6 @@ namespace osecir namespace details { -//district, county or state id of a data entry if available, 0 (for whole country) otherwise -//used to compare data entries to integer ids in STL algorithms -template -int get_region_id(const EpiDataEntry& entry) -{ - return entry.county_id - ? entry.county_id->get() - : (entry.state_id ? entry.state_id->get() : (entry.district_id ? entry.district_id->get() : 0)); -} //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) { @@ -201,38 +193,6 @@ IOResult read_confirmed_cases_data( return success(); } -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)); - - auto max_date_entry = std::max_element(divi_data.begin(), divi_data.end(), [](auto&& a, auto&& b) { - return a.date < b.date; - }); - if (max_date_entry == divi_data.end()) { - log_error("DIVI data file is empty."); - return failure(StatusCode::InvalidFileFormat, path + ", file is empty."); - } - auto max_date = max_date_entry->date; - if (max_date < date) { - log_error("Specified date does not exist in DIVI data."); - return failure(StatusCode::OutOfRange, path + ", specified date does not exist in DIVI data."); - } - - for (auto&& entry : divi_data) { - auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { - return r == 0 || r == get_region_id(entry); - }); - auto date_df = entry.date; - if (it != vregion.end() && date_df == date) { - auto region_idx = size_t(it - vregion.begin()); - vnum_icu[region_idx] = entry.num_icu; - } - } - - return success(); -} - IOResult>> read_population_data(const std::string& path, const std::vector& vregion, bool accumulate_age_groups) { diff --git a/cpp/models/ode_secir/parameters_io.h b/cpp/models/ode_secir/parameters_io.h index 7713232ce4..659ca49e7d 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -27,6 +27,7 @@ #include "ode_secir/model.h" #include "memilio/io/epi_data.h" +#include "memilio/io/parameters_io.h" #include "memilio/io/result_io.h" namespace mio @@ -167,16 +168,6 @@ IOResult set_confirmed_cases_data(std::vector>& model, const std return success(); } -/** - * @brief Reads number of ICU patients from DIVI register into Parameters. - * @param[in] path Path to DIVI file. - * @param[in] vregion Keys of the region of interest. - * @param date Date for which we initialize. - * @param vnum_icu Number of ICU patients. - */ -IOResult read_divi_data(const std::string& path, const std::vector& vregion, Date date, - std::vector& vnum_icu); - /** * @brief Sets populations data from DIVI register into Model. * @tparam FP floating point data type, e.g., double. diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index a4c7028483..dd699e841f 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -30,6 +30,7 @@ #include "memilio/mobility/graph.h" #include "memilio/mobility/metapopulation_mobility_instant.h" #include "memilio/io/epi_data.h" +#include "memilio/io/parameters_io.h" #include "memilio/io/io.h" #include "memilio/io/json_serializer.h" #include "memilio/io/result_io.h" @@ -43,31 +44,6 @@ namespace osecirts namespace details { - -/** - * @brief Gets the region ID (county, state, or district) of an EpiDataEntry. - * - * If none are available, it defaults to 0 which is representing the whole country. - * - * @tparam EpiDataEntry The type of the data entry. - * @param data_entry The (RKI) data entry to extract the region ID from. - * @return The region ID as integer, or 0 if no specific region information is available. - */ -template -int get_region_id(const EpiDataEntry& data_entry) -{ - if (data_entry.county_id) { - return data_entry.county_id->get(); - } - if (data_entry.state_id) { - return data_entry.state_id->get(); - } - if (data_entry.district_id) { - return data_entry.district_id->get(); - } - return 0; -} - /** * @brief Computes the distribution of confirmed cases across infection states based on Case (RKI) data. * @@ -558,70 +534,6 @@ IOResult set_confirmed_cases_data(std::vector& model, const std::st return success(); } -/** - * @brief Extracts the number of individuals in critical condition (ICU) for each region - * on a specified date from the provided DIVI data. - * - * @tparam FP Floating point type (default: double). - * - * @param[in] divi_data Vector of DIVI data entries containing date, region, and ICU information. - * @param[in] vregion Vector of region IDs for which the data is computed. - * @param[in] date Date for which the ICU data is computed. - * @param[out] vnum_icu Output vector containing the number of ICU cases for each region. - * - * @return An IOResult indicating success or failure. - */ -template -IOResult compute_divi_data(const std::vector& divi_data, const std::vector& vregion, Date date, - std::vector& vnum_icu) -{ - auto max_date_entry = std::max_element(divi_data.begin(), divi_data.end(), [](auto&& a, auto&& b) { - return a.date < b.date; - }); - if (max_date_entry == divi_data.end()) { - log_error("DIVI data is empty."); - return failure(StatusCode::InvalidValue, "DIVI data is empty."); - } - auto max_date = max_date_entry->date; - if (max_date < date) { - log_error("DIVI data does not contain the specified date."); - return failure(StatusCode::OutOfRange, "DIVI data does not contain the specified date."); - } - - for (auto&& entry : divi_data) { - auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { - return r == 0 || r == get_region_id(entry); - }); - auto date_df = entry.date; - if (it != vregion.end() && date_df == date) { - auto region_idx = size_t(it - vregion.begin()); - vnum_icu[region_idx] = entry.num_icu; - } - } - - return success(); -} - -/** - * @brief Reads DIVI data from a file and computes the ICU data for specified regions and date. - * - * @tparam FP Floating point type (default: double). - * - * @param[in] path Path to the file containing DIVI data. - * @param[in] vregion Vector of region IDs for which the data is computed. - * @param[in] date Date for which the ICU data is computed. - * @param[out] vnum_icu Output vector containing the number of ICU cases for each region. - * - * @return An IOResult indicating success or failure. - */ -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); -} - /** * @brief Sets ICU data from DIVI data into the a vector of models, distributed across age groups. * diff --git a/cpp/models/ode_secirvvs/parameters_io.cpp b/cpp/models/ode_secirvvs/parameters_io.cpp index 35e6903064..7daea0f392 100644 --- a/cpp/models/ode_secirvvs/parameters_io.cpp +++ b/cpp/models/ode_secirvvs/parameters_io.cpp @@ -29,16 +29,6 @@ namespace osecirvvs { namespace details { -//gets the county or state id of the entry if available, 0 (for whole country) otherwise -//used for comparisons of entry to integer region id -template -int get_region_id(const EpiDataEntry& rki_entry) -{ - return rki_entry.county_id ? rki_entry.county_id->get() - : (rki_entry.state_id ? rki_entry.state_id->get() - : (rki_entry.district_id ? rki_entry.district_id->get() : 0)); -} - 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, @@ -279,43 +269,6 @@ IOResult read_confirmed_cases_data_fix_recovered(const std::vector 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 read_divi_data(divi_data, vregion, date, vnum_icu); -} - -IOResult read_divi_data(const std::vector& divi_data, const std::vector& vregion, Date date, - std::vector& vnum_icu) -{ - auto max_date_entry = std::max_element(divi_data.begin(), divi_data.end(), [](auto&& a, auto&& b) { - return a.date < b.date; - }); - if (max_date_entry == divi_data.end()) { - log_error("DIVI data is empty."); - return failure(StatusCode::InvalidValue, "DIVI data is empty."); - } - auto max_date = max_date_entry->date; - if (max_date < date) { - log_error("DIVI data does not contain the specified date."); - return failure(StatusCode::OutOfRange, "DIVI data does not contain the specified date."); - } - - for (auto&& entry : divi_data) { - auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { - return r == 0 || r == get_region_id(entry); - }); - auto date_df = entry.date; - if (it != vregion.end() && date_df == date) { - auto region_idx = size_t(it - vregion.begin()); - vnum_icu[region_idx] = entry.num_icu; - } - } - - return success(); -} - IOResult>> read_population_data(const std::string& path, const std::vector& vregion) { diff --git a/cpp/models/ode_secirvvs/parameters_io.h b/cpp/models/ode_secirvvs/parameters_io.h index dbf0056d26..f6119fccf1 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -26,6 +26,7 @@ #include "ode_secirvvs/model.h" #include "memilio/io/epi_data.h" +#include "memilio/io/parameters_io.h" #include "memilio/io/io.h" #include "memilio/io/result_io.h" #include "memilio/utils/date.h" @@ -387,21 +388,6 @@ IOResult set_confirmed_cases_data(std::vector& model, const std::st return success(); } -/** - * @brief reads number of ICU patients from DIVI register into Parameters - * @param[in] path Path to transformed DIVI file - * @param[in] vregion Keys of the region of interest - * @param[in] date Date for which the arrays are initialized - * @param[in, out] vnum_icu number of ICU patients - * @see mio::read_divi_data - * @{ - */ -IOResult read_divi_data(const std::string& path, const std::vector& vregion, Date date, - std::vector& vnum_icu); -IOResult read_divi_data(const std::vector& divi_data, const std::vector& vregion, Date date, - std::vector& vnum_icu); -/**@}*/ - /** * @brief sets populations data from DIVI register into Model * @param[in, out] model vector of objects in which the data is set From a841c9051b18a60e2e2d66a811a330b9b76e57d8 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:27:05 +0100 Subject: [PATCH 2/7] read population data --- cpp/memilio/CMakeLists.txt | 1 + cpp/memilio/io/parameters_io.cpp | 72 +++++++++++++++++++++++ cpp/memilio/io/parameters_io.h | 21 +++++++ cpp/models/ode_secir/parameters_io.cpp | 38 ------------ cpp/models/ode_secir/parameters_io.h | 16 +---- cpp/models/ode_secirts/parameters_io.cpp | 40 ------------- cpp/models/ode_secirts/parameters_io.h | 28 +-------- cpp/models/ode_secirvvs/parameters_io.cpp | 39 ------------ cpp/models/ode_secirvvs/parameters_io.h | 17 +----- cpp/tests/test_odesecir.cpp | 20 +------ cpp/tests/test_odesecirvvs.cpp | 11 ++-- 11 files changed, 109 insertions(+), 194 deletions(-) create mode 100644 cpp/memilio/io/parameters_io.cpp diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index ab8fea25fc..ffca36d571 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(memilio io/mobility_io.h io/mobility_io.cpp io/parameters_io.h + io/parameters_io.cpp io/result_io.h io/result_io.cpp io/epi_data.h diff --git a/cpp/memilio/io/parameters_io.cpp b/cpp/memilio/io/parameters_io.cpp new file mode 100644 index 0000000000..a547e1508b --- /dev/null +++ b/cpp/memilio/io/parameters_io.cpp @@ -0,0 +1,72 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Henrik Zunker +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "memilio/config.h" + +#ifdef MEMILIO_HAS_JSONCPP + +#include "memilio/io/epi_data.h" +#include "memilio/io/result_io.h" +#include "json/value.h" +#include +#include + +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 ad895e6183..9aedfe6072 100644 --- a/cpp/memilio/io/parameters_io.h +++ b/cpp/memilio/io/parameters_io.h @@ -113,6 +113,27 @@ IOResult read_divi_data(const std::string& path, const std::vector& v return compute_divi_data(divi_data, vregion, date, vnum_icu); } +/** + * @brief Reads population data from a vector of population data entries. + * + * @param[in] population_data Vector of population data entries. + * @param[in] vregion Vector of keys representing the regions of interest. + * @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&, + const std::vector&); + +/** + * @brief Reads population data from census data. + * + * @param[in] path Path to the population data file. + * @param[in] vregion Vector of keys representing the regions of interest. + * @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&, const std::vector&); + } // namespace mio #endif //MEMILIO_HAS_JSONCPP diff --git a/cpp/models/ode_secir/parameters_io.cpp b/cpp/models/ode_secir/parameters_io.cpp index 47f45b0ef8..6ac7dc71db 100644 --- a/cpp/models/ode_secir/parameters_io.cpp +++ b/cpp/models/ode_secir/parameters_io.cpp @@ -193,44 +193,6 @@ IOResult read_confirmed_cases_data( return success(); } -IOResult>> -read_population_data(const std::string& path, const std::vector& vregion, bool accumulate_age_groups) -{ - BOOST_OUTCOME_TRY(auto&& population_data, mio::read_population_data(path, !accumulate_age_groups)); - //if we set up the model for one age group, the population data should be read in with the - //age groups given in the population data json file and are accumulated later - //otherwise the populations are directly saved for the correct model age groups - size_t age_group_size = accumulate_age_groups ? PopulationDataEntry::age_group_names.size() - : ConfirmedCasesDataEntry::age_group_names.size(); - std::vector> vnum_population(vregion.size(), std::vector(age_group_size, 0.0)); - - for (auto&& entry : population_data) { - auto it = std::find_if(vregion.begin(), vregion.end(), [&entry](auto r) { - return r == 0 || (entry.county_id && regions::StateId(r) == regions::get_state_id(int(*entry.county_id))) || - (entry.county_id && regions::CountyId(r) == *entry.county_id) || - (entry.district_id && regions::DistrictId(r) == *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] += entry.population[AgeGroup(age)]; - } - } - } - if (accumulate_age_groups) { - std::vector> vnum_pop_acc(vregion.size(), std::vector(1, 0)); - for (size_t region = 0; region < vregion.size(); ++region) { - vnum_pop_acc[region][0] = - std::accumulate(vnum_population[region].begin(), vnum_population[region].end(), 0.0); - } - return success(vnum_pop_acc); - } - else { - return success(vnum_population); - } -} - } // 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 659ca49e7d..e47c49efcf 100644 --- a/cpp/models/ode_secir/parameters_io.h +++ b/cpp/models/ode_secir/parameters_io.h @@ -206,16 +206,6 @@ IOResult set_divi_data(std::vector>& model, const std::string& p return success(); } -/** - * @brief Reads population data from census data. - * @tparam FP floating point data type, e.g., double. - * @param[in] path Path to RKI file. - * @param[in] vregion Vector of keys of the regions of interest. - * @param[in] accumulate_age_groups Specifies whether population data should be accumulated to one age group. - */ -IOResult>> -read_population_data(const std::string& path, const std::vector& vregion, bool accumulate_age_groups = false); - /** * @brief Sets population data from census data which has been read into num_population. * @tparam FP floating point data type, e.g., double. @@ -251,9 +241,7 @@ template IOResult set_population_data(std::vector>& model, const std::string& path, const std::vector& vregion) { - // Specifies whether population data should be accumulated to one age group. - const bool is_single_age_group = static_cast(model[0].parameters.get_num_groups()) == 1; - BOOST_OUTCOME_TRY(const auto&& num_population, read_population_data(path, vregion, is_single_age_group)); + BOOST_OUTCOME_TRY(const auto&& num_population, read_population_data(path, vregion)); BOOST_OUTCOME_TRY(set_population_data(model, num_population, vregion)); return success(); } @@ -291,7 +279,7 @@ IOResult export_input_data_county_timeseries( std::vector> extrapolated_data( region.size(), TimeSeries::zero(num_days + 1, (size_t)InfectionState::Count * num_age_groups)); - BOOST_OUTCOME_TRY(auto&& num_population, details::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) { diff --git a/cpp/models/ode_secirts/parameters_io.cpp b/cpp/models/ode_secirts/parameters_io.cpp index 4f25fee0b3..fb80842365 100644 --- a/cpp/models/ode_secirts/parameters_io.cpp +++ b/cpp/models/ode_secirts/parameters_io.cpp @@ -52,46 +52,6 @@ namespace osecirts { namespace details { - -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); -} - -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); -} - } // namespace details } // namespace osecirts } // namespace mio diff --git a/cpp/models/ode_secirts/parameters_io.h b/cpp/models/ode_secirts/parameters_io.h index dd699e841f..3b6834aa9e 100644 --- a/cpp/models/ode_secirts/parameters_io.h +++ b/cpp/models/ode_secirts/parameters_io.h @@ -581,30 +581,6 @@ IOResult set_divi_data(std::vector& model, const std::string& path, return success(); } -/** - * @brief Reads population data from census data. - * - * @param[in] path Path to the population data file. - * @param[in] vregion Vector of keys representing the regions of interest. - * @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. - * @see mio::read_population_data - */ -IOResult>> read_population_data(const std::string& path, - const std::vector& vregion); - -/** - * @brief Reads population data from a vector of population data entries. - * - * @param[in] population_data Vector of population data entries. - * @param[in] vregion Vector of keys representing the regions of interest. - * @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. - * @see mio::read_population_data - */ -IOResult>> read_population_data(const std::vector& population_data, - const std::vector& vregion); - /** * @brief Sets the population data for the given models based on the provided population distribution and immunity levels. * @@ -697,7 +673,7 @@ template IOResult set_population_data(std::vector& model, const std::string& path, const std::vector& vregion, const std::vector> immunity_population) { - BOOST_OUTCOME_TRY(auto&& num_population, details::read_population_data(path, vregion)); + 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(); } @@ -888,7 +864,7 @@ IOResult export_input_data_county_timeseries( 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, details::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; diff --git a/cpp/models/ode_secirvvs/parameters_io.cpp b/cpp/models/ode_secirvvs/parameters_io.cpp index 7daea0f392..7df9c317f6 100644 --- a/cpp/models/ode_secirvvs/parameters_io.cpp +++ b/cpp/models/ode_secirvvs/parameters_io.cpp @@ -269,45 +269,6 @@ IOResult read_confirmed_cases_data_fix_recovered(const std::vector>> 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); -} - -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); -} - } // 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 f6119fccf1..07456a70e2 100644 --- a/cpp/models/ode_secirvvs/parameters_io.h +++ b/cpp/models/ode_secirvvs/parameters_io.h @@ -425,19 +425,6 @@ IOResult set_divi_data(std::vector& model, const std::string& path, return success(); } -/** - * @brief reads population data from census data. - * @param[in] path Path to population data file. - * @param[in] vregion vector of keys of the regions of interest - * @see mio::read_population_data - * @{ - */ -IOResult>> read_population_data(const std::string& path, - const std::vector& vregion); -IOResult>> read_population_data(const std::vector& population_data, - const std::vector& vregion); -/**@}*/ - /** * @brief sets population data from census data which has been read into num_population * @param[in, out] model vector of objects in which the data is set @@ -641,7 +628,7 @@ 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, details::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)); @@ -841,7 +828,7 @@ IOResult export_input_data_county_timeseries( 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, details::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; diff --git a/cpp/tests/test_odesecir.cpp b/cpp/tests/test_odesecir.cpp index b1dce952d9..8c1dc11351 100644 --- a/cpp/tests/test_odesecir.cpp +++ b/cpp/tests/test_odesecir.cpp @@ -26,6 +26,7 @@ #include "ode_secir/parameter_space.h" #include "ode_secir/parameters.h" #include "ode_secir/parameters_io.h" +#include "memilio/io/parameters_io.h" #include "memilio/data/analyze_result.h" #include "memilio/math/adapt_rk.h" @@ -1216,21 +1217,6 @@ TEST(TestOdeSecir, apply_constraints_parameters) } #if defined(MEMILIO_HAS_JSONCPP) - -TEST(TestOdeSecir, read_population_data_one_age_group) -{ - std::string path = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); - const std::vector region{1001}; - auto result_one_age_group = mio::osecir::details::read_population_data(path, region, true).value(); - auto result_multiple_age_groups = mio::osecir::details::read_population_data(path, region, false).value(); - EXPECT_EQ(result_one_age_group.size(), 1); - EXPECT_EQ(result_one_age_group[0].size(), 1); - EXPECT_EQ(result_one_age_group[0][0], 90163.0); - - EXPECT_EQ(result_multiple_age_groups.size(), 1); - EXPECT_EQ(result_multiple_age_groups[0].size(), 6); - EXPECT_EQ(result_multiple_age_groups[0][0], 3433.0); -} #if defined(MEMILIO_HAS_HDF5) class ModelTestOdeSecir : public testing::Test @@ -1362,7 +1348,7 @@ TEST_F(ModelTestOdeSecir, 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{1002}; - auto population_data = mio::osecir::details::read_population_data(path, region, false).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++) { @@ -1414,7 +1400,7 @@ TEST_F(ModelTestOdeSecir, model_initialization_old_date) // read population data std::string path = mio::path_join(TEST_DATA_DIR, "county_current_population.json"); const std::vector region{1002}; - auto population_data = mio::osecir::details::read_population_data(path, region, false).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 = diff --git a/cpp/tests/test_odesecirvvs.cpp b/cpp/tests/test_odesecirvvs.cpp index 695dfd2eef..43701a974d 100644 --- a/cpp/tests/test_odesecirvvs.cpp +++ b/cpp/tests/test_odesecirvvs.cpp @@ -28,6 +28,7 @@ #include "memilio/epidemiology/simulation_day.h" #include "memilio/io/io.h" #include "memilio/io/result_io.h" +#include "memilio/io/parameters_io.h" #include "memilio/mobility/graph.h" #include "memilio/utils/stl_util.h" #include "memilio/epidemiology/age_group.h" @@ -792,7 +793,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::osecirvvs::details::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++) { @@ -863,7 +864,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::osecirvvs::details::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++) { @@ -900,7 +901,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::osecirvvs::details::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++) { @@ -933,7 +934,7 @@ 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::osecirvvs::details::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( @@ -973,7 +974,7 @@ 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::osecirvvs::details::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( From 6aba288d633e3c7072635850a30c362f79687da9 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 21 Mar 2025 11:13:19 +0100 Subject: [PATCH 3/7] [ci skip] doc: 2024 -> 2025 --- cpp/memilio/io/parameters_io.cpp | 2 +- cpp/memilio/io/parameters_io.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/memilio/io/parameters_io.cpp b/cpp/memilio/io/parameters_io.cpp index a547e1508b..f9f9f8b3c0 100644 --- a/cpp/memilio/io/parameters_io.cpp +++ b/cpp/memilio/io/parameters_io.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2020-2024 MEmilio +* Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker * diff --git a/cpp/memilio/io/parameters_io.h b/cpp/memilio/io/parameters_io.h index 9aedfe6072..14b333e3aa 100644 --- a/cpp/memilio/io/parameters_io.h +++ b/cpp/memilio/io/parameters_io.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2020-2024 MEmilio +* Copyright (C) 2020-2025 MEmilio * * Authors: Henrik Zunker * From 3e7c55cd1d2139ef5919bf698f353afe4a098e8d Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Fri, 30 May 2025 11:28:57 +0200 Subject: [PATCH 4/7] add tests for cov --- cpp/tests/test_odesecir.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cpp/tests/test_odesecir.cpp b/cpp/tests/test_odesecir.cpp index 944e74a75c..0fc35ec7c2 100644 --- a/cpp/tests/test_odesecir.cpp +++ b/cpp/tests/test_odesecir.cpp @@ -1447,6 +1447,22 @@ TEST(TestOdeSecir, set_divi_data_invalid_dates) mio::set_log_level(mio::LogLevel::warn); } +TEST(TestOdeSecir, set_divi_data_empty_data) +{ + // Create an empty DIVI data vector + std::vector empty_data; + std::vector regions = {0, 1}; + 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); + + // Expect failure due to empty DIVI data + EXPECT_FALSE(result); + EXPECT_EQ(result.error().code(), mio::StatusCode::InvalidValue); + EXPECT_EQ(result.error().message(), "DIVI data is empty."); +} + TEST_F(ModelTestOdeSecir, set_confirmed_cases_data_with_ICU) { // set params @@ -1492,5 +1508,19 @@ TEST_F(ModelTestOdeSecir, set_confirmed_cases_data_with_ICU) } } +TEST(TestOdeSecir, read_population_data_failure) +{ + // Create invalid population data entry without county_id or district_id + std::vector invalid_data; + mio::PopulationDataEntry invalid_entry; + 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}); + EXPECT_FALSE(result); + EXPECT_EQ(result.error().code(), mio::StatusCode::InvalidFileFormat); + EXPECT_EQ(result.error().message(), "File with county population expected."); +} + #endif #endif From 0f0d49867c6605956689c61c49f219e6fe1e4ab5 Mon Sep 17 00:00:00 2001 From: Henrik Zunker <69154294+HenrZu@users.noreply.github.com> Date: Tue, 17 Jun 2025 09:17:05 +0200 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: jubicker <113909589+jubicker@users.noreply.github.com> --- cpp/memilio/io/parameters_io.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/memilio/io/parameters_io.h b/cpp/memilio/io/parameters_io.h index 14b333e3aa..50581afef4 100644 --- a/cpp/memilio/io/parameters_io.h +++ b/cpp/memilio/io/parameters_io.h @@ -58,7 +58,7 @@ int get_region_id(const EpiDataEntry& data_entry) * @param[in] divi_data Vector of DIVI data entries containing date, region, and ICU information. * @param[in] vregion Vector of region IDs for which the data is computed. * @param[in] date Date for which the ICU data is computed. - * @param[out] vnum_icu Output vector containing the number of ICU cases for each region. + * @param[in, out] vnum_icu Output vector containing the number of ICU cases for each region. * * @return An IOResult indicating success or failure. */ @@ -101,7 +101,7 @@ IOResult compute_divi_data(const std::vector& divi_data, const * @param[in] path Path to the file containing DIVI data. * @param[in] vregion Vector of region IDs for which the data is computed. * @param[in] date Date for which the ICU data is computed. - * @param[out] vnum_icu Output vector containing the number of ICU cases for each region. + * @param[in, out] vnum_icu Output vector containing the number of ICU cases for each region. * * @return An IOResult indicating success or failure. */ From f8f729f5a6a78bd764ac8cffcdc413312a92c3c3 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 17 Jun 2025 09:25:25 +0200 Subject: [PATCH 6/7] [ci skip] rm space in doc, add param names --- cpp/memilio/io/parameters_io.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cpp/memilio/io/parameters_io.h b/cpp/memilio/io/parameters_io.h index 50581afef4..2fe7cc3224 100644 --- a/cpp/memilio/io/parameters_io.h +++ b/cpp/memilio/io/parameters_io.h @@ -33,7 +33,8 @@ namespace mio { /** - * @brief Gets the region ID (county, state, or district) of an EpiDataEntry. + * @brief Gets the region ID (county, state, or district) of an EpiDataEntry. If none are available, + * it defaults to 0 which is representing the whole country. * * If none are available, it defaults to 0 which is representing the whole country. * @@ -121,8 +122,8 @@ 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&, - const std::vector&); +IOResult>> read_population_data(const std::vector& population_data, + const std::vector&) vregion; /** * @brief Reads population data from census data. @@ -132,7 +133,8 @@ 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&, const std::vector&); +IOResult>> read_population_data(const std::string& path, + const std::vector& vregion); } // namespace mio From fd8fb0ed2936e46daf911a25aea680deabbb6d3d Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 17 Jun 2025 09:53:10 +0200 Subject: [PATCH 7/7] typo --- cpp/memilio/io/parameters_io.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/memilio/io/parameters_io.h b/cpp/memilio/io/parameters_io.h index 2fe7cc3224..3ea00fc73b 100644 --- a/cpp/memilio/io/parameters_io.h +++ b/cpp/memilio/io/parameters_io.h @@ -123,7 +123,7 @@ IOResult read_divi_data(const std::string& path, const std::vector& v * 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; + const std::vector& vregion); /** * @brief Reads population data from census data.