diff --git a/.gitignore b/.gitignore index 957e6d8c1e..e7823ced97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,284 +1,284 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/.pnp -.pnp.js - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local -*.xls -*.xlsx -*.zip -*.tar.gz -*.csv - -# Created by https://www.gitignore.io/api/c++,node,python -# Edit at https://www.gitignore.io/?templates=c++,node,python - -### C++ ### -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# rollup.js default build output -dist/ - -# Uncomment the public line if your project uses Gatsby -# https://nextjs.org/blog/next-9-1#public-directory-support -# https://create-react-app.dev/docs/using-the-public-folder/#docsNav -# public - -# Storybook build outputs -.out -.storybook-out - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# Temporary folders -tmp/ -temp/ - -### Python ### -# Pycharm -.idea - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions - -# Distribution / packaging -.Python -build/ -develop-eggs/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST -_skbuild/ - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# Data output -pycode/epidemiology/**/*.json -pycode/epidemiology/**/*.h5 -pycode/epi_venv/ -pycode/build_pylint/ - -# Automatic generated python code -pycode/memilio-simulation-stubs/ - -# Credentials for data download -pycode/memilio-epidata/memilio/epidata/CredentialsRegio.ini - -# Docs -docs/html -docs/xml - -# End of https://www.gitignore.io/api/c++,node,python +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/.pnp +.pnp.js + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +*.xls +*.xlsx +*.zip +*.tar.gz +*.csv + +# Created by https://www.gitignore.io/api/c++,node,python +# Edit at https://www.gitignore.io/?templates=c++,node,python + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# rollup.js default build output +dist/ + +# Uncomment the public line if your project uses Gatsby +# https://nextjs.org/blog/next-9-1#public-directory-support +# https://create-react-app.dev/docs/using-the-public-folder/#docsNav +# public + +# Storybook build outputs +.out +.storybook-out + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Temporary folders +tmp/ +temp/ + +### Python ### +# Pycharm +.idea + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions + +# Distribution / packaging +.Python +build/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +_skbuild/ + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Data output +pycode/epidemiology/**/*.json +pycode/epidemiology/**/*.h5 +pycode/epi_venv/ +pycode/build_pylint/ + +# Automatic generated python code +pycode/memilio-simulation-stubs/ + +# Credentials for data download +pycode/memilio-epidata/memilio/epidata/CredentialsRegio.ini + +# Docs +docs/html +docs/xml + +# End of https://www.gitignore.io/api/c++,node,python diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index bee7f41eaa..ee32ff0baf 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -24,8 +24,8 @@ option(MEMILIO_ENABLE_PROFILING "Enable runtime performance profiling of memilio mark_as_advanced(MEMILIO_USE_BUNDLED_SPDLOG MEMILIO_SANITIZE_ADDRESS MEMILIO_SANITIZE_UNDEFINED) -# try to treat AppleClang as Clang, but warn about missing support -if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") +# try to treat AppleClang as Clang, but warn about missing support +if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") message(WARNING "The compiler ID \"AppleClang\" is not supported, trying to compile with \"Clang\" options.") set(CMAKE_CXX_COMPILER_ID "Clang") endif() @@ -82,6 +82,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "DEBUG") message(STATUS "Coverage enabled") include(CodeCoverage) append_coverage_compiler_flags() + # In addition to standard flags, disable elision and inlining to prevent e.g. closing brackets being marked as # uncovered. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors -fno-default-inline") @@ -146,6 +147,7 @@ endif() include(thirdparty/CMakeLists.txt) add_subdirectory(memilio/ad) add_subdirectory(memilio) +add_subdirectory(munich_postprocessing) if(MEMILIO_BUILD_MODELS) add_subdirectory(models/abm) diff --git a/cpp/memilio/utils/random_number_generator.h b/cpp/memilio/utils/random_number_generator.h index 96456dade4..c36ae18d27 100644 --- a/cpp/memilio/utils/random_number_generator.h +++ b/cpp/memilio/utils/random_number_generator.h @@ -711,6 +711,13 @@ IOResult deserialize_internal(IOContext& io, Tag using PoissonDistribution = DistributionAdapter>; +/** + * adapted lognormal_distribution. + * @see DistributionAdapter + */ +template +using LogNormalDistribution = DistributionAdapter>; + } // namespace mio #endif diff --git a/cpp/models/abm/analyze_result.h b/cpp/models/abm/analyze_result.h index 05107af313..efc92f6716 100644 --- a/cpp/models/abm/analyze_result.h +++ b/cpp/models/abm/analyze_result.h @@ -59,39 +59,128 @@ std::vector ensemble_params_percentile(const std::vector((uint32_t)virus_variant + 1)) { + for (auto virus_variant : enum_members()) { // Global infection parameters param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}].params.m(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}].params.s(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.m(); + return result; }); param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + static auto result = + model.parameters.template get()[{virus_variant, age_group}] + .params.s(); + return result; }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { - return model.parameters.template get()[{virus_variant, age_group}]; + return model.parameters.template get()[{virus_variant, age_group}]; }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + return model.parameters.template get()[{virus_variant, age_group}]; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + return model.parameters.template get()[{virus_variant, age_group}]; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + return model.parameters.template get()[{virus_variant, age_group}]; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { return model.parameters.template get()[{virus_variant, age_group}]; }); @@ -155,6 +244,16 @@ std::vector ensemble_params_percentile(const std::vector auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}].params.a(); + return result; + }); + param_percentil(node, [age_group, virus_variant](auto&& model) -> auto& { + static auto result = + model.parameters.template get()[{virus_variant, age_group}].params.b(); + return result; + }); param_percentil(node, [virus_variant](auto&& model) -> auto& { return model.parameters.template get()[{virus_variant}]; }); diff --git a/cpp/models/abm/infection.cpp b/cpp/models/abm/infection.cpp index e7ca94ab3b..bd0d80b8c5 100644 --- a/cpp/models/abm/infection.cpp +++ b/cpp/models/abm/infection.cpp @@ -19,7 +19,15 @@ */ #include "abm/infection.h" +#include "abm/infection_state.h" +#include "abm/time.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/random_number_generator.h" +#include +#include +#include #include +#include namespace mio { @@ -27,34 +35,47 @@ namespace abm { Infection::Infection(PersonalRandomNumberGenerator& rng, VirusVariant virus, AgeGroup age, const Parameters& params, - TimePoint init_date, InfectionState init_state, ProtectionEvent latest_protection, bool detected) + TimePoint init_date, InfectionState init_state, ProtectionEvent latest_protection, bool detected, + bool shift_init, double shift_rate) : m_virus_variant(virus) , m_detected(detected) { assert(age.get() < params.get_num_groups()); - m_viral_load.start_date = draw_infection_course(rng, age, params, init_date, init_state, latest_protection); + m_viral_load.start_date = + draw_infection_course(rng, age, params, init_date, init_state, latest_protection, shift_init, shift_rate); auto vl_params = params.get()[{virus, age}]; ScalarType high_viral_load_factor = 1; if (latest_protection.type != ProtectionType::NoProtection) { - high_viral_load_factor -= - params.get()[{latest_protection.type, age, virus}]( - init_date.days() - latest_protection.time.days()); + high_viral_load_factor -= params.get()[{latest_protection.type, age, virus}]( + init_date.days() - latest_protection.time.days()); } m_viral_load.peak = vl_params.viral_load_peak.get_distribution_instance()(rng, vl_params.viral_load_peak.params) * high_viral_load_factor; - m_viral_load.incline = - vl_params.viral_load_incline.get_distribution_instance()(rng, vl_params.viral_load_incline.params); - m_viral_load.decline = - vl_params.viral_load_decline.get_distribution_instance()(rng, vl_params.viral_load_decline.params); - m_viral_load.end_date = - m_viral_load.start_date + - days(int(m_viral_load.peak / m_viral_load.incline - m_viral_load.peak / m_viral_load.decline)); + m_time_is_infected = m_infection_course.back().first - m_infection_course[0].first; + // the third entry in the infection course is the time an agents switches to InfectedSymptoms or recovers after non-symptomatic + TimePoint t_peak = m_infection_course[2].first; + m_viral_load.incline = std::abs(m_viral_load.peak / (t_peak - m_infection_course[0].first).days()); + m_viral_load.end_date = m_viral_load.start_date + m_time_is_infected; + if (m_infection_course.size() < 4) { + t_peak = m_viral_load.start_date + TimeSpan(int(0.5 * m_time_is_infected.seconds())); + m_viral_load.peak = m_viral_load.incline * (t_peak - m_viral_load.start_date).days(); + } + m_viral_load.decline = -m_viral_load.peak / (m_viral_load.end_date - t_peak).days(); auto inf_params = params.get()[{virus, age}]; m_log_norm_alpha = inf_params.infectivity_alpha.get_distribution_instance()(rng, inf_params.infectivity_alpha.params); m_log_norm_beta = inf_params.infectivity_beta.get_distribution_instance()(rng, inf_params.infectivity_beta.params); + + // // print infection course + // for (size_t i = size_t(0); i < m_infection_course.size(); ++i) { + // std::cout << static_cast(m_infection_course[i].second) << ": " << m_infection_course[i].first.days(); + // } + // std::cout << "\n"; + // for (auto t = m_viral_load.start_date; t <= m_viral_load.end_date + days(3); t += days(1)) { + // std::cout << "t: " << t.days() << " i(t): " << get_infectivity(t) << std::endl; + // } } ScalarType Infection::get_viral_load(TimePoint t) const @@ -75,9 +96,15 @@ ScalarType Infection::get_viral_load(TimePoint t) const ScalarType Infection::get_infectivity(TimePoint t) const { - if (m_viral_load.start_date >= t || get_infection_state(t) == InfectionState::Exposed) + auto time_shift = TimeSpan(int(0.6 * (m_infection_course[1].first - m_infection_course[0].first).seconds())); + if (m_viral_load.start_date + time_shift >= t) return 0; - return 1 / (1 + exp(-(m_log_norm_alpha + m_log_norm_beta * get_viral_load(t)))); + ScalarType infectivity = 1 / (1 + exp(-(m_log_norm_alpha + m_log_norm_beta * get_viral_load(t - time_shift)))); + if ((infectivity < 0) || + (infectivity > (1.0 / (1 + exp(-(m_log_norm_alpha + m_log_norm_beta * m_viral_load.peak)))))) { + log_error("Error: Infectivity smaller zero or above peak value."); + } + return 0.75 * infectivity; } VirusVariant Infection::get_virus_variant() const @@ -97,6 +124,24 @@ InfectionState Infection::get_infection_state(TimePoint t) const .second; } +TimePoint Infection::get_infection_start() const +{ + return (*std::find_if(m_infection_course.begin(), m_infection_course.end(), + [](const std::pair& inf) { + return (inf.second == InfectionState::Exposed); + })) + .first; +} + +TimePoint Infection::get_infection_end() const +{ + return (*std::find_if(m_infection_course.begin(), m_infection_course.end(), + [](const std::pair& inf) { + return (inf.second == InfectionState::Recovered || inf.second == InfectionState::Dead); + })) + .first; +} + void Infection::set_detected() { m_detected = true; @@ -112,11 +157,29 @@ TimePoint Infection::get_start_date() const return m_viral_load.start_date; } +TimeSpan Infection::get_time_in_state(InfectionState state) +{ + auto pos = std::find_if(m_infection_course.begin(), m_infection_course.end(), + [state](const std::pair& inf) { + return (inf.second == state); + }); + // infection state is not part of infection course + if (pos == m_infection_course.end()) { + return TimeSpan(0); + } + return ((pos + 1)->first - pos->first); +} + TimePoint Infection::draw_infection_course(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState init_state, - ProtectionEvent latest_protection) + ProtectionEvent latest_protection, bool shift_init, double shift_rate) { assert(age.get() < params.get_num_groups()); + double shift_in_days = 0.; + if (shift_init) { + shift_in_days = mio::ExponentialDistribution::get_instance()(rng, shift_rate); + } + init_date -= days(shift_in_days); TimePoint start_date = draw_infection_course_backward(rng, age, params, init_date, init_state); draw_infection_course_forward(rng, age, params, init_date, init_state, latest_protection); return start_date; @@ -129,6 +192,8 @@ void Infection::draw_infection_course_forward(PersonalRandomNumberGenerator& rng assert(age.get() < params.get_num_groups()); auto t = init_date; TimeSpan time_period{}; // time period for current infection state + auto time_in_state = params.get()[{ + m_virus_variant, age}]; // time distribution parameters for current infection state InfectionState next_state{start_state}; // next state to enter m_infection_course.push_back(std::pair(t, next_state)); auto& uniform_dist = UniformDistribution::get_instance(); @@ -137,20 +202,42 @@ void Infection::draw_infection_course_forward(PersonalRandomNumberGenerator& rng switch (next_state) { case InfectionState::Exposed: // roll out how long until infected without symptoms - time_period = days(params.get()[{m_virus_variant, age}]); // subject to change - next_state = InfectionState::InfectedNoSymptoms; + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::Exposed && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial state. " + "Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } + next_state = InfectionState::InfectedNoSymptoms; break; case InfectionState::InfectedNoSymptoms: // roll out next infection step v = uniform_dist(rng); - if (v < 0.5) { // TODO: subject to change - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + if (v < params.get()[{m_virus_variant, age}]) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedNoSymptoms && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } next_state = InfectionState::InfectedSymptoms; } else { - time_period = days( - params.get()[{m_virus_variant, age}]); // TODO: subject to change + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedNoSymptoms && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } next_state = InfectionState::Recovered; } @@ -159,20 +246,36 @@ void Infection::draw_infection_course_forward(PersonalRandomNumberGenerator& rng // roll out next infection step { ScalarType severity_protection_factor = 0.5; - v = uniform_dist(rng); if (latest_protection.type != ProtectionType::NoProtection) { severity_protection_factor = params.get()[{latest_protection.type, age, m_virus_variant}]( t.days() - latest_protection.time.days()); } - if (v < (1 - severity_protection_factor) * 0.5) { - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + // roll out next infection step + v = uniform_dist(rng); + if (v < (1 - severity_protection_factor) * + params.get()[{m_virus_variant, age}]) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedSymptoms && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } next_state = InfectionState::InfectedSevere; } else { - time_period = days( - params.get()[{m_virus_variant, age}]); // TODO: subject to change + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedSymptoms && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } next_state = InfectionState::Recovered; } break; @@ -180,25 +283,56 @@ void Infection::draw_infection_course_forward(PersonalRandomNumberGenerator& rng case InfectionState::InfectedSevere: // roll out next infection step v = uniform_dist(rng); - if (v < 0.5) { // TODO: subject to change - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change - next_state = InfectionState::InfectedCritical; + if (v < params.get()[{m_virus_variant, age}]) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedSevere && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } + next_state = InfectionState::InfectedCritical; } else { - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change - next_state = InfectionState::Recovered; + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedSevere && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } + next_state = InfectionState::Recovered; } break; case InfectionState::InfectedCritical: // roll out next infection step v = uniform_dist(rng); - if (v < 0.5) { // TODO: subject to change - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change - next_state = InfectionState::Dead; + if (v < params.get()[{m_virus_variant, age}]) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedCritical && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } + next_state = InfectionState::Dead; } else { - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); + if (start_state == InfectionState::InfectedCritical && (init_date + time_period) <= TimePoint(0)) { + log_warning("Drawn shift time for start_date (in days) {} is bigger that total time in initial " + "state. Time in initial state will " + "be set to shift time + 1 hours.", + init_date.days()); + time_period = TimePoint(0) - init_date + hours(1); + } next_state = InfectionState::Recovered; } break; @@ -214,9 +348,10 @@ TimePoint Infection::draw_infection_course_backward(PersonalRandomNumberGenerato const Parameters& params, TimePoint init_date, InfectionState init_state) { - assert(age.get() < params.get_num_groups()); auto start_date = init_date; TimeSpan time_period{}; // time period for current infection state + auto time_in_state = params.get()[{ + m_virus_variant, age}]; // time distribution parameters for current infection state InfectionState previous_state{init_state}; // next state to enter auto& uniform_dist = UniformDistribution::get_instance(); ScalarType v; // random draws @@ -224,56 +359,70 @@ TimePoint Infection::draw_infection_course_backward(PersonalRandomNumberGenerato while ((previous_state != InfectionState::Exposed)) { switch (previous_state) { - case InfectionState::InfectedNoSymptoms: - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + case InfectionState::InfectedNoSymptoms: { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::Exposed; - break; + } break; - case InfectionState::InfectedSymptoms: - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + case InfectionState::InfectedSymptoms: { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedNoSymptoms; - break; + } break; - case InfectionState::InfectedSevere: - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + case InfectionState::InfectedSevere: { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedSymptoms; - break; + } break; - case InfectionState::InfectedCritical: - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + case InfectionState::InfectedCritical: { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedSevere; - break; + } break; - case InfectionState::Recovered: + case InfectionState::Recovered: { // roll out next infection step v = uniform_dist(rng); - if (v < 0.25) { - time_period = days( - params.get()[{m_virus_variant, age}]); // TODO: subject to change + // compute correct probabilities while factoring out the chance to die + auto p_death = params.get()[{m_virus_variant, age}] * + params.get()[{m_virus_variant, age}] * + params.get()[{m_virus_variant, age}] * + params.get()[{m_virus_variant, age}]; + if (v < (1 - params.get()[{m_virus_variant, age}]) / (1 - p_death)) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedNoSymptoms; } - else if (v < 0.5) { // TODO: subject to change - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + else if (v < (1 - params.get()[{m_virus_variant, age}] * + (1 - params.get()[{m_virus_variant, age}])) / + (1 - p_death)) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedSymptoms; } - else if (v < 0.75) { - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + else if (v < (1 - params.get()[{m_virus_variant, age}] * + params.get()[{m_virus_variant, age}] * + (1 - params.get()[{m_virus_variant, age}])) / + (1 - p_death)) { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedSevere; } else { - time_period = - days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedCritical; } - break; + } break; - case InfectionState::Dead: - time_period = days(params.get()[{m_virus_variant, age}]); // TODO: subject to change + case InfectionState::Dead: { + time_in_state = params.get()[{m_virus_variant, age}]; + time_period = days(time_in_state.get_distribution_instance()(rng, time_in_state.params)); previous_state = InfectionState::InfectedCritical; - break; + } break; default: break; diff --git a/cpp/models/abm/infection.h b/cpp/models/abm/infection.h index 95cf0c4e48..0699838dff 100644 --- a/cpp/models/abm/infection.h +++ b/cpp/models/abm/infection.h @@ -71,11 +71,14 @@ class Infection * @param[in] init_date Date of initializing the Infection. * @param[in] init_state [Default: InfectionState::Exposed] #InfectionState at time of initializing the Infection. * @param[in] latest_protection [Default: {ProtectionType::NoProtection, TimePoint(0)}] The pair value of last ProtectionType (previous Infection/Vaccination) and TimePoint of that protection. - * @param[in] detected [Default: false] If the Infection is detected. + * @param[in] detected [Default: false] If the Infection is detected. + * @param[in] shift_init [Default: false] If the infection should not start at the beginning of the initial state. This is only relevant for model initialization. + * @param[in] shift_rate [Default: 1] Rate of the exponential distribution used to draw the shift time if start_date should be shifted. */ Infection(PersonalRandomNumberGenerator& rng, VirusVariant virus, AgeGroup age, const Parameters& params, TimePoint start_date, InfectionState start_state = InfectionState::Exposed, - ProtectionEvent latest_protection = {ProtectionType::NoProtection, TimePoint(0)}, bool detected = false); + ProtectionEvent latest_protection = {ProtectionType::NoProtection, TimePoint(0)}, bool detected = false, + bool shift_init = false, double shift_rate = 1.); /** * @brief Gets the ViralLoad of the Infection at a given TimePoint. @@ -109,6 +112,18 @@ class Infection */ InfectionState get_infection_state(TimePoint t) const; + /** + * @brief Get the starting time of the Infection. + * @return starting time point of the Infection. + */ + TimePoint get_infection_start() const; + + /** + * @brief Get the end time of the Infection. + * @return End time point of the Infection i.e. time point of recovery or death. + */ + TimePoint get_infection_end() const; + /** * @brief Set the Infection to detected. */ @@ -136,6 +151,14 @@ class Infection .add("detected", m_detected); } + /** + * @brief Get the the time in #InfectionState. + * If the infection state is not part of the infection course, the time is zero. + * @param[in] state InfectionState of the query. + * @return TimeSpan spent in state. + */ + TimeSpan get_time_in_state(InfectionState state); + private: friend DefaultFactory; Infection() = default; @@ -149,10 +172,13 @@ class Infection * @param[in] params Parameters of the Model. * @param[in] init_date Date of initializing the Infection. * @param[in] init_state #InfectionState at time of initializing the Infection. + * @param[in] shift_init Specifies whether start date should be shifted. Only relevant for model initialization. + * @param[in] shift_rate Rate for the exponential distribution used to draw the ne init_date is shift is required. * @return The starting date of the Infection. */ TimePoint draw_infection_course(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, - TimePoint init_date, InfectionState start_state, ProtectionEvent latest_protection); + TimePoint init_date, InfectionState start_state, ProtectionEvent latest_protection, + bool shift_init, double shift_rate); /** * @brief Determine ViralLoad course and Infection course prior to the given start_state. @@ -184,6 +210,7 @@ class Infection ScalarType m_log_norm_alpha, m_log_norm_beta; ///< Parameters for the infectivity mapping, which is modelled through an invlogit function. bool m_detected; ///< Whether an Infection is detected or not. + TimeSpan m_time_is_infected; }; } // namespace abm diff --git a/cpp/models/abm/location.cpp b/cpp/models/abm/location.cpp index e2cf026140..0d7a30ed53 100644 --- a/cpp/models/abm/location.cpp +++ b/cpp/models/abm/location.cpp @@ -33,6 +33,7 @@ Location::Location(LocationType loc_type, LocationId loc_id, size_t num_agegroup , m_parameters(num_agegroups) , m_cells(num_cells) , m_required_mask(MaskType::None) + , m_closed(false) { assert(num_cells > 0 && "Number of cells has to be larger than 0."); } diff --git a/cpp/models/abm/location.h b/cpp/models/abm/location.h index 4475504a93..738b7d6d24 100644 --- a/cpp/models/abm/location.h +++ b/cpp/models/abm/location.h @@ -271,6 +271,45 @@ class Location .add("geographical_location", m_geographical_location); } + /** + * @brief Set the id of the wastewater zone the location is in. + * @param[in] id The id of the wastewater zone. + */ + void set_wastewater_id(int id) + { + m_wastewater_id = id; + } + + /** + * @brief Get the id of the wastewater zone the location is in. + */ + int get_wastewater_id() const + { + return m_wastewater_id; + } + + /** + * @brief Get the information whether the location is closed and persons cannot enter it. + * @return True if location is closed. + */ + bool is_closed() const + { + return m_closed; + } + + /** + * @brief Close location. + */ + void close_location() + { + m_closed = true; + } + + void open_location() + { + m_closed = false; + } + private: friend DefaultFactory; Location() = default; @@ -281,6 +320,8 @@ class Location std::vector m_cells{}; ///< A vector of all Cell%s that the Location is divided in. MaskType m_required_mask; ///< Least secure type of Mask that is needed to enter the Location. GeographicalLocation m_geographical_location; ///< Geographical location (longitude and latitude) of the Location. + int m_wastewater_id; ///< Only used for INSIDe Munich Demonstrator. Id of wastewater zone the location is in. + bool m_closed; ///< Only used for INSIDe Munich Demonstrator. If Location is closed, no agents can enter it. }; } // namespace abm diff --git a/cpp/models/abm/mobility_rules.cpp b/cpp/models/abm/mobility_rules.cpp index 32858037f7..b1c20769f5 100644 --- a/cpp/models/abm/mobility_rules.cpp +++ b/cpp/models/abm/mobility_rules.cpp @@ -18,6 +18,7 @@ * limitations under the License. */ #include "abm/mobility_rules.h" +#include "abm/parameters.h" #include "abm/person.h" #include "abm/random_events.h" #include "abm/location_type.h" @@ -56,7 +57,8 @@ LocationType go_to_school(PersonalRandomNumberGenerator& /*rng*/, const Person& return LocationType::School; } //return home - if (current_loc == LocationType::School && t.hour_of_day() >= 15) { + if (current_loc == LocationType::School && person.get_return_from_school_time(params) >= t.time_since_midnight() && + person.get_return_from_school_time(params) < t.time_since_midnight() + dt) { return LocationType::Home; } return current_loc; @@ -75,7 +77,8 @@ LocationType go_to_work(PersonalRandomNumberGenerator& /*rng*/, const Person& pe return LocationType::Work; } //return home - if (current_loc == LocationType::Work && t.hour_of_day() >= 17) { + if (current_loc == LocationType::Work && t.time_since_midnight() <= person.get_return_from_work_time(params) && + t.time_since_midnight() + dt > person.get_return_from_work_time(params)) { return LocationType::Home; } return current_loc; @@ -86,8 +89,8 @@ LocationType go_to_shop(PersonalRandomNumberGenerator& rng, const Person& person { auto current_loc = person.get_location_type(); //leave - if (t.day_of_week() < 6 && t.hour_of_day() > 7 && t.hour_of_day() < 22 && current_loc == LocationType::Home && - !person.is_in_quarantine(t, params)) { + if (params.get()[person.get_age()] && t.day_of_week() < 6 && t.hour_of_day() > 7 && + t.hour_of_day() < 22 && current_loc == LocationType::Home && !person.is_in_quarantine(t, params)) { return random_transition(rng, current_loc, dt, {{LocationType::BasicsShop, params.get()[person.get_age()]}}); } @@ -104,19 +107,42 @@ LocationType go_to_event(PersonalRandomNumberGenerator& rng, const Person& perso const Parameters& params) { auto current_loc = person.get_location_type(); - //leave - if (current_loc == LocationType::Home && t < params.get() && - ((t.day_of_week() <= 4 && t.hour_of_day() >= 19) || (t.day_of_week() >= 5 && t.hour_of_day() >= 10)) && - !person.is_in_quarantine(t, params)) { - return random_transition(rng, current_loc, dt, - {{LocationType::SocialEvent, - params.get().get_matrix_at(t.days())[(size_t)person.get_age()]}}); + if (params.get()[person.get_age()] || + params.get()[person.get_age()]) { + //leave + if (current_loc == LocationType::Home && t < params.get() && + ((t.day_of_week() <= 4 && t.hour_of_day() >= 19 && t.hour_of_day() < 22) || + (t.day_of_week() >= 5 && t.hour_of_day() >= 10 && t.hour_of_day() < 22)) && + !person.is_in_quarantine(t, params)) { + return random_transition(rng, current_loc, dt, + {{LocationType::SocialEvent, params.get().get_matrix_at( + t.days())[(size_t)person.get_age()]}}); + } + + //return home + if (current_loc == LocationType::SocialEvent && t.hour_of_day() >= 17 && + person.get_time_at_location() >= hours(2)) { + return LocationType::Home; + } } - - //return home - if (current_loc == LocationType::SocialEvent && t.hour_of_day() >= 20 && - person.get_time_at_location() >= hours(2)) { - return LocationType::Home; + else { + //leave + if (current_loc == LocationType::Home && t < params.get() && + ((t.day_of_week() <= 4 && t.hour_of_day() >= 8 && t.hour_of_day() < 22) || + (t.day_of_week() >= 5 && t.hour_of_day() >= 10 && t.hour_of_day() < 22)) && + !person.is_in_quarantine(t, params)) { + return random_transition(rng, current_loc, dt, + {{LocationType::SocialEvent, params.get().get_matrix_at( + t.days())[(size_t)person.get_age()]}}); + } + + //return home + if ((t.day_of_week() <= 4 && current_loc == LocationType::SocialEvent && + person.get_time_at_location() >= hours(2)) || + (t.day_of_week() >= 5 && current_loc == LocationType::SocialEvent && t.hour_of_day() >= 17 && + person.get_time_at_location() >= hours(2))) { + return LocationType::Home; + } } return current_loc; diff --git a/cpp/models/abm/model.cpp b/cpp/models/abm/model.cpp index 4c8ba1f7f5..3c8d88f68b 100755 --- a/cpp/models/abm/model.cpp +++ b/cpp/models/abm/model.cpp @@ -21,10 +21,14 @@ #include "abm/location_id.h" #include "abm/location_type.h" #include "abm/intervention_type.h" +#include "abm/parameters.h" #include "abm/person.h" #include "abm/location.h" #include "abm/mobility_rules.h" +#include "abm/time.h" +#include "abm/virus_variant.h" #include "memilio/epidemiology/age_group.h" +#include "memilio/utils/compiler_diagnostics.h" #include "memilio/utils/logging.h" #include "memilio/utils/mioomp.h" #include "memilio/utils/stl_util.h" @@ -78,6 +82,7 @@ void Model::evolve(TimePoint t, TimeSpan dt) log_info("ABM Model interaction."); interaction(t, dt); log_info("ABM Model mobility."); + check_close_locations(t); perform_mobility(t, dt); } @@ -100,8 +105,11 @@ void Model::perform_mobility(TimePoint t, TimeSpan dt) auto try_mobility_rule = [&](auto rule) -> bool { // run mobility rule and check if change of location can actually happen - auto target_type = rule(personal_rng, person, t, dt, parameters); - const Location& target_location = get_location(find_location(target_type, person_id)); + auto target_type = rule(personal_rng, person, t, dt, parameters); + const Location& target_location = get_location(find_location(target_type, person_id)); + if (target_location.is_closed()) { + return false; + } const LocationId current_location = person.get_location(); // the Person cannot move if they do not wear mask as required at targeted location @@ -262,6 +270,34 @@ void Model::compute_exposure_caches(TimePoint t, TimeSpan dt) } // implicit taskloop barrier // here is an implicit (and needed) barrier from parallel for + //apply infection rate dampings + auto& dampings = parameters.get(); + if (!dampings.empty()) { + // We want to go through the dampings vector and get the last entry that is smaller than t + auto it = std::upper_bound(dampings.begin(), dampings.end(), t, + [](TimePoint tp, const std::pair& p) { + return tp < p.first; + }); + if (it > dampings.begin() + 1) { //new damping has to be applied + if (it != dampings.begin() + 2) { + mio::log_error("Error: At least one ABM Damping was skipped."); + } + else { + //first remove old damping + double old_factor = dampings.begin()->second; + for (auto virus = int(VirusVariant(0)); virus != int(VirusVariant::Count); ++virus) { + parameters.get()[{VirusVariant(virus)}] *= 1. / old_factor; + } + //Remove old damping + dampings.erase(dampings.begin()); + //Apply new damping + for (auto virus = int(VirusVariant(0)); virus != int(VirusVariant::Count); ++virus) { + parameters.get()[{VirusVariant(virus)}] *= dampings.begin()->second; + } + } + } + } + // 2) add all contributions from each person PRAGMA_OMP(taskloop) for (size_t i = 0; i < num_persons; ++i) { @@ -269,7 +305,7 @@ void Model::compute_exposure_caches(TimePoint t, TimeSpan dt) const auto location = person.get_location().get(); mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location], m_contact_exposure_rates_cache[location], person, - get_location(person.get_id()), t, dt); + get_location(person.get_id()), t, dt, parameters); } // implicit taskloop barrier } // implicit single barrier } @@ -358,5 +394,49 @@ const TestingStrategy& Model::get_testing_strategy() const return m_testing_strategy; } +void Model::open_all_locations() +{ + for (auto& loc : m_locations) { + loc.open_location(); + } +} + +void Model::open_locations_of_type(LocationType type) +{ + for (auto& loc : m_locations) { + if (loc.get_type() == type) { + loc.open_location(); + } + } +} + +void Model::check_close_locations(TimePoint t) +{ + // check whether location closure should be applied + auto& closures = parameters.get(); + if (closures.size() > 0) { + // check if closure needs to be applied + if (std::get<0>(*closures.begin()) <= t) { + //first remove old closure + open_locations_of_type(std::get<1>(*closures.begin())); + // Distribution used to draw whether location is closed + auto& uniform_dist = UniformDistribution::get_instance(); + double p; + //Apply new closure + for (auto& loc : m_locations) { + if (std::get<1>(*closures.begin()) == loc.get_type()) { + p = uniform_dist(m_rng); + if (p < std::get<2>(*closures.begin())) { + // close location + loc.close_location(); + } + } + } + //Remove closure + closures.erase(closures.begin()); + } + } +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/model.h b/cpp/models/abm/model.h index ea9f49c90c..d69bdb2d6a 100644 --- a/cpp/models/abm/model.h +++ b/cpp/models/abm/model.h @@ -20,6 +20,7 @@ #ifndef MIO_ABM_MODEL_H #define MIO_ABM_MODEL_H +#include "abm/location_id.h" #include "abm/model_functions.h" #include "abm/location_type.h" #include "abm/mobility_data.h" @@ -32,10 +33,15 @@ #include "abm/random_events.h" #include "abm/testing_strategy.h" #include "memilio/epidemiology/age_group.h" +#include "memilio/utils/compiler_diagnostics.h" +#include "memilio/utils/logging.h" #include "memilio/utils/random_number_generator.h" #include "memilio/utils/stl_util.h" #include +#include +#include +#include #include namespace mio @@ -112,7 +118,7 @@ class Model void serialize(IOContext& io) const { auto obj = io.create_object("Model"); - obj.add_element("parameters", parameters); + // obj.add_element("parameters", parameters); // skip caches, they are rebuild by the deserialized model obj.add_list("persons", get_persons().begin(), get_persons().end()); obj.add_list("locations", get_locations().begin(), get_locations().end()); @@ -131,8 +137,8 @@ class Model template static IOResult deserialize(IOContext& io) { - auto obj = io.expect_object("Model"); - auto params = obj.expect_element("parameters", Tag{}); + auto obj = io.expect_object("Model"); + //auto params = obj.expect_element("parameters", Tag{}); auto persons = obj.expect_list("persons", Tag{}); auto locations = obj.expect_list("locations", Tag{}); auto location_types = obj.expect_element("location_types", Tag{}); @@ -142,9 +148,9 @@ class Model auto rng = obj.expect_element("rng", Tag{}); return apply( io, - [](auto&& params_, auto&& persons_, auto&& locations_, auto&& location_types_, auto&& trip_list_, + [](auto&& persons_, auto&& locations_, auto&& location_types_, auto&& trip_list_, auto&& use_mobility_rules_, auto&& cemetery_id_, auto&& rng_) { - Model model{params_}; + Model model{1}; model.m_persons.assign(persons_.cbegin(), persons_.cend()); model.m_locations.assign(locations_.cbegin(), locations_.cend()); model.m_has_locations = location_types_; @@ -154,7 +160,7 @@ class Model model.m_rng = rng_; return model; }, - params, persons, locations, location_types, trip_list, use_mobility_rules, cemetery_id, rng); + persons, locations, location_types, trip_list, use_mobility_rules, cemetery_id, rng); } /** @@ -454,6 +460,34 @@ class Model } /** @} */ + void add_infection_rate_damping(TimePoint t, double factor) + { + parameters.get().push_back(std::make_pair(t, factor)); + } + + /** + * @brief Set wastewater id for every location in the model. + * @param[in] loc_to_id Map with the wastewater zone id for every location. + */ + void set_wastewater_ids(std::map, int>& loc_to_id) + { + for (auto& loc : m_locations) { + auto it = loc_to_id.find(std::make_tuple(loc.get_type(), loc.get_id())); + if (it == loc_to_id.end()) { + log_error("No Wastewater zone found for location with type {} and id {}.", + static_cast(loc.get_type()), loc.get_id().get()); + } + else { + loc.set_wastewater_id(it->second); + } + } + } + + void add_location_closure(TimePoint t, LocationType loc_type, double percentage) + { + parameters.get().push_back(std::make_tuple(t, loc_type, percentage)); + } + private: /** * @brief Person%s interact at their Location and may become infected. @@ -481,6 +515,22 @@ class Model */ void compute_exposure_caches(TimePoint t, TimeSpan dt); + /** + * @brief Opens all locations in the model + */ + void open_all_locations(); + + /** + * @brief Opens all locations of a given type in the model + */ + void open_locations_of_type(LocationType type); + + /** + * @brief Check whether locations shall be closed and close the corresponding locations. + * @param[in] t Timepoint at which location closures are checked. + */ + void check_close_locations(TimePoint t); + mutable Eigen::Matrix m_local_population_cache; ///< Current number of Persons in a given location. Eigen::Matrix diff --git a/cpp/models/abm/model_functions.cpp b/cpp/models/abm/model_functions.cpp index 9e8a0ae78e..84fe4f735b 100644 --- a/cpp/models/abm/model_functions.cpp +++ b/cpp/models/abm/model_functions.cpp @@ -81,7 +81,7 @@ void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const daily_transmissions_by_contacts(local_contact_exposure, cell_index, virus, age_receiver, local_parameters)) + daily_transmissions_by_air(local_air_exposure, cell_index, virus, global_parameters)) * - dt.days() * (1 - mask_protection) * (1 - person.get_protection_factor(t, virus, global_parameters)); + (1 - mask_protection) * (1 - person.get_protection_factor(t, virus, global_parameters)); local_indiv_trans_prob[v] = std::make_pair(virus, local_indiv_trans_prob_v); } @@ -96,10 +96,12 @@ void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const } } person.add_time_at_location(dt); + person.change_time_since_transmission(dt, t); } void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExposureRates& local_contact_exposure, - const Person& person, const Location& location, const TimePoint t, const TimeSpan dt) + const Person& person, const Location& location, const TimePoint t, const TimeSpan dt, + const Parameters& global_parameters) { if (person.get_location() != location.get_id()) { mio::log_debug("In add_exposure_contribution: Person {} is not at Location {}", person.get_id().get(), @@ -118,9 +120,11 @@ void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExpo location.get_cells()[cell.get()].compute_space_per_person_relative(); } else { - local_air_exposure[{cell, virus}] += infection.get_infectivity(t + dt / 2); + local_air_exposure[{cell, virus}] += global_parameters.get()[{virus}] * + infection.get_infectivity(t + dt / 2); } - local_contact_exposure[{cell, virus, age}] += infection.get_infectivity(t + dt / 2); + local_contact_exposure[{cell, virus, age}] += + global_parameters.get()[{virus}] * infection.get_infectivity(t + dt / 2); } } } diff --git a/cpp/models/abm/model_functions.h b/cpp/models/abm/model_functions.h index 757d7e77d2..9af3bc5300 100644 --- a/cpp/models/abm/model_functions.h +++ b/cpp/models/abm/model_functions.h @@ -63,9 +63,11 @@ ScalarType daily_transmissions_by_air(const AirExposureRates& rates, const CellI * @param[in] location The person's current location. * @param[in] t Current Simulation time. * @param[in] dt Length of the current Simulation time step. + * @param[in] global_parameters Parameters of the Model. */ void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExposureRates& local_contact_exposure, - const Person& person, const Location& location, const TimePoint t, const TimeSpan dt); + const Person& person, const Location& location, const TimePoint t, const TimeSpan dt, + const Parameters& global_parameters); /** * @brief Let a Person interact with the population at its current Location, possibly getting infected. diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 204e2eb77d..f35cdd096d 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2024 MEmilio * -* Authors: Daniel Abele, Elisabeth Kluth, Khoa Nguyen +* Authors: Daniel Abele, Elisabeth Kluth, Khoa Nguyen, David Kerkmann * * Contact: Martin J. Kuehn * @@ -20,6 +20,7 @@ #ifndef MIO_ABM_PARAMETERS_H #define MIO_ABM_PARAMETERS_H +#include "abm/location_type.h" #include "abm/mask_type.h" #include "abm/time.h" #include "abm/virus_variant.h" @@ -36,24 +37,26 @@ #include "memilio/epidemiology/age_group.h" #include "memilio/epidemiology/damping.h" #include "memilio/epidemiology/contact_matrix.h" - -#include #include -#include +#include +#include namespace mio { namespace abm { +// Distribution that can be used for the time spend in InfectionStates +using InfectionStateTimesDistributionsParameters = LogNormalDistribution::ParamType; + /** - * @brief Time that a Person is infected but not yet infectious. + * @brief Time that a Person is infected but not yet infectious in day unit */ struct IncubationPeriod { - using Type = CustomIndexArray, VirusVariant, AgeGroup>; + using Type = CustomIndexArray; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); } static std::string name() { @@ -61,113 +64,187 @@ struct IncubationPeriod { } }; -struct InfectedNoSymptomsToSymptoms { - using Type = CustomIndexArray, VirusVariant, AgeGroup>; +/** +* @brief Time that a Person is infected but presymptomatic in day unit +*/ +struct TimeInfectedNoSymptomsToSymptoms { + using Type = CustomIndexArray; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); } static std::string name() { - return "InfectedNoSymptomsToSymptoms"; + return "TimeInfectedNoSymptomsToSymptoms"; } }; -struct InfectedNoSymptomsToRecovered { - using Type = CustomIndexArray, VirusVariant, AgeGroup>; +/** +* @brief Time that a Person is infected when staying asymptomatic in day unit +*/ +struct TimeInfectedNoSymptomsToRecovered { + using Type = CustomIndexArray; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); } static std::string name() { - return "InfectedNoSymptomsToRecovered"; + return "TimeInfectedNoSymptomsToRecovered"; } }; -struct InfectedSymptomsToRecovered { - using Type = CustomIndexArray, VirusVariant, AgeGroup>; +/** +* @brief Time that a Person is infected and symptomatic but +* who do not need to be hospitalized yet in day unit +*/ +struct TimeInfectedSymptomsToSevere { + using Type = CustomIndexArray; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); } static std::string name() { - return "InfectedSymptomsToRecovered"; + return "TimeInfectedSymptomsToSevere"; } }; -struct InfectedSymptomsToSevere { - using Type = CustomIndexArray, VirusVariant, AgeGroup>; +/** +* @brief Time that a Person is infected and symptomatic who will recover in day unit +*/ +struct TimeInfectedSymptomsToRecovered { + using Type = CustomIndexArray; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); } static std::string name() { - return "InfectedSymptomsToSevere"; + return "TimeInfectedSymptomsToRecovered"; } }; -struct SevereToCritical { - using Type = CustomIndexArray, VirusVariant, AgeGroup>; +/** + * @brief Time that a Person is infected and 'simply' hospitalized before becoming critical in day unit + */ +struct TimeInfectedSevereToCritical { + using Type = CustomIndexArray; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); + } + static std::string name() + { + return "TimeInfectedSevereToCritical"; + } +}; + +/** + * @brief Time that a Person is infected and 'simply' hospitalized before recovering in day unit + */ +struct TimeInfectedSevereToRecovered { + using Type = CustomIndexArray; + static Type get_default(AgeGroup size) + { + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); + } + static std::string name() + { + return "TimeInfectedSevereToRecovered"; + } +}; + +/** + * @brief Time that a Person is treated by ICU before dying in day unit + */ +struct TimeInfectedCriticalToDead { + using Type = CustomIndexArray; + static Type get_default(AgeGroup size) + { + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); } static std::string name() { - return "SevereToCritical"; + return "TimeInfectedCriticalToDead"; } }; -struct SevereToRecovered { +/** + * @brief Time that a Person is treated by ICU before recovering in day unit + */ +struct TimeInfectedCriticalToRecovered { + using Type = CustomIndexArray; + static Type get_default(AgeGroup size) + { + return Type({VirusVariant::Count, size}, InfectionStateTimesDistributionsParameters{1., 1.}); + } + static std::string name() + { + return "TimeInfectedCriticalToRecovered"; + } +}; + +/** +* @brief the percentage of symptomatic cases +*/ +struct SymptomsPerInfectedNoSymptoms { using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, .5); } static std::string name() { - return "SevereToRecovered"; + return "SymptomaticPerInfectedNoSymptoms"; } }; -struct CriticalToRecovered { +/** +* @brief the percentage of hospitalized cases per infected cases +*/ +struct SeverePerInfectedSymptoms { using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, .5); } static std::string name() { - return "CriticalToRecovered"; + return "SeverePerInfectedSymptoms"; } }; -struct CriticalToDead { +/** +* @brief the percentage of ICU cases per hospitalized cases +*/ +struct CriticalPerInfectedSevere { using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, .5); } static std::string name() { - return "CriticalToDead"; + return "CriticalPerInfectedSevere"; } }; -struct RecoveredToSusceptible { +/** +* @brief the percentage of dead cases per ICU cases +*/ +struct DeathsPerInfectedCritical { using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { - return Type({VirusVariant::Count, size}, 1.); + return Type({VirusVariant::Count, size}, .5); } static std::string name() { - return "RecoveredToSusceptible"; + return "DeathsPerInfectedCritical"; } }; + /** * @brief Parameters for the ViralLoad course. Default values taken as constant values from the average from * https://github.com/VirologyCharite/SARS-CoV-2-VL-paper/tree/main @@ -232,6 +309,22 @@ struct InfectivityDistributions { } }; +/** + * @brief Individual virus shed factor to account for variability in infectious viral load spread. +*/ +struct VirusShedFactor { + using Type = CustomIndexArray::ParamType, VirusVariant, AgeGroup>; + static Type get_default(AgeGroup size) + { + Type default_val({VirusVariant::Count, size}, UniformDistribution::ParamType{0., 0.28}); + return default_val; + } + static std::string name() + { + return "VirusShedFactor"; + } +}; + /** * @brief Probability that an Infection is detected. */ @@ -267,6 +360,36 @@ struct MaskProtection { } }; +/** + * @brief Determines the infection rate by viral shed. Used as a linear factor. +*/ +struct InfectionRateFromViralShed { + using Type = CustomIndexArray; + static Type get_default(AgeGroup /*size*/) + { + return Type({VirusVariant::Count}, 0.1); //Julia + } + static std::string name() + { + return "InfectionRateFromViralShed"; + } +}; + +/** + * @brief Determines dampings on the infection rate. The dampings are used as a linear factor. +*/ +struct InfectionRateDampings { + using Type = std::vector>; + static Type get_default(AgeGroup /*size*/) + { + return Type(std::vector>{std::make_pair(TimePoint(0), 1.)}); //Julia + } + static std::string name() + { + return "InfectionRateDampings"; + } +}; + /** * @brief Aerosol transmission rates. */ @@ -274,7 +397,7 @@ struct AerosolTransmissionRates { using Type = CustomIndexArray; static Type get_default(AgeGroup /*size*/) { - return Type({VirusVariant::Count}, 1.0); + return Type({VirusVariant::Count}, 0.0); //Julia } static std::string name() { @@ -489,6 +612,36 @@ struct GotoWorkTimeMaximum { } }; +/** + * @brief Earliest time that a Person can return from work. + */ +struct ReturnFromWorkTimeMinimum { + using Type = CustomIndexArray; + static auto get_default(AgeGroup size) + { + return CustomIndexArray(size, hours(15)); + } + static std::string name() + { + return "ReturnFromWorkTimeMinimum"; + } +}; + +/** + * @brief Latest time that a Person can return from work. + */ +struct ReturnFromWorkTimeMaximum { + using Type = CustomIndexArray; + static auto get_default(AgeGroup size) + { + return CustomIndexArray(size, hours(18)); + } + static std::string name() + { + return "ReturnFromWorkTimeMaximum"; + } +}; + /** * @brief Earliest time that a Person can go to school. */ @@ -519,6 +672,36 @@ struct GotoSchoolTimeMaximum { } }; +/** + * @brief Earliest time that a Person can return from school. + */ +struct ReturnFromSchoolTimeMinimum { + using Type = CustomIndexArray; + static auto get_default(AgeGroup size) + { + return CustomIndexArray(size, hours(14)); + } + static std::string name() + { + return "ReturnFromSchoolTimeMinimum"; + } +}; + +/** + * @brief Latest time that a Person can return from school. + */ +struct ReturnFromSchoolTimeMaximum { + using Type = CustomIndexArray; + static auto get_default(AgeGroup size) + { + return CustomIndexArray(size, hours(17)); + } + static std::string name() + { + return "ReturnFromSchoolTimeMaximum"; + } +}; + /** * @brief The set of AgeGroups that can go to school. */ @@ -549,15 +732,50 @@ struct AgeGroupGotoWork { } }; +/** + * @brief The set of AgeGroups that can go to work. + */ +struct AgeGroupGotoShop { + using Type = CustomIndexArray; + static Type get_default(AgeGroup num_agegroups) + { + return Type(num_agegroups, false); + } + static std::string name() + { + return "AgeGroupGotoShop"; + } +}; + +/** + * @brief Determines location closures. + * x% (3rd tuple value) of locations of the given type are closed at the given TimePoint. + */ +struct LocationClosures { + using Type = std::vector>; + static Type get_default(AgeGroup /*size*/) + { + return Type(std::vector>{ + std::make_tuple(TimePoint(0), LocationType::Cemetery, 0.)}); //Julia + } + static std::string name() + { + return "LocationClosures"; + } +}; + using ParametersBase = - ParameterSet; + ParameterSet; /** * @brief Maximum number of Person%s an infectious Person can infect at the respective Location. @@ -643,116 +861,223 @@ class Parameters : public ParametersBase */ bool check_constraints() const { - for (auto age_group : make_index_range(AgeGroup{m_num_groups})) { - for (auto virus_variant : enum_members()) { + for (auto i = AgeGroup(0); i < AgeGroup(m_num_groups); ++i) { + for (auto&& v : enum_members()) { + + if (this->get()[{v, i}].params.m() < 0) { + log_error("Constraint check: Mean of parameter IncubationPeriod of virus variant {} and " + "age group {:.0f} smaller " + "than {:.4f}", + (uint32_t)v, (size_t)i, 0); + return true; + } - if (this->get()[{virus_variant, age_group}] < 0) { - log_error("Constraint check: Parameter IncubationPeriod of age group {:.0f} smaller than {:.4f}", - (size_t)age_group, 0); + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedNoSymptomsToSymptoms " + "of virus variant " + "{} and age group {:.0f} smaller " + "than {:d}", + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error("Constraint check: Parameter InfectedNoSymptomsToSymptoms of age group {:.0f} smaller " + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedNoSymptomsToRecovered of " + "virus variant " + "{} and age group {:.0f} smaller " "than {:d}", - (size_t)age_group, 0); + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error("Constraint check: Parameter InfectedNoSymptomsToRecovered of age group {:.0f} smaller " + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedSymptomsToSevere of virus " + "variant {} " + "and age group {:.0f} smaller " "than {:d}", - (size_t)age_group, 0); + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error( - "Constraint check: Parameter InfectedSymptomsToRecovered of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedSymptomsToRecovered of virus " + "variant {} " + "and age group {:.0f} smaller " + "than {:d}", + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error( - "Constraint check: Parameter InfectedSymptomsToSevere of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedSevereToCritical of virus " + "variant {} " + "and age group {:.0f} smaller " + "than {:d}", + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error("Constraint check: Parameter SevereToCritical of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedSevereToRecovered of virus " + "variant {} " + "and age group {:.0f} smaller " + "than {:d}", + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error("Constraint check: Parameter SevereToRecovered of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedCriticalToDead of virus variant {} " + "and age group {:.0f} smaller " + "than {:d}", + (uint32_t)v, (size_t)i, 0); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error("Constraint check: Parameter CriticalToDead of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}].params.m() < 0.0) { + log_error("Constraint check: Mean of parameter TimeInfectedCriticalToRecovered of virus " + "variant {} " + "and age group {:.0f} smaller " + "than {:d}", + (uint32_t)v, (size_t)i, 0); + return true; + } + + if (this->get()[{v, i}] < 0.0 || + this->get()[{v, i}] > 1.0) { + log_error("Constraint check: Parameter SymptomsPerInfectedNoSymptoms of virus variant {} and age " + "group {:.0f} smaller than {:d} or larger than {:d}", + (uint32_t)v, (size_t)i, 0, 1); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error("Constraint check: Parameter CriticalToRecovered of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}] < 0.0 || + this->get()[{v, i}] > 1.0) { + log_error("Constraint check: Parameter SeverePerInfectedSymptoms of virus variant {} and age group " + "{:.0f} smaller than {:d} or larger than {:d}", + (uint32_t)v, (size_t)i, 0, 1); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0) { - log_error( - "Constraint check: Parameter RecoveredToSusceptible of age group {:.0f} smaller than {:d}", - (size_t)age_group, 0); + if (this->get()[{v, i}] < 0.0 || + this->get()[{v, i}] > 1.0) { + log_error("Constraint check: Parameter CriticalPerInfectedSevere of virus variant {} and age group " + "{:.0f} smaller than {:d} or larger than {:d}", + (uint32_t)v, (size_t)i, 0, 1); return true; } - if (this->get()[{virus_variant, age_group}] < 0.0 || - this->get()[{virus_variant, age_group}] > 1.0) { - log_error("Constraint check: Parameter DetectInfection of age group {:.0f} smaller than {:d} or " + if (this->get()[{v, i}] < 0.0 || + this->get()[{v, i}] > 1.0) { + log_error("Constraint check: Parameter DeathsPerInfectedCritical of age group {:.0f} smaller than " + "{:d} or larger than {:d}", + (uint32_t)v, (size_t)i, 0, 1); + return true; + } + + if (this->get()[{v, i}] < 0.0 || this->get()[{v, i}] > 1.0) { + log_error("Constraint check: Parameter DetectInfection of virus variant {} and age group {:.0f} " + "smaller than {:d} or " "larger than {:d}", - (size_t)age_group, 0, 1); + (uint32_t)v, (size_t)i, 0, 1); return true; } } - if (this->get()[age_group].seconds() < 0.0 || - this->get()[age_group].seconds() > - this->get()[age_group].seconds()) { + if (this->get()[i].seconds() < 0.0 || + this->get()[i].seconds() > this->get()[i].seconds()) { log_error("Constraint check: Parameter GotoWorkTimeMinimum of age group {:.0f} smaller {:d} or " "larger {:d}", - (size_t)age_group, 0, this->get()[age_group].seconds()); + (size_t)i, 0, this->get()[i].seconds()); return true; } - if (this->get()[age_group].seconds() < - this->get()[age_group].seconds() || - this->get()[age_group] > days(1)) { + if (this->get()[i].seconds() < this->get()[i].seconds() || + this->get()[i] > days(1)) { log_error("Constraint check: Parameter GotoWorkTimeMaximum of age group {:.0f} smaller {:d} or larger " "than one day time span", - (size_t)age_group, this->get()[age_group].seconds()); + (size_t)i, this->get()[i].seconds()); return true; } - if (this->get()[age_group].seconds() < 0.0 || - this->get()[age_group].seconds() > - this->get()[age_group].seconds()) { + if (this->get()[i].seconds() < 0.0 || + this->get()[i].seconds() > + this->get()[i].seconds()) { + log_error("Constraint check: Parameter ReturnFromWorkTimeMinimum of age group {:.0f} smaller {:d} or " + "larger {:d}", + (size_t)i, 0, this->get()[i].seconds()); + return true; + } + + if (this->get()[i].seconds() < + this->get()[i].seconds() || + this->get()[i] > days(1)) { + log_error( + "Constraint check: Parameter ReturnFromWorkTimeMaximum of age group {:.0f} smaller {:d} or larger " + "than one day time span", + (size_t)i, this->get()[i].seconds()); + return true; + } + + if (this->get()[i].seconds() < 0.0 || + this->get()[i].seconds() > + this->get()[i].seconds()) { + log_error("Constraint check: Parameter ReturnFromWorkTimeMinimum of age group {:.0f} smaller {:d} or " + "larger {:d}", + (size_t)i, 0, this->get()[i].seconds()); + return true; + } + + if (this->get()[i].seconds() < + this->get()[i].seconds() || + this->get()[i] > days(1)) { + log_error( + "Constraint check: Parameter ReturnFromWorkTimeMaximum of age group {:.0f} smaller {:d} or larger " + "than one day time span", + (size_t)i, this->get()[i].seconds()); + return true; + } + + if (this->get()[i].seconds() < 0.0 || + this->get()[i].seconds() > this->get()[i].seconds()) { log_error("Constraint check: Parameter GotoSchoolTimeMinimum of age group {:.0f} smaller {:d} or " "larger {:d}", - (size_t)age_group, 0, this->get()[age_group].seconds()); + (size_t)i, 0, this->get()[i].seconds()); return true; } - if (this->get()[age_group].seconds() < - this->get()[age_group].seconds() || - this->get()[age_group] > days(1)) { + if (this->get()[i].seconds() < this->get()[i].seconds() || + this->get()[i] > days(1)) { log_error("Constraint check: Parameter GotoWorkTimeMaximum of age group {:.0f} smaller {:d} or larger " "than one day time span", - (size_t)age_group, this->get()[age_group].seconds()); + (size_t)i, this->get()[i].seconds()); + return true; + } + if (this->get()[i].seconds() < 0.0 || + this->get()[i].seconds() > + this->get()[i].seconds()) { + log_error("Constraint check: Parameter ReturnFromSchoolTimeMinimum of age group {:.0f} smaller {:d} or " + "larger {:d}", + (size_t)i, 0, this->get()[i].seconds()); + return true; + } + + if (this->get()[i].seconds() < 0.0 || + this->get()[i].seconds() > + this->get()[i].seconds()) { + log_error("Constraint check: Parameter ReturnFromSchoolTimeMinimum of age group {:.0f} smaller {:d} or " + "larger {:d}", + (size_t)i, 0, this->get()[i].seconds()); + return true; + } + + if (this->get()[i].seconds() < + this->get()[i].seconds() || + this->get()[i] > days(1)) { + log_error( + "Constraint check: Parameter ReturnFromWorkTimeMaximum of age group {:.0f} smaller {:d} or larger " + "than one day time span", + (size_t)i, this->get()[i].seconds()); return true; } } diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 34475c4bbb..9c38bfd58d 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -36,6 +36,7 @@ Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, Loca : m_location(location_id) , m_location_type(location_type) , m_assigned_locations((uint32_t)LocationType::Count, LocationId::invalid_id()) + , m_time_since_transmission(std::numeric_limits::max() / 2) , m_home_isolation_start(TimePoint(-(std::numeric_limits::max() / 2))) , m_age(age) , m_time_at_location(0) @@ -46,10 +47,12 @@ Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, Loca , m_last_transport_mode(TransportMode::Unknown) , m_test_results({TestType::Count}, TestResult()) { - m_random_workgroup = UniformDistribution::get_instance()(rng); - m_random_schoolgroup = UniformDistribution::get_instance()(rng); - m_random_goto_work_hour = UniformDistribution::get_instance()(rng); - m_random_goto_school_hour = UniformDistribution::get_instance()(rng); + m_random_workgroup = UniformDistribution::get_instance()(rng); + m_random_schoolgroup = UniformDistribution::get_instance()(rng); + m_random_goto_work_hour = UniformDistribution::get_instance()(rng); + m_random_return_from_work_hour = UniformDistribution::get_instance()(rng); + m_random_goto_school_hour = UniformDistribution::get_instance()(rng); + m_random_return_from_school_hour = UniformDistribution::get_instance()(rng); } Person::Person(const Person& other, PersonId id) @@ -65,7 +68,8 @@ bool Person::is_infected(TimePoint t) const } // subject to change if Recovered is removed if (m_infections.back().get_infection_state(t) == InfectionState::Susceptible || - m_infections.back().get_infection_state(t) == InfectionState::Recovered) { + m_infections.back().get_infection_state(t) == InfectionState::Recovered || + m_infections.back().get_infection_state(t) == InfectionState::Dead) { return false; } return true; @@ -81,11 +85,22 @@ InfectionState Person::get_infection_state(TimePoint t) const } } -void Person::add_new_infection(Infection&& inf) +void Person::add_new_infection(Infection&& inf, TimePoint current_time) { + m_time_since_transmission = current_time - inf.get_infection_start(); m_infections.push_back(std::move(inf)); } +void Person::change_time_since_transmission(const TimeSpan dt, TimePoint t) +{ + if (is_infected(t + dt)) { + m_time_since_transmission = ((t + dt) - m_infections.back().get_infection_start()); + } + else { + m_time_since_transmission = mio::abm::TimeSpan(std::numeric_limits::max() / 2); + } +} + LocationId Person::get_location() const { return m_location; @@ -132,6 +147,15 @@ TimeSpan Person::get_go_to_work_time(const Parameters& params) const return minimum_goto_work_time + seconds(seconds_after_minimum); } +TimeSpan Person::get_return_from_work_time(const Parameters& params) const +{ + TimeSpan minimum_return_from_work_time = params.get()[m_age]; + TimeSpan maximum_return_from_work_time = params.get()[m_age]; + int timeSlots = (maximum_return_from_work_time.seconds() - minimum_return_from_work_time.seconds()); + int seconds_after_minimum = int(timeSlots * m_random_return_from_work_hour); + return minimum_return_from_work_time + seconds(seconds_after_minimum); +} + TimeSpan Person::get_go_to_school_time(const Parameters& params) const { TimeSpan minimum_goto_school_time = params.get()[m_age]; @@ -141,6 +165,15 @@ TimeSpan Person::get_go_to_school_time(const Parameters& params) const return minimum_goto_school_time + seconds(seconds_after_minimum); } +TimeSpan Person::get_return_from_school_time(const Parameters& params) const +{ + TimeSpan minimum_return_from_school_time = params.get()[m_age]; + TimeSpan maximum_return_from_school_time = params.get()[m_age]; + int timeSlots = (maximum_return_from_school_time.seconds() - minimum_return_from_school_time.seconds()); + int seconds_after_minimum = int(timeSlots * m_random_return_from_school_hour); + return minimum_return_from_school_time + seconds(seconds_after_minimum); +} + bool Person::goes_to_school(TimePoint t, const Parameters& params) const { return m_random_schoolgroup < params.get().get_matrix_at(t.days())[0]; @@ -214,14 +247,14 @@ bool Person::is_compliant(PersonalRandomNumberGenerator& rng, InterventionType i ProtectionEvent Person::get_latest_protection() const { ProtectionType latest_protection_type = ProtectionType::NoProtection; - TimePoint infection_time = TimePoint(0); + TimePoint infection_time = TimePoint(0); if (!m_infections.empty()) { latest_protection_type = ProtectionType::NaturalInfection; - infection_time = m_infections.back().get_start_date(); + infection_time = m_infections.back().get_start_date(); } if (!m_vaccinations.empty() && infection_time.days() <= m_vaccinations.back().time.days()) { latest_protection_type = m_vaccinations.back().type; - infection_time = m_vaccinations.back().time; + infection_time = m_vaccinations.back().time; } return ProtectionEvent{latest_protection_type, infection_time}; } @@ -240,7 +273,7 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const void Person::set_mask(MaskType type, TimePoint t) { m_mask.change_mask(type, t); -} +} void Person::add_test_result(TimePoint t, TestType type, bool result) { diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 28a81eb3f7..fcaeb4fbf3 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -112,7 +112,7 @@ class Person * @brief Adds a new Infection to the list of Infection%s. * @param[in] inf The new Infection. */ - void add_new_infection(Infection&& inf); + void add_new_infection(Infection&& inf, TimePoint current_time = TimePoint(0)); /** * @brief Get the AgeGroup of this Person. @@ -207,6 +207,15 @@ class Person */ TimeSpan get_go_to_work_time(const Parameters& params) const; + /** + * @brief Draw at what time the Person returns from work. + * Every Person has a random number to determine what time to return from work. + * Depending on this number Person decides what time it has to return from work. + * @param[in] params Parameters that describe the migration between Location%s. + * @return The time of returning from work. + */ + TimeSpan get_return_from_work_time(const Parameters& params) const; + /** * @brief Draw if the Person goes to school or stays at home during lockdown. * Every Person has a random number that determines if they go to school in case of a lockdown. @@ -217,7 +226,7 @@ class Person bool goes_to_school(TimePoint t, const Parameters& params) const; /** - * @brief Draw at what time the Person goes to work. + * @brief Draw at what time the Person goes to school. * Every Person has a random number to determine what time to go to school. * Depending on this number Person decides what time has to go to school. * @param[in] params Parameters that describe the mobility between Location%s. @@ -225,6 +234,15 @@ class Person */ TimeSpan get_go_to_school_time(const Parameters& params) const; + /** + * @brief Draw at what time the Person return from school. + * Every Person has a random number to determine what time to return from school. + * Depending on this number Person decides what time it has to return from school. + * @param[in] params Parameters that describe the migration between Location%s. + * @return The time of returning from school. + */ + TimeSpan get_return_from_school_time(const Parameters& params) const; + /** * @brief Answers the question if a Person is currently in quarantine. * If a Person is in quarantine this Person cannot change to Location%s other than Home or the Hospital. @@ -420,6 +438,13 @@ class Person */ TestResult get_test_result(TestType type) const; + TimeSpan get_time_since_transmission() const + { + return m_time_since_transmission; + }; + + void change_time_since_transmission(const TimeSpan dt, TimePoint t); + private: LocationId m_location; ///< Current Location of the Person. LocationType m_location_type; ///< Type of the current Location. @@ -427,13 +452,16 @@ class Person Person always visits the same Home or School etc. */ std::vector m_vaccinations; ///< Vector with all vaccinations the Person has received. std::vector m_infections; ///< Vector with all Infection%s the Person had. + TimeSpan m_time_since_transmission; TimePoint m_home_isolation_start; ///< TimePoint when the Person started isolation at home. AgeGroup m_age; ///< AgeGroup the Person belongs to. TimeSpan m_time_at_location; ///< Time the Person has spent at its current Location so far. double m_random_workgroup; ///< Value to determine if the Person goes to work or works from home during lockdown. double m_random_schoolgroup; ///< Value to determine if the Person goes to school or stays at home during lockdown. double m_random_goto_work_hour; ///< Value to determine at what time the Person goes to work. + double m_random_return_from_work_hour; ///< Value to determine at what time the Person returns from work. double m_random_goto_school_hour; ///< Value to determine at what time the Person goes to school. + double m_random_return_from_school_hour; ///< Value to determine at what time the Person returns from school. Mask m_mask; ///< The Mask of the Person. std::vector m_compliance; ///< Vector of compliance values for all #InterventionType%s. Values from 0 to 1. diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp new file mode 100755 index 0000000000..e534b8cec0 --- /dev/null +++ b/cpp/models/abm/world.cpp @@ -0,0 +1,241 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele, Majid Abedi, Elisabeth Kluth, Carlotta Gerstein, Martin J. Kuehn, David Kerkmann, Khoa Nguyen +* +* 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 "abm/world.h" +#include "abm/location_type.h" +#include "abm/mask_type.h" +#include "abm/person.h" +#include "abm/location.h" +#include "abm/migration_rules.h" +#include "abm/infection.h" +#include "abm/vaccine.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/mioomp.h" +#include "memilio/utils/random_number_generator.h" +#include "memilio/utils/stl_util.h" + +namespace mio +{ +namespace abm +{ + +LocationId World::add_location(LocationType type, uint32_t num_cells) +{ + LocationId id = {static_cast(m_locations.size()), type}; + m_locations.emplace_back(std::make_unique(id, parameters.get_num_groups(), num_cells)); + m_has_locations[size_t(type)] = true; + return id; +} + +Person& World::add_person(const LocationId id, AgeGroup age) +{ + assert(age.get() < parameters.get_num_groups()); + uint32_t person_id = static_cast(m_persons.size()); + m_persons.push_back(std::make_unique(m_rng, get_individualized_location(id), age, person_id)); + auto& person = *m_persons.back(); + person.set_assigned_location(m_cemetery_id); + get_individualized_location(id).add_person(person); + return person; +} + +void World::evolve(TimePoint t, TimeSpan dt) +{ + begin_step(t, dt); + log_info("ABM World interaction."); + interaction(t, dt); + log_info("ABM World migration."); + migration(t, dt); +} + +void World::interaction(TimePoint t, TimeSpan dt) +{ + PRAGMA_OMP(parallel for) + for (auto i = size_t(0); i < m_persons.size(); ++i) { + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + person->interact(personal_rng, t, dt, parameters); + } +} + +void World::migration(TimePoint t, TimeSpan dt) +{ + PRAGMA_OMP(parallel for) + for (auto i = size_t(0); i < m_persons.size(); ++i) { + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + + auto try_migration_rule = [&](auto rule) -> bool { + //run migration rule and check if migration can actually happen + auto target_type = rule(personal_rng, *person, t, dt, parameters); + auto& target_location = find_location(target_type, *person); + auto& current_location = person->get_location(); + if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + if (target_location != current_location && + target_location.get_number_persons() < target_location.get_capacity().persons) { + bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); + if (wears_mask) { + person->migrate_to(target_location); + } + return true; + } + } + return false; + }; + + //run migration rules one after the other if the corresponding location type exists + //shortcutting of bool operators ensures the rules stop after the first rule is applied + if (m_use_migration_rules) { + (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || + (has_locations({LocationType::School, LocationType::Home}) && try_migration_rule(&go_to_school)) || + (has_locations({LocationType::Work, LocationType::Home}) && try_migration_rule(&go_to_work)) || + (has_locations({LocationType::BasicsShop, LocationType::Home}) && try_migration_rule(&go_to_shop)) || + (has_locations({LocationType::SocialEvent, LocationType::Home}) && try_migration_rule(&go_to_event)) || + (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); + } + else { + //no daily routine migration, just infection related + (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || + (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); + } + } + + // check if a person makes a trip + bool weekend = t.is_weekend(); + size_t num_trips = m_trip_list.num_trips(weekend); + + if (num_trips != 0) { + while (m_trip_list.get_current_index() < num_trips && + m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds()) { + auto& trip = m_trip_list.get_next_trip(weekend); + auto& person = m_persons[trip.person_id]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + if (!person->is_in_quarantine(t, parameters) && person->get_infection_state(t) != InfectionState::Dead) { + auto& target_location = get_individualized_location(trip.migration_destination); + if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + person->apply_mask_intervention(personal_rng, target_location); + person->migrate_to(target_location, trip.trip_mode); + } + } + m_trip_list.increase_index(); + } + } + if (((t).days() < std::floor((t + dt).days()))) { + m_trip_list.reset_index(); + } +} + +void World::begin_step(TimePoint t, TimeSpan dt) +{ + m_testing_strategy.update_activity_status(t); + PRAGMA_OMP(parallel for) + for (auto i = size_t(0); i < m_locations.size(); ++i) { + auto&& location = m_locations[i]; + location->cache_exposure_rates(t, dt, parameters.get_num_groups()); + } +} + +auto World::get_locations() const -> Range> +{ + return std::make_pair(ConstLocationIterator(m_locations.begin()), ConstLocationIterator(m_locations.end())); +} + +auto World::get_persons() const -> Range> +{ + return std::make_pair(ConstPersonIterator(m_persons.begin()), ConstPersonIterator(m_persons.end())); +} + +const Location& World::get_individualized_location(LocationId id) const +{ + return *m_locations[id.index]; +} + +Location& World::get_individualized_location(LocationId id) +{ + return *m_locations[id.index]; +} + +const Location& World::find_location(LocationType type, const Person& person) const +{ + auto index = person.get_assigned_location_index(type); + assert(index != INVALID_LOCATION_INDEX && "unexpected error."); + return get_individualized_location({index, type}); +} + +Location& World::find_location(LocationType type, const Person& person) +{ + auto index = person.get_assigned_location_index(type); + assert(index != INVALID_LOCATION_INDEX && "unexpected error."); + return get_individualized_location({index, type}); +} + +size_t World::get_subpopulation_combined(TimePoint t, InfectionState s) const +{ + return std::accumulate(m_locations.begin(), m_locations.end(), (size_t)0, + [t, s](size_t running_sum, const std::unique_ptr& loc) { + return running_sum + loc->get_subpopulation(t, s); + }); +} + +size_t World::get_subpopulation_combined_per_location_type(TimePoint t, InfectionState s, LocationType type) const +{ + return std::accumulate(m_locations.begin(), m_locations.end(), (size_t)0, + [t, s, type](size_t running_sum, const std::unique_ptr& loc) { + return loc->get_type() == type ? running_sum + loc->get_subpopulation(t, s) + : running_sum; + }); +} + +TripList& World::get_trip_list() +{ + return m_trip_list; +} + +const TripList& World::get_trip_list() const +{ + return m_trip_list; +} + +void World::use_migration_rules(bool param) +{ + m_use_migration_rules = param; +} + +bool World::use_migration_rules() const +{ + return m_use_migration_rules; +} + +TestingStrategy& World::get_testing_strategy() +{ + return m_testing_strategy; +} + +const TestingStrategy& World::get_testing_strategy() const +{ + return m_testing_strategy; +} + +} // namespace abm +} // namespace mio diff --git a/cpp/munich_postprocessing/CMakeLists.txt b/cpp/munich_postprocessing/CMakeLists.txt new file mode 100644 index 0000000000..443dbea87a --- /dev/null +++ b/cpp/munich_postprocessing/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(output_processing output_processing.cpp) +target_link_libraries(output_processing PRIVATE memilio) + +# target_compile_options(output_processing PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/munich_postprocessing/output_processing.cpp b/cpp/munich_postprocessing/output_processing.cpp new file mode 100644 index 0000000000..1d3569e043 --- /dev/null +++ b/cpp/munich_postprocessing/output_processing.cpp @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "memilio/io/hdf5_cpp.h" +#include "models/abm/location_type.h" +#include "memilio/utils/logging.h" +#include "boost/filesystem.hpp" +#include "output_processing.h" + +std::string determine_age_group(uint32_t age) +{ + if (age <= 4) { + return "0-4"; + } + else if (age <= 15) { + return "5-15"; + } + else if (age <= 34) { + return "16-34"; + } + else if (age <= 59) { + return "35-59"; + } + else if (age <= 79) { + return "60-79"; + } + else if (age > 79) { + return "80+"; + } + else { + return "NaN"; + } +} + +int stringToMinutes(const std::string& input) +{ + size_t colonPos = input.find(":"); + if (colonPos == std::string::npos) { + // Handle invalid input (no colon found) + return -1; // You can choose a suitable error code here. + } + + std::string xStr = input.substr(0, colonPos); + std::string yStr = input.substr(colonPos + 1); + + int x = std::stoi(xStr); + int y = std::stoi(yStr); + return x * 60 + y; +} + +int longLatToInt(const std::string& input) +{ + double y = std::stod(input) * 1e+5; //we want the 5 numbers after digit + return (int)y; +} + +void split_line(std::string string, std::vector* row) +{ + std::vector strings; + boost::split(strings, string, boost::is_any_of(",")); + std::transform(strings.begin(), strings.end(), std::back_inserter(*row), [&](std::string s) { + if (s.find(":") != std::string::npos) { + return stringToMinutes(s); + } + else if (s.find(".") != std::string::npos) { + return longLatToInt(s); + } + else { + return std::stoi(s); + } + }); +} + +void calculate_agents_per_quantity(std::string output_file, std::string save_file, std::string area_or_type) +{ + auto start = std::chrono::high_resolution_clock::now(); + //Load H5 file + mio::H5File sim_output{H5Fopen(output_file.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)}; + std::string group_name = "data"; + //Open group + mio::H5Group h5group{H5Gopen(sim_output.id, group_name.c_str(), H5P_DEFAULT)}; + //Open dataset + mio::H5DataSet dset_loc_ids{H5Dopen(h5group.id, "loc_ids", H5P_DEFAULT)}; + //Open dataspace + mio::H5DataSpace dataspace{H5Dget_space(dset_loc_ids.id)}; + //Get dimensions + int ndims = H5Sget_simple_extent_ndims(dataspace.id); + hsize_t* dims = (hsize_t*)malloc(ndims * sizeof(hsize_t)); + H5Sget_simple_extent_dims(dataspace.id, dims, NULL); + + hsize_t rows = dims[0]; + hsize_t cols = dims[1]; + std::cout << "Num agents: " << rows << std::endl; + int* data = (int*)malloc(rows * cols * sizeof(int)); + std::cout << "Starting reading dataset...\n"; + herr_t status = H5Dread(dset_loc_ids.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); + std::cout << "Finished reading dataset!\n"; + std::vector data1(data, data + dims[0] * dims[1]); + std::cout << "Starting getting max area...\n"; + int max_area = *std::max_element(data1.begin(), data1.end()); + std::cout << "Got max area!\n"; + //Save data in matrix + std::vector> matrix(dims[1], std::vector(max_area + 1)); + for (size_t i = 0; i < dims[0]; ++i) { + for (size_t j = 0; j < dims[1]; ++j) { + int loc_type = data[i * dims[1] + j]; + matrix[j][loc_type] += 1; + } + } + std::cout << "Created matrix!\n"; + + auto file = fopen(save_file.c_str(), "w"); + if (file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", save_file); + } + else { + fprintf(file, "t,%s,NumAgents", area_or_type.c_str()); + fprintf(file, "\n"); + for (size_t i = 0; i < matrix.size(); ++i) { + for (size_t j = 0; j < matrix[0].size(); ++j) { + fprintf(file, "%d,", static_cast(i)); + fprintf(file, "%d,", static_cast(j)); + fprintf(file, "%d,", matrix[i][j]); + fprintf(file, "\n"); + } + } + fclose(file); + } + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + std::cout << "Time to calculate agents per quantity: " << duration.count() << "[s]" << std::endl; +} + +void calculate_agents_per_quantity_age_groups(std::string output_file, std::string save_file, std::string area_or_type, + std::string person_file) +{ + auto start = std::chrono::high_resolution_clock::now(); + std::vector ages; + // Read in persons + const boost::filesystem::path p = person_file; + if (!boost::filesystem::exists(p)) { + mio::log_error("Cannot read in data. File does not exist."); + } + // File pointer + std::fstream fin; + + // Open an existing file + fin.open(person_file, std::ios::in); + std::vector row; + std::vector row_string; + std::string line; + + // Read the Titles from the Data file + std::getline(fin, line); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + std::vector titles; + boost::split(titles, line, boost::is_any_of(",")); + uint32_t count_of_titles = 0; + std::map index = {}; + for (auto const& title : titles) { + index.insert({title, count_of_titles}); + row_string.push_back(title); + count_of_titles++; + } + + while (std::getline(fin, line)) { + row.clear(); + + // read columns in this row + split_line(line, &row); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + + uint32_t age = row[index["age"]]; + ages.push_back(age); + } + + //Load H5 file + mio::H5File sim_output{H5Fopen(output_file.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)}; + std::string group_name = "data"; + //Open group + mio::H5Group h5group{H5Gopen(sim_output.id, group_name.c_str(), H5P_DEFAULT)}; + //Open dataset + mio::H5DataSet dset_loc_ids{H5Dopen(h5group.id, "loc_ids", H5P_DEFAULT)}; + //Open dataspace + mio::H5DataSpace dataspace{H5Dget_space(dset_loc_ids.id)}; + //Get dimensions + int ndims = H5Sget_simple_extent_ndims(dataspace.id); + hsize_t* dims = (hsize_t*)malloc(ndims * sizeof(hsize_t)); + H5Sget_simple_extent_dims(dataspace.id, dims, NULL); + + hsize_t rows = dims[0]; + hsize_t cols = dims[1]; + int* data = (int*)malloc(rows * cols * sizeof(int)); + herr_t status = H5Dread(dset_loc_ids.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); + std::vector data1(data, data + dims[0] * dims[1]); + int max_area = *std::max_element(data1.begin(), data1.end()); + + //Age groups + std::map age_groups{{"0-4", 0}, {"5-15", 1}, {"16-34", 2}, + {"35-59", 3}, {"60-79", 4}, {"80+", 5}}; + //Age groups + std::map index_to_age_groups{{0, "0-4"}, {1, "5-15"}, {2, "16-34"}, + {3, "35-59"}, {4, "60-79"}, {5, "80+"}}; + //Save data in matrix + std::vector>> matrix(dims[1], + std::vector>(max_area + 1, std::vector(6))); + for (size_t agent = 0; agent < dims[0]; ++agent) { + for (size_t j = 0; j < dims[1]; ++j) { + int loc_type = data[agent * dims[1] + j]; + std::string ag = determine_age_group(ages[agent]); + if (ag == "NaN") { + mio::log(mio::LogLevel::err, "Age group does not exists.}"); + } + matrix[j][loc_type][age_groups[ag]] += 1; + } + } + std::cout << "Created matrix!\n"; + + auto file = fopen(save_file.c_str(), "w"); + if (file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", save_file); + } + else { + fprintf(file, "t,%s,AgeGroup,NumAgents", area_or_type.c_str()); + fprintf(file, "\n"); + for (size_t agent = 0; agent < matrix.size(); ++agent) { + for (size_t quantity = 0; quantity < matrix[0].size(); ++quantity) { + for (size_t age_group = 0; age_group < matrix[0][0].size(); ++age_group) { + fprintf(file, "%d,", static_cast(agent)); + fprintf(file, "%d,", static_cast(quantity)); + fprintf(file, "%s,", index_to_age_groups[age_group].c_str()); + fprintf(file, "%d,", matrix[agent][quantity][age_group]); + fprintf(file, "\n"); + } + } + } + fclose(file); + } + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + std::cout << "Time to calculate agents per quantity age groups: " << duration.count() << "[s]" << std::endl; +} + +int main() +{ + // int sim_num = 0; + // std::string output_file_loctype = "/hpc_data/bick_ju/OnlyMovement/" + std::to_string(sim_num) + "_output_v5.h5"; + // std::string output_file_ww_area = "/hpc_data/bick_ju/OnlyMovement/" + std::to_string(sim_num) + "_output_v4.h5"; + // std::string save_file_loctype = + // "/hpc_data/bick_ju/OnlyMovement/" + std::to_string(sim_num) + "_num_agents_loc_type.txt"; + // std::string save_file_area = "/hpc_data/bick_ju/OnlyMovement/" + std::to_string(sim_num) + "_num_agents_area.txt"; + // calculate_agents_per_quantity(output_file_loctype, save_file_loctype, "locType"); + // calculate_agents_per_quantity(output_file_ww_area, save_file_area, "area"); + std::string output_dir_infections = + "/localdata1/bick_ju/code/inside-demonstrator-munich/pycode/examples/simulation/ABM Demonstrator/output/test/"; + std::string save_file_loctype_infections = output_dir_infections + "num_agents_infections_loctype.txt"; + std::string save_file_ww_area_infections = output_dir_infections + "num_agents_infections_area.txt"; + int num_sims = 1; + calculate_infections_per_quantity(output_dir_infections, save_file_loctype_infections, "locType", "v5", num_sims); + calculate_infections_per_quantity(output_dir_infections, save_file_ww_area_infections, "area", "v4", num_sims); + // std::string save_file_loctype_ag = + // "/hpc_data/bick_ju/OnlyMovement/" + std::to_string(sim_num) + "_num_agents_loc_type_ag.txt"; + // std::string save_file_area_ag = + // "/hpc_data/bick_ju/OnlyMovement/" + std::to_string(sim_num) + "_num_agents_area_ag.txt"; + // std::string person_file = "/home/bick_ju/Documents/INSIDe/persons/persons_final_corr.csv"; + // calculate_agents_per_quantity_age_groups(output_file_loctype, save_file_loctype_ag, "locType", person_file); + // calculate_agents_per_quantity_age_groups(output_file_ww_area, save_file_area_ag, "area", person_file); + return 0; +} diff --git a/cpp/munich_postprocessing/output_processing.h b/cpp/munich_postprocessing/output_processing.h new file mode 100644 index 0000000000..f862f82973 --- /dev/null +++ b/cpp/munich_postprocessing/output_processing.h @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include "memilio/io/hdf5_cpp.h" +#include "memilio/utils/logging.h" +#include "boost/filesystem.hpp" + +void calculate_infections_per_quantity(std::string output_path, std::string save_file, std::string area_or_type, + std::string v4_or_v5, int num_sims) +{ + auto start = std::chrono::high_resolution_clock::now(); + auto txt_file = fopen(save_file.c_str(), "w"); + if (txt_file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", save_file); + } + fprintf(txt_file, "Sim,t,%s,NumInfected,NewInfections,NumAgents", area_or_type.c_str()); + fprintf(txt_file, "\n"); + + for (int sim = 0; sim < num_sims; ++sim) { + auto start_sim = std::chrono::high_resolution_clock::now(); + //Location dataset + std::string file = output_path + std::to_string(sim) + "_output_" + v4_or_v5 + ".h5"; + //Load H5 file + mio::H5File sim_output{H5Fopen(file.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT)}; + std::string group_name = "data"; + //Open group + mio::H5Group h5group{H5Gopen(sim_output.id, group_name.c_str(), H5P_DEFAULT)}; + //Open dataset + mio::H5DataSet dset_loc_ids{H5Dopen(h5group.id, "loc_ids", H5P_DEFAULT)}; + //Open dataspace + mio::H5DataSpace dataspace{H5Dget_space(dset_loc_ids.id)}; + //Get dimensions + int ndims = H5Sget_simple_extent_ndims(dataspace.id); + hsize_t* dims = (hsize_t*)malloc(ndims * sizeof(hsize_t)); + H5Sget_simple_extent_dims(dataspace.id, dims, NULL); + hsize_t rows = dims[0]; + hsize_t cols = dims[1]; + int* data = (int*)malloc(rows * cols * sizeof(int)); + //Read data to vector + herr_t status = H5Dread(dset_loc_ids.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); + std::vector data1(data, data + dims[0] * dims[1]); + //Get the maximum area/location type; is needed as dimension for result matrix + int max_area = *std::max_element(data1.begin(), data1.end()); + + //Transmission recovery tp dataset + //Open dataset + mio::H5DataSet dset_transm_rec_tp{H5Dopen(h5group.id, "transm_recovery_tp", H5P_DEFAULT)}; + //Open dataspace + mio::H5DataSpace dataspace2{H5Dget_space(dset_transm_rec_tp.id)}; + //Get dimensions + int ndims2 = H5Sget_simple_extent_ndims(dataspace2.id); + hsize_t* dims2 = (hsize_t*)malloc(ndims2 * sizeof(hsize_t)); + H5Sget_simple_extent_dims(dataspace2.id, dims2, NULL); + hsize_t rows2 = dims2[0]; + hsize_t cols2 = dims2[1]; + int* data2 = (int*)malloc(rows2 * cols2 * sizeof(int)); + //Read data to vector + herr_t status2 = H5Dread(dset_transm_rec_tp.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, data2); + + //Save data in matrix + //Number of new infections for every time point per area/location type and + std::vector> matrix_new_infections(dims[1], std::vector(max_area + 1)); + //Number of infected agents for every time point per area/location type + std::vector> matrix_infected(dims[1], std::vector(max_area + 1)); + //Number of total agents for every time point per area/location type + std::vector> matrix(dims[1], std::vector(max_area + 1)); + //Iterate over all agents + for (size_t agent = 0; agent < dims[0]; ++agent) { + for (size_t t = 0; t < dims[1]; ++t) { + int loc_type = data[agent * dims[1] + t]; + matrix[t][loc_type] += 1; + } + int t_transm = data2[agent * dims2[1]]; + //If time point of transmission is bigger than last time point, the agent has not been infected during the simulation + if (t_transm > int(dims[1])) { + continue; + } + //Get recovery time point or simulation end time point if agent does not recover during the simulation time frame + int t_rec = std::min(data2[agent * dims2[1] + 1], static_cast(dims[1])); + if (t_transm >= 0) { //Agent got infected during the simulation + int loc_type_at_infection = data[agent * dims[1] + t_transm]; + matrix_new_infections[t_transm][loc_type_at_infection] += 1; + } + for (int j = std::max(0, t_transm); j < t_rec; ++j) { + int loc_type = data[agent * dims[1] + j]; + matrix_infected[j][loc_type] += 1; + } + } + + for (size_t t = 0; t < matrix_infected.size(); ++t) { + for (size_t loc = 0; loc < matrix_infected[0].size(); ++loc) { + fprintf(txt_file, "%d,", sim); + fprintf(txt_file, "%d,", static_cast(t)); + fprintf(txt_file, "%d,", static_cast(loc)); + fprintf(txt_file, "%d,", matrix_infected[t][loc]); + fprintf(txt_file, "%d,", matrix_new_infections[t][loc]); + fprintf(txt_file, "%d,", matrix[t][loc]); + fprintf(txt_file, "\n"); + } + } + auto stop_sim = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop_sim - start_sim); + std::cout << "Sim " << std::to_string(sim) + << ": Time to calculate infections per quantity: " << duration.count() << "[s]" << std::endl; + } + fclose(txt_file); + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + std::cout << "Total time to calculate infections per quantity: " << duration.count() << "[s]" << std::endl; +} diff --git a/cpp/simulations/CMakeLists.txt b/cpp/simulations/CMakeLists.txt index a784b83ba5..c1d8c6ab49 100644 --- a/cpp/simulations/CMakeLists.txt +++ b/cpp/simulations/CMakeLists.txt @@ -19,3 +19,7 @@ if(MEMILIO_HAS_JSONCPP AND MEMILIO_HAS_HDF5) target_link_libraries(abm_braunschweig PRIVATE memilio abm Boost::filesystem ${HDF5_C_LIBRARIES}) target_compile_options(abm_braunschweig PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) endif() + +# add_executable(abm_demonstrator abm_INSIDe_demonstrator.cpp) +# target_link_libraries(abm_demonstrator PRIVATE memilio abm) +# target_compile_options(abm_demonstrator PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/simulations/abm.cpp b/cpp/simulations/abm.cpp index ba25076ac1..dff1551188 100644 --- a/cpp/simulations/abm.cpp +++ b/cpp/simulations/abm.cpp @@ -469,153 +469,101 @@ void set_parameters(mio::abm::Parameters params) params.set({{mio::abm::VirusVariant::Count, mio::AgeGroup(num_age_groups)}, 4.}); //0-4 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.276; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.092; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.142; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.276; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.092; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.142; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; //5-14 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.276; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.276; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.092; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.142; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.142; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.186; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; //15-34 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.139; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.003; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.157; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.013; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.139; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.003; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.157; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.013; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.126; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; //35-59 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.136; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.009; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.113; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.05; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.136; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.009; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.113; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.05; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; //60-79 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.123; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.024; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.083; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.123; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.024; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.083; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; //80+ - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.315; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.115; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.033; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.055; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.115; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.033; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.055; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; // Set each parameter for vaccinated people including personal infection and vaccine protection levels. // Summary: https://doi.org/10.1038/s41577-021-00550-x, - - //0-4 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.161; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.132; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.0; - - //5-14 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.161; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = - 0.132; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.0; - - //15-34 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.142; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.157; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.013; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.0; - - //35-59 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = - 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = - 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.141; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.003; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.113; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.05; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.0; - - //60-79 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = - 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = - 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.136; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.009; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.083; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.0; - - //80+ - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = - 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.133; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.012; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.055; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.0; } /** diff --git a/cpp/simulations/abm_INSIDe_demonstrator.cpp b/cpp/simulations/abm_INSIDe_demonstrator.cpp new file mode 100644 index 0000000000..65edd506f2 --- /dev/null +++ b/cpp/simulations/abm_INSIDe_demonstrator.cpp @@ -0,0 +1,1156 @@ +/* +* Copyright (C) 2020-2023 German Aerospace Center (DLR-SC) +* +* Authors: Daniel Abele, Khoa Nguyen +* +* 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 "abm/household.h" +#include "abm/infection_state.h" +#include "abm/location.h" +#include "abm/simulation.h" +#include "memilio/io/result_io.h" +#include "memilio/utils/uncertain_value.h" +#include "memilio/io/io.h" +#include "memilio/io/history.h" +#include "memilio/utils/random_number_generator.h" +#include "boost/filesystem.hpp" +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; + +// Assign the name to general age group. +size_t num_age_groups = 6; +const auto age_group_0_to_4 = mio::AgeGroup(0); +const auto age_group_5_to_14 = mio::AgeGroup(1); +const auto age_group_15_to_34 = mio::AgeGroup(2); +const auto age_group_35_to_59 = mio::AgeGroup(3); +const auto age_group_60_to_79 = mio::AgeGroup(4); +const auto age_group_80_plus = mio::AgeGroup(5); + +struct LocationMapping { + std::string inputId; + std::vector modelId{}; +}; + +std::string convert_loc_id_to_string(std::tuple tuple_id) +{ + std::string locationType = std::to_string(static_cast(std::get<0>(tuple_id))); + std::string locationIndex = std::to_string(std::get<1>(tuple_id)); + if (static_cast(std::get<0>(tuple_id)) < 10) { + locationType = "0" + locationType; + } + if (std::get<1>(tuple_id) < 10) { + locationIndex = "0" + locationIndex; + } + + return "I" + locationType + locationIndex; +} + +std::vector> get_agents_per_location( + std::tuple loc_id, + std::vector>& log) +{ + std::vector> agents_per_location; + for (auto& log_tuple : log) { + if (std::get<0>(log_tuple).type == std::get<0>(loc_id) && std::get<0>(log_tuple).index == std::get<1>(loc_id)) { + agents_per_location.push_back(std::make_tuple(std::get<1>(log_tuple), std::get<2>(log_tuple))); + } + } + return agents_per_location; +} + +/** + * read input test file and save input areas and inhabitants per area + * @param[in, out] areas vector that is filled with area types and area ids from input file + * @param[in, out] inhabitants vector that is filled with the inhabitants per area given in the input file + * @param[in] input_dir path to input txt file +*/ +void read_txt(std::vector>& areas, std::vector& inhabitants, + const fs::path& input_dir) +{ + std::ifstream file; + file.open(input_dir.string()); + std::string line = ""; + while (std::getline(file, line)) { + int inhabitant; + std::string Id; + std::string area; + std::string tmpString; + std::stringstream inputString(line); + std::getline(inputString, Id, ','); + std::getline(inputString, tmpString, ','); + inhabitant = atoi(tmpString.c_str()); + inhabitants.push_back(inhabitant); + std::getline(inputString, area, ','); + areas.push_back(std::make_pair(Id, area)); + line = ""; + } +} + +/** + * Add location to world + * @param[in] world world object the location is added to + * @param[in] type location type + * @param[in] max_contacts maximum number of contacts at the location + * @param[in] persons maximum number of persons for location capacity + * @param[in] volume maximum volume for location capacity + * @return added location +*/ +mio::abm::LocationId add_location(mio::abm::World& world, mio::abm::LocationType type, double max_contacts, int persons, + int volume) +{ + //add a location of given type to the world + auto location = world.add_location(type); + //set maximum number of contacts and capacity //TODO + world.get_individualized_location(location).get_infection_parameters().set(max_contacts); + world.get_individualized_location(location).set_capacity(persons, volume); + + return location; +} + +/** + * Insert the abm location Ids to mapping for the corresponding input area id. + * @param[in, out] locationIds vector that maps every input area id to the corresponding abm location ids + * @param[in] inputId input area id that is added to mapping vector + * @param[in] locations abm locations that correspond to inputId + * @return mapping vector +*/ +void insert_locations_to_map(std::vector& locationIds, std::string& inputId, + std::vector& locations) +{ + LocationMapping map; + map.inputId = inputId; + std::string locationType; + std::string locationIndex; + //An abm locationId consists of a type and an index. + //For the mapping the locationId is stored as a string of the form xxyy where xx specifies + //the location type and yy the location index + for (auto& location : locations) { + locationType = std::to_string(int(location.type)); + locationIndex = std::to_string(location.index); + if (int(location.type) < 10) { + locationType = "0" + locationType; + } + if (location.index < 10) { + locationIndex = "0" + locationIndex; + } + map.modelId.push_back((locationType + locationIndex)); + } + + locationIds.push_back(map); +} + +/** + * Make a one-person household group + * @param[in] member household member + * @param[in] number_of_households number of one-person households in the household group + * @return household_group The one-person household group + * +*/ +mio::abm::HouseholdGroup make_one_person_households(const mio::abm::HouseholdMember& member, int number_of_households) +{ + auto household_group = mio::abm::HouseholdGroup(); + for (int hh = 0; hh < number_of_households; ++hh) { + auto household = mio::abm::Household(); + household.add_members(member, 1); + //add one-person household to household group + household_group.add_households(household, 1); + } + return household_group; +} + +/** + * Make a multiple-person household group + * @param[in] child household member representing a child + * @param[in] parent household member representing a parent + * @param[in] other random household member + * @param[in] household_size household size e.g 2-person household, 3-person household etc. + * @param[in] number_of_two_parent_households number of households with two parents and (household_size - 2) children + * @param[in] number_of_one_parent_households number of households with one parent and (household_size - 1) children + * @param[in] number_of_other_households number of households with random members + * @return household_group multiple-person household group +*/ +mio::abm::HouseholdGroup make_multiple_person_households(const mio::abm::HouseholdMember& child, + const mio::abm::HouseholdMember& parent, + const mio::abm::HouseholdMember& other, int household_size, + int number_of_two_parent_households, + int number_of_one_parent_households, + int number_of_other_households) +{ + auto household_group = mio::abm::HouseholdGroup(); + + //Add two parent households + auto hh_two_parents = mio::abm::Household(); + hh_two_parents.add_members(child, household_size - 2); + hh_two_parents.add_members(parent, 2); + household_group.add_households(hh_two_parents, number_of_two_parent_households); + + //Add one parent households + auto hh_one_parent = mio::abm::Household(); + hh_one_parent.add_members(child, household_size - 1); + hh_one_parent.add_members(parent, 1); + household_group.add_households(hh_one_parent, number_of_one_parent_households); + + //add other households + auto hh_other = mio::abm::Household(); + hh_other.add_members(other, household_size); + household_group.add_households(hh_other, number_of_other_households); + + return household_group; +} + +/** + * Add households to the world for a given number of inhabitants. + * @param[in, out] world + * @param[in] distribution vector containing the percentages of 1-person, 2-person, ... households + * @param[in] num_inhabitants number of inhabitants that should be distributed to households + * @return locations vector with location ids of the added households +*/ +std::vector add_households(mio::abm::World& world, std::vector& distribution, + int num_inhabitants) +{ + //vector that saves the number of households for every household size + std::vector households(distribution.size()); + size_t household_size; + std::vector locations; + //index of the first new household + size_t new_index = world.get_locations().size(); + while (num_inhabitants > 0) { + //draw household size from the given distribution + household_size = mio::DiscreteDistribution::get_instance()(world.get_rng(), distribution); + //increase the number of households of the drawn household size + households[household_size] += 1; + num_inhabitants -= (int)(household_size + 1); + } + + //One-Person Households + auto one_person_household_member = mio::abm::HouseholdMember(num_age_groups); + one_person_household_member.set_age_weight(age_group_15_to_34, 5); + one_person_household_member.set_age_weight(age_group_35_to_59, 6); + one_person_household_member.set_age_weight(age_group_60_to_79, 4); + one_person_household_member.set_age_weight(age_group_80_plus, 2); + int number_of_one_person_households = households[0]; + + auto one_person_household_group = + make_one_person_households(one_person_household_member, number_of_one_person_households); + add_household_group_to_world(world, one_person_household_group); + + //Members for multiple person households + auto child = mio::abm::HouseholdMember(num_age_groups); + child.set_age_weight(age_group_0_to_4, 1); + child.set_age_weight(age_group_5_to_14, 1); + + auto parent = mio::abm::HouseholdMember(num_age_groups); + parent.set_age_weight(age_group_15_to_34, 2); + parent.set_age_weight(age_group_35_to_59, 2); + parent.set_age_weight(age_group_60_to_79, 1); + + auto other = mio::abm::HouseholdMember(num_age_groups); + other.set_age_weight(age_group_0_to_4, 1); + other.set_age_weight(age_group_5_to_14, 2); + other.set_age_weight(age_group_15_to_34, 3); + other.set_age_weight(age_group_35_to_59, 3); + other.set_age_weight(age_group_60_to_79, 2); + other.set_age_weight(age_group_80_plus, 2); + + //Two-Person Households + int two_person_two_parents = (int)(0.4 * households[1]); + int two_person_one_parent = (int)(0.4 * households[1]); + int two_person_others = households[1] - two_person_two_parents - two_person_one_parent; + + auto two_person_household_group = make_multiple_person_households(child, parent, other, 2, two_person_two_parents, + two_person_one_parent, two_person_others); + add_household_group_to_world(world, two_person_household_group); + + //Three-Person Households + int three_person_two_parents = (int)(0.4 * households[2]); + int three_person_one_parent = (int)(0.4 * households[2]); + int three_person_others = households[2] - three_person_two_parents - three_person_one_parent; + + auto three_person_household_group = make_multiple_person_households( + child, parent, other, 3, three_person_two_parents, three_person_one_parent, three_person_others); + add_household_group_to_world(world, three_person_household_group); + + //Four-Person Households + int four_person_two_parents = (int)(0.5 * households[3]); + int four_person_one_parent = (int)(0.2 * households[3]); + int four_person_others = households[3] - four_person_two_parents - four_person_one_parent; + + auto four_person_household_group = make_multiple_person_households(child, parent, other, 4, four_person_two_parents, + four_person_one_parent, four_person_others); + add_household_group_to_world(world, four_person_household_group); + + //Five-Person Households + int five_person_two_parents = (int)(0.6 * households[4]); + int five_person_one_parent = (int)(0.1 * households[4]); + int five_person_others = households[4] - five_person_two_parents - five_person_one_parent; + + auto five_person_household_group = make_multiple_person_households(child, parent, other, 5, five_person_two_parents, + five_person_one_parent, five_person_others); + add_household_group_to_world(world, five_person_household_group); + + //fill location id vector with new locations for mapping + mio::abm::LocationId id; + id.type = mio::abm::LocationType::Home; + //add LocationIds for Mapping + for (int hh = 0; hh < std::accumulate(households.begin(), households.end(), 0); ++hh) { + id.index = (int)new_index; + locations.emplace_back(id); + ++new_index; + } + + return locations; +} + +/** + * Creates abm locations from input areas + * @param[in] areas input areas consisting of a type and an id + * @param[in] inhabitants number of inhabitants per input area + * @param[in, out] world + * @param[in, out] locationIds mapping of abm location ids to corresponding input area ids + * @param[in] one_person_hh percentage of one-person households + * @param[in] two_person_hh percentage of two-person households + * @param[in] three_person_hh percentage of three-person households + * @param[in] four_person_hh percentage of four-person households + * @param[in] five_person_hh percentage of five-person households +*/ +void create_locations_from_input(std::vector>& areas, std::vector& inhabitants, + mio::abm::World& world, std::vector& locationIds, + ScalarType one_person_hh, ScalarType two_person_hh, ScalarType three_person_hh, + ScalarType four_person_hh, ScalarType five_person_hh) +{ + assert(areas.size() == inhabitants.size()); + std::string residential = "residential"; + std::vector household_distribution = {one_person_hh, two_person_hh, three_person_hh, four_person_hh, + five_person_hh}; + //school and hospital is needed for migration rules + bool has_school = false; + bool has_hospital = false; + for (size_t loc = 0; loc < areas.size(); ++loc) { + std::vector locations; + if (std::search((areas[loc].second).begin(), (areas[loc].second).end(), residential.begin(), + residential.end()) != (areas[loc].second).end()) { + //Home + locations = add_households(world, household_distribution, inhabitants[loc]); + } + else if (areas[loc].second == "mixed" || areas[loc].second == "mixed\r") { + //areas of type "mixed" are equally distributed to of location of type Home amd type Work + size_t location_type = + mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), std::vector{1., 1.}); + if (location_type) { + locations.emplace_back(add_location(world, mio::abm::LocationType::Work, 40., 100, 2000)); + } + else { + //Home + locations = add_households(world, household_distribution, inhabitants[loc]); + } + } + else { + if (areas[loc].second == "recreational" || areas[loc].second == "recreational\r") { + //Social Event + locations.emplace_back(add_location(world, mio::abm::LocationType::SocialEvent, 30., 30, 40)); + } + else if (areas[loc].second == "shopping_business" || areas[loc].second == "shopping_business\r") { + //school, hospital and icu are added first + if (!has_school) { + locations.emplace_back(add_location(world, mio::abm::LocationType::School, 40., 500, 2000)); + has_school = true; + } + else if (!has_hospital) { + locations.emplace_back(add_location(world, mio::abm::LocationType::Hospital, 5., 300, 10000)); + locations.emplace_back(add_location(world, mio::abm::LocationType::ICU, 5., 30, 1000)); + has_hospital = true; + } + else { + //the other areas of type 'shopping_business' are equally distributed to locations of type BasicsShop and type Work + size_t location_type = mio::DiscreteDistribution::get_instance()( + mio::thread_local_rng(), std::vector{1., 1.}); + if (location_type) { + locations.emplace_back(add_location(world, mio::abm::LocationType::BasicsShop, 20., 100, 1000)); + } + else { + locations.emplace_back(add_location(world, mio::abm::LocationType::Work, 40., 300, 2000)); + } + } + } + else if (areas[loc].second == "university" || areas[loc].second == "university\r") { + //area of type 'university' is converted to location of type Work + locations.emplace_back(add_location(world, mio::abm::LocationType::Work, 50., 200, 4000)); + } + else { + mio::log_error("Area input type does not match to abm location type."); + } + } + //insert locations to input area mapping + insert_locations_to_map(locationIds, areas[loc].first, locations); + } +} + +/** +* Returns parameters for LogNormalDistributiom given desired expected value and standard deviation. +* @param[in] mean desired expected value +* @param[in] std desired standard deviation +* @return pair with parameters for LogNormalDistribtuion +*/ +std::pair get_my_and_sigma(double mean, double std) +{ + double my = log(mean * mean / sqrt(mean * mean + std * std)); + double sigma = sqrt(log(1 + std * std / (mean * mean))); + return {my, sigma}; +} + +/** + * Set infection parameters + * @param[in, out] infection_params infection parameters +*/ +void set_infection_parameters(mio::abm::Parameters& infection_params) +{ + //set parameters for every agegroup + auto incubation_period_params = get_my_and_sigma(3, 1.2); + infection_params.get() = {incubation_period_params.first, + incubation_period_params.second}; + + //0-4 + auto TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(2.2, 0.5); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + + auto TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(9.2, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + + auto TimeInfectedSymptomsToSevere = get_my_and_sigma(10.5, 1.1); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + + auto TimeInfectedSymptomsToRecovered = get_my_and_sigma(7.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + + auto TimeInfectedSevereToRecovered = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + + auto TimeInfectedSevereToCritical = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + + auto TimeInfectedCriticalToRecovered = get_my_and_sigma(7.0, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + + auto TimeInfectedCriticalToDead = get_my_and_sigma(6.0, 2.0); + infection_params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + {TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + + //5-14 + TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(2.2, 0.5); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + + TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(9.2, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + + TimeInfectedSymptomsToSevere = get_my_and_sigma(10.5, 1.1); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + + TimeInfectedSymptomsToRecovered = get_my_and_sigma(7.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + + TimeInfectedSevereToRecovered = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + + TimeInfectedSevereToCritical = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + + TimeInfectedCriticalToRecovered = get_my_and_sigma(7.0, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + + TimeInfectedCriticalToDead = get_my_and_sigma(6.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = { + TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + + //15-34 + TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(2.2, 0.5); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + + TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(9.2, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + + TimeInfectedSymptomsToSevere = get_my_and_sigma(10.5, 1.1); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + + TimeInfectedSymptomsToRecovered = get_my_and_sigma(7.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + + TimeInfectedSevereToRecovered = get_my_and_sigma(6.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + + TimeInfectedSevereToCritical = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + + TimeInfectedCriticalToRecovered = get_my_and_sigma(7.0, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + + TimeInfectedCriticalToDead = get_my_and_sigma(6.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = { + TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + + //35-59 + TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(2.2, 0.5); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + + TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(9.2, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + + TimeInfectedSymptomsToSevere = get_my_and_sigma(6.0, 1.1); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + + TimeInfectedSymptomsToRecovered = get_my_and_sigma(7.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + + TimeInfectedSevereToRecovered = get_my_and_sigma(8.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + + TimeInfectedSevereToCritical = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + + TimeInfectedCriticalToRecovered = get_my_and_sigma(17.5, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + + TimeInfectedCriticalToDead = get_my_and_sigma(16.5, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = { + TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + + //60-79 + TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(2.2, 0.5); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + + TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(9.2, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + + TimeInfectedSymptomsToSevere = get_my_and_sigma(6.0, 1.1); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + + TimeInfectedSymptomsToRecovered = get_my_and_sigma(7.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + + TimeInfectedSevereToRecovered = get_my_and_sigma(10.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + + TimeInfectedSevereToCritical = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + + TimeInfectedCriticalToRecovered = get_my_and_sigma(17.5, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + + TimeInfectedCriticalToDead = get_my_and_sigma(16.5, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = { + TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + + //80+ + TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(2.2, 0.5); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + + TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(9.2, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + + TimeInfectedSymptomsToSevere = get_my_and_sigma(6.0, 1.1); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + + TimeInfectedSymptomsToRecovered = get_my_and_sigma(7.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + + TimeInfectedSevereToRecovered = get_my_and_sigma(15.0, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + + TimeInfectedSevereToCritical = get_my_and_sigma(5.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + + TimeInfectedCriticalToRecovered = get_my_and_sigma(12.5, 3.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + + TimeInfectedCriticalToDead = get_my_and_sigma(11.0, 2.0); + infection_params + .get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = { + TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + + // Set percentage parameters + infection_params.get() = 0.79; + infection_params.get() = 0.08; + infection_params.get() = 0.18; + infection_params.get() = 0.1; +} + +/** + * Get a person's infection state according to a given distribution + * @param[in] exposed percentage of infection state 'exposed' + * @param[in] infected_no_symptoms percentage of infection state 'infected no symptoms' + * @param[in] infected_symptoms percentage of of infection state 'infected symptoms' + * @param[in] infected_severe percentage of infection state 'infected severe' + * @param[in] infected_critical percentage of infection state 'infected critical' + * @param[in] recovered_infected_no_symptoms percentage of infection state 'recovered of state infected no symptoms' + * @param[in] recovered_infected percentage of infection state 'recovered of other infection state' + * @return state drawn infection state +*/ +mio::abm::InfectionState get_infection_state(mio::abm::Person::RandomNumberGenerator& rng, ScalarType exposed, + ScalarType infected_no_symptoms, ScalarType infected_symptoms, + ScalarType infected_severe, ScalarType infected_critical, + ScalarType recovered) +{ + ScalarType susceptible = + 1 - exposed - infected_no_symptoms - infected_symptoms - infected_severe - infected_critical - recovered; + std::vector weights = { + susceptible, exposed, infected_no_symptoms, infected_symptoms, infected_severe, infected_critical, recovered}; + if (weights.size() != (size_t)mio::abm::InfectionState::Count - 1) { + mio::log_error("Initialization in ABM wrong, please correct vector length."); + } + size_t state = mio::DiscreteDistribution::get_instance()(rng, weights); + return (mio::abm::InfectionState)state; +} + +/** + * Assign an infection state to each person. + * @param[in, out] world + * @param[in] t0 starting time point + * @param[in] exposed percentage of infection state 'exposed' + * @param[in] infected_no_symptoms percentage of infection state 'infected no symptoms' + * @param[in] infected_symptoms percentage of of infection state 'infected symptoms' + * @param[in] infected_severe percentage of infection state 'infected severe' + * @param[in] infected_critical percentage of infection state 'infected critical' + * @param[in] recovered percentage of infection state 'recovered' +*/ +void assign_infection_states(mio::abm::World& world, mio::abm::TimePoint t0, ScalarType exposed, + ScalarType infected_no_symptoms, ScalarType infected_symptoms, ScalarType infected_severe, + ScalarType infected_critical, ScalarType recovered) +{ + auto persons = world.get_persons(); + for (auto& person : persons) { + auto rng = mio::abm::Person::RandomNumberGenerator(world.get_rng(), person); + auto infection_state = get_infection_state(rng, exposed, infected_no_symptoms, infected_symptoms, + infected_severe, infected_critical, recovered); + if (infection_state != mio::abm::InfectionState::Susceptible) { + person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), + world.parameters, t0, infection_state, + person.get_latest_protection(), false), + t0); + } + } +} + +std::vector find_all_locations_of_type(mio::abm::World& world, mio::abm::LocationType type) +{ + std::vector locations; + for (auto& loc : world.get_locations()) { + if (loc.get_type() == type) { + locations.push_back(mio::abm::LocationId{loc.get_index(), type}); + } + } + return locations; +} + +/** + * Assign locations to persons. + * @param[in, out] world +*/ +void assign_locations(mio::abm::World& world) +{ + //get locations from world + //schools + std::vector schools = find_all_locations_of_type(world, mio::abm::LocationType::School); + std::vector school_weights(schools.size(), 1); + //hispitals + std::vector hospitals = find_all_locations_of_type(world, mio::abm::LocationType::Hospital); + std::vector hospital_weights(hospitals.size(), 1); + //icu + std::vector icus = find_all_locations_of_type(world, mio::abm::LocationType::ICU); + std::vector icu_weights(icus.size(), 1); + //workplaces + std::vector workplaces = find_all_locations_of_type(world, mio::abm::LocationType::Work); + std::vector workplaces_weights(workplaces.size(), 1); + //shops + std::vector basic_shops = + find_all_locations_of_type(world, mio::abm::LocationType::BasicsShop); + std::vector basic_shops_weights(basic_shops.size(), 1); + //social events + std::vector social_events = + find_all_locations_of_type(world, mio::abm::LocationType::SocialEvent); + std::vector social_event_weights(social_events.size(), 1); + + auto persons = world.get_persons(); + for (auto& person : persons) { + //assign shop + size_t shop = mio::DiscreteDistribution::get_instance()(world.get_rng(), basic_shops_weights); + person.set_assigned_location(basic_shops[shop]); + //assign hospital + size_t hospital = mio::DiscreteDistribution::get_instance()(world.get_rng(), hospital_weights); + person.set_assigned_location(hospitals[hospital]); + //assign icu + size_t icu = mio::DiscreteDistribution::get_instance()(world.get_rng(), icu_weights); + person.set_assigned_location(icus[icu]); + //assign event + size_t event = mio::DiscreteDistribution::get_instance()(world.get_rng(), social_event_weights); + person.set_assigned_location(social_events[event]); + //assign work and school + if (person.get_age() == age_group_5_to_14) { + size_t school = mio::DiscreteDistribution::get_instance()(world.get_rng(), school_weights); + person.set_assigned_location(schools[school]); + } + if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { + size_t work = mio::DiscreteDistribution::get_instance()(world.get_rng(), workplaces_weights); + person.set_assigned_location(workplaces[work]); + } + } +} + +/** + * Create sampled simulation with start time t0. + * @param[in] t0 start time of the simulation + * @param[in] input_dir text file with the locations and the inhabitants per residential area + * @param[in, out] locationIds mapping of input area to abm locations + * @return sim abm simulation +*/ +mio::abm::Simulation create_sampled_simulation(const mio::abm::TimePoint& t0, const fs::path& input_dir, + std::vector& locationIds) +{ + std::vector> areas; + std::vector inhabitants; + mio::unused(locationIds); + mio::unused(input_dir); + // Read input file containing the locations and the number of inhabitants per location + read_txt(areas, inhabitants, input_dir); + + //Assumed percentage for 1-Person, 2-Person, 3-Person, 4-Person and 5-Persons households + ScalarType one_person_hh_pct = 0.37, two_person_hh_pct = 0.33, three_person_hh_pct = 0.15, four_person_hh_pct = 0.1, + five_person_hh_pct = 0.05; + // Assumed percentage of infection state at the beginning of the simulation. + ScalarType exposed_pct = 0.005, infected_no_symptoms_pct = 0.001, infected_symptoms_pct = 0.001, + infected_severe_pct = 0.0001, infected_critical_pct = 0.0, recovered_pct = 0.0; + //Set global infection parameters + auto world = mio::abm::World(num_age_groups); + set_infection_parameters(world.parameters); + + //Transform the input location to correspondin abm location + create_locations_from_input(areas, inhabitants, world, locationIds, one_person_hh_pct, two_person_hh_pct, + three_person_hh_pct, four_person_hh_pct, five_person_hh_pct); + + //Assign an infection state to every person + assign_infection_states(world, t0, exposed_pct, infected_no_symptoms_pct, infected_symptoms_pct, + infected_severe_pct, infected_critical_pct, recovered_pct); + // //Assign the locations to persons + assign_locations(world); + + auto sim = mio::abm::Simulation(t0, std::move(world)); + return sim; +} + +//Loggers used for output object + +//time point logger +struct LogTimePoint : mio::LogAlways { + using Type = double; + static Type log(const mio::abm::Simulation& sim) + { + return sim.get_time().hours(); + } +}; + +//LocationId logger +struct LogLocationIds : mio::LogOnce { + using Type = std::vector>; + static Type log(const mio::abm::Simulation& sim) + { + std::vector> location_ids{}; + for (auto&& location : sim.get_world().get_locations()) { + location_ids.push_back(std::make_tuple(location.get_type(), location.get_index())); + } + return location_ids; + } +}; + +//agent logger +struct LogPersonsPerLocationAndInfectionTime : mio::LogAlways { + using Type = std::vector>; + static Type log(const mio::abm::Simulation& sim) + { + std::vector> + location_ids_person{}; + for (auto&& person : sim.get_world().get_persons()) { + location_ids_person.push_back(std::make_tuple(person.get_location().get_id(), person.get_person_id(), + person.get_time_since_transmission(), + person.get_infection_state(sim.get_time()))); + } + return location_ids_person; + } +}; + +void write_results_to_file(std::string path, + mio::History::WriteWrapper::Data& logg) +{ + auto location_ids = std::get<1>(logg); + auto agents = std::get<2>(logg); + auto time_points = std::get<0>(logg); + + std::string input; + std::ofstream myfile(path); + for (size_t loc_id_index = 0; loc_id_index < location_ids[0].size(); ++loc_id_index) { + input = convert_loc_id_to_string(location_ids[0][loc_id_index]) + " " + std::to_string(time_points.size()); + for (size_t t = 0; t < time_points.size(); ++t) { + auto a_per_loc = get_agents_per_location(location_ids[0][loc_id_index], agents[t]); + input += " " + std::to_string(time_points[t]) + " " + std::to_string(a_per_loc.size()); + for (auto& agent : a_per_loc) { + double time_since_transmission; + if (std::get<1>(agent) > mio::abm::TimeSpan(std::numeric_limits::max() / 4)) { + time_since_transmission = -1; + } + else { + time_since_transmission = std::get<1>(agent).hours(); + } + input += " " + std::to_string(std::get<0>(agent)) + " " + std::to_string(time_since_transmission); + } + } + myfile << input << "\n"; + } + myfile.close(); +} + +void write_location_mapping_to_file(std::string path, std::vector& LocationIds) +{ + std::string input; + std::ofstream myfile(path); + for (auto& id : LocationIds) { + input = id.inputId + " "; + for (auto& model_id : id.modelId) { + input += model_id + " "; + } + myfile << input << "\n"; + } + + myfile.close(); +} + +std::map> initialize_model(mio::abm::Model& model, std::string person_file) +{ + + std::map> loc_area_mapping; + std::map locations; + + const fs::path p = filename; + if (!fs::exists(p)) { + mio::log_error("Cannot read in data. File does not exist."); + } + // File pointer + std::fstream fin; + + // Open an existing file + fin.open(filename, std::ios::in); + std::vector row; + std::vector row_string; + std::string line; + + // Read the Titles from the Data file + std::getline(fin, line); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + std::vector titles; + boost::split(titles, line, boost::is_any_of(",")); + uint32_t count_of_titles = 0; + std::map index = {}; + for (auto const& title : titles) { + index.insert({title, count_of_titles}); + row_string.push_back(title); + count_of_titles++; + } + + while (std::getline(fin, line)) { + row.clear(); + + // read columns in this row + split_line(line, &row); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + + uint32_t age = row[index["age"]]; + + int home_id = row[index["home_id"]]; + int home_zone = row[index["home_zone"]]; + + auto iter_home = locations.find(home_id); + if (iter_home == locations.end()) { + home = model.add_location(mio::abm::LocationType::Home); + locations.insert({home_id, home}); + std::string loc = "0" + std::to_string(mio::abm::LocationType::Home) + std::to_string(home.get()); + auto zone_iter = loc_area_mapping.find(home_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({home_zone, {loc}}); + } + else { + loc_area_mapping[home_zone].push_back(loc); + } + } + else { + home = locations[home_id]; + } + auto pid = model.add_person(home, determine_age_group(age)); + auto& person = model.get_person(pid); + person.set_assigned_location(mio::abm::LocationType::Home, home); + + int shop_id = row[index["shop_id"]]; + int shop_zone = row[index["shop_zone"]]; + + auto iter_shop = locations.find(shop_id); + if (iter_shop == locations.end()) { + shop = model.add_location(mio::abm::LocationType::BasicsShop); + if (shop_id = !-1) { + locations.insert({shop_id, shop}); + } + std::string loc = "0" + std::to_string(mio::abm::LocationType::BasicsShop) + std::to_string(shop.get()); + auto zone_iter = loc_area_mapping.find(shop_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({shop_zone, {loc}}); + } + else { + loc_area_mapping[shop_zone].push_back(loc); + } + } + else { + shop = locations[shop_id]; + } + person.set_assigned_location(mio::abm::LocationType::BasicsShop, shop); + + int event_id = row[index["event_id"]]; + int event_zone = row[index["event_zone"]]; + + auto iter_event = locations.find(event_id); + if (iter_event == locations.end()) { + event = model.add_location(mio::abm::LocationType::SocialEvent); + if (event_id != -1) { + locations.insert({event_id, event}); + } + std::string loc = "0" + std::to_string(mio::abm::LocationType::SocialEvent) + std::to_string(event.get()); + auto zone_iter = loc_area_mapping.find(event_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({event_zone, {loc}}); + } + else { + loc_area_mapping[event_zone].push_back(loc); + } + } + else { + event = locations[event_id]; + } + person.set_assigned_location(mio::abm::LocationType::SocialEvent, event); + + if (person.get_age() == mio::AgeGroup(1)) { + int school_id = row[index["school_id"]]; + int school_zone = row[index["school_zone"]]; + + auto iter_school = locations.find(school_id); + if (iter_school == locations.end()) { + school = model.add_location(mio::abm::LocationType::School); + if (school_id != -1) { + locations.insert({school_id, school}); + } + std::string loc = "0" + std::to_string(mio::abm::LocationType::School) + std::to_string(school.get()); + auto zone_iter = loc_area_mapping.find(school_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({school_zone, {loc}}); + } + else { + loc_area_mapping[school_zone].push_back(loc); + } + } + else { + school = locations[school_id]; + } + person.set_assigned_location(mio::abm::LocationType::School, school); + } + + if (person.get_age() == mio::AgeGroup(2) || person.get_age() == mio::AgeGroup(3)) { + int work_id = row[index["work_id"]]; + int work_zone = row[index["work_zone"]]; + + auto iter_work = locations.find(work_id); + if (iter_work == locations.end()) { + work = model.add_location(mio::abm::LocationType::Work); + if (work_id != -1) { + locations.insert({work_id, work}); + } + std::string loc = "0" + std::to_string(mio::abm::LocationType::Work) + std::to_string(work.get()); + auto zone_iter = loc_area_mapping.find(work_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({work_zone, {loc}}); + } + else { + loc_area_mapping[work_zone].push_back(loc); + } + } + else { + work = locations[work_id]; + } + person.set_assigned_location(mio::abm::LocationType::Work, work); + } + } + + return loc_area_mapping; +} + +mio::IOResult run(const fs::path& input_dir) +{ + mio::set_log_level(mio::LogLevel::warn); + // auto t0 = mio::abm::TimePoint(0); // Start time per simulation + // auto tmax = mio::abm::TimePoint(0) + mio::abm::days(14); // End time per simulation + + auto model = mio::abm::Model(size_t(6)); + auto dict = initialize_model(model, "../../pycode/examples/simulation/ABM Demonstrator/input/persons.csv"); + + mio::unused(model); + mio::unused(dict); + // //mapping of input areas to abm locations + // std::vector LocationIds; + // //create sampled simulation + // auto sim = create_sampled_simulation(t0, input_dir, LocationIds); + + // //output object + // mio::History history; + + // //advance until tmax + // sim.advance(tmax, history); + + // //output + // auto logg = history.get_log(); + // write_results_to_file("output_abm_demonstrator.txt", logg); + // write_location_mapping_to_file("location_mapping.txt", LocationIds); + + // std::cout << "# t S E C I I_s I_c R_C R_I D\n"; + // for (auto i = 0; i < sim.get_result().get_num_time_points(); ++i) { + // std::cout << sim.get_result().get_time(i) << " "; + // auto v = sim.get_result().get_value(i); + // for (auto j = 0; j < v.size(); ++j) { + // std::cout << v[j] << " "; + // if (j < v.size() - 1) { + // std::cout << " "; + // } + // } + // if (i < sim.get_result().get_num_time_points() - 1) { + // std::cout << "\n"; + // } + // } + + return mio::success(); +} + +template +void print(T& data) +{ + for (auto item : data) { + std::cout << item << " "; + } + std::cout << std::endl; +} + +int main() +{ + const fs::path input_dir = + "C:/Users/bick_ju/Documents/INSIDe/Demonstrator/INSIDeDemonstrator/INSIDe_Demonstrator_AreaList.csv"; + auto result = run(input_dir); + + return 0; +} diff --git a/cpp/simulations/abm_braunschweig.cpp b/cpp/simulations/abm_braunschweig.cpp index a8be3bb5f9..16f2ea6bc7 100644 --- a/cpp/simulations/abm_braunschweig.cpp +++ b/cpp/simulations/abm_braunschweig.cpp @@ -400,90 +400,116 @@ void set_parameters(mio::abm::Parameters params) params.set({{mio::abm::VirusVariant::Count, mio::AgeGroup(num_age_groups)}, 4.}); //0-4 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.276; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.092; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.142; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.276; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.092; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.142; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; //5-14 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.276; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.276; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.092; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.142; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.142; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.186; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; //15-34 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.139; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.003; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.157; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.013; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.139; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.003; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.157; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.013; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.126; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; //35-59 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.136; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.009; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.113; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.05; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.136; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.009; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.113; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.05; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; //60-79 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.123; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.024; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.083; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.123; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.024; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.083; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; //80+ - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.315; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.315; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.079; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.115; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.033; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.055; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.115; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.033; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.055; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; // Set each parameter for vaccinated people including personal infection and vaccine protection levels. // Summary: https://doi.org/10.1038/s41577-021-00550-x, //0-4 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.161; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.132; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.0; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.161; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.132; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.186; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.015; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.001; + // Protection of reinfection is the same for all age-groups, based on: // https://doi.org/10.1016/S0140-6736(22)02465-5, https://doi.org/10.1038/s41591-021-01377-8 params.get()[{mio::abm::ProtectionType::NaturalInfection, age_group_0_to_4, @@ -534,16 +560,19 @@ void set_parameters(mio::abm::Parameters params) {{0, 0.5}, {30, 0.88}, {60, 0.91}, {90, 0.98}, {120, 0.94}, {150, 0.88}, {450, 0.5}}}; //5-14 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.161; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.161; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.132; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.186; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.143; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.0; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.186; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.015; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = + 0.143; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 0.001; // Protection of reinfection is the same for all age-groups, based on: // https://doi.org/10.1016/S0140-6736(22)02465-5, https://doi.org/10.1038/s41591-021-01377-8 params.get()[{mio::abm::ProtectionType::NaturalInfection, age_group_5_to_14, @@ -593,17 +622,21 @@ void set_parameters(mio::abm::Parameters params) {{0, 0.5}, {30, 0.88}, {60, 0.91}, {90, 0.98}, {120, 0.94}, {150, 0.88}, {450, 0.5}}}; //15-34 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.126; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.142; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.001; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.157; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 0.013; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.142; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.001; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.157; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.013; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.0; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 0.021; // Set up personal infection and vaccine protection levels, based on: https://doi.org/10.1038/s41577-021-00550-x, https://doi.org/10.1038/s41591-021-01377-8 params.get()[{mio::abm::ProtectionType::NaturalInfection, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}] = { @@ -651,17 +684,20 @@ void set_parameters(mio::abm::Parameters params) {{0, 0.5}, {30, 0.88}, {60, 0.91}, {90, 0.98}, {120, 0.94}, {150, 0.88}, {180, 0.90}, {450, 0.5}}}; //35-59 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.141; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.003; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.113; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.05; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.0; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.141; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.003; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.113; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.02; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = + 0.05; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = 0.008; // Protection of reinfection is the same for all age-groups, based on: // https://doi.org/10.1016/S0140-6736(22)02465-5, https://doi.org/10.1038/s41591-021-01377-8 params.get()[{mio::abm::ProtectionType::NaturalInfection, age_group_35_to_59, @@ -709,17 +745,21 @@ void set_parameters(mio::abm::Parameters params) mio::TimeSeriesFunctorType::LinearInterpolation, {{0, 0.5}, {30, 0.88}, {60, 0.91}, {90, 0.98}, {120, 0.94}, {150, 0.88}, {180, 0.90}, {450, 0.5}}}; //60-79 - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.136; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.009; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.083; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.0; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.136; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.009; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.083; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.023; // Protection of reinfection is the same for all age-groups, based on: // https://doi.org/10.1016/S0140-6736(22)02465-5, https://doi.org/10.1038/s41591-021-01377-8 params.get()[{mio::abm::ProtectionType::NaturalInfection, age_group_60_to_79, @@ -768,16 +808,19 @@ void set_parameters(mio::abm::Parameters params) {{0, 0.5}, {30, 0.91}, {60, 0.86}, {90, 0.91}, {120, 0.94}, {150, 0.95}, {180, 0.90}, {450, 0.5}}}; //80+ - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.179; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.179; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.126; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.133; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.012; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.055; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.035; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.0; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.133; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.012; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.055; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.036; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = + 0.035; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_80_plus}] = 0.052; // Protection of reinfection is the same for all age-groups, based on: // https://doi.org/10.1016/S0140-6736(22)02465-5, https://doi.org/10.1038/s41591-021-01377-8 params.get()[{mio::abm::ProtectionType::NaturalInfection, age_group_80_plus, diff --git a/cpp/tests/abm_helpers.cpp b/cpp/tests/abm_helpers.cpp index 8ab1e79753..5e604df25d 100644 --- a/cpp/tests/abm_helpers.cpp +++ b/cpp/tests/abm_helpers.cpp @@ -62,7 +62,7 @@ void interact_testing(mio::abm::PersonalRandomNumberGenerator& personal_rng, mio }); // caclculate current exposures for (const mio::abm::Person& p : local_population) { - add_exposure_contribution(local_air_exposure, local_contact_exposure, p, location, t, dt); + add_exposure_contribution(local_air_exposure, local_contact_exposure, p, location, t, dt, global_parameters); } // run interaction mio::abm::interact(personal_rng, person, location, local_air_exposure, local_contact_exposure, t, dt, diff --git a/cpp/tests/test_abm_infection.cpp b/cpp/tests/test_abm_infection.cpp index 56e3f873fb..21f0de7020 100644 --- a/cpp/tests/test_abm_infection.cpp +++ b/cpp/tests/test_abm_infection.cpp @@ -19,8 +19,10 @@ */ #include "abm/location_type.h" +#include "abm/parameters.h" #include "abm/person.h" #include "abm_helpers.h" +#include "memilio/utils/random_number_generator.h" #include "random_number_test.h" using TestInfection = RandomNumberTest; @@ -40,6 +42,11 @@ TEST_F(TestInfection, init) auto counter = mio::Counter(0); auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); + // Mock recovery transition + ScopedMockDistribution>>> mock_lognorm_dist; + EXPECT_CALL(mock_lognorm_dist.get_mock(), invoke) + .Times(testing::AtLeast(1)) + .WillRepeatedly(testing::Return(1)); // Time in every state is one day ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) .Times(testing::AtLeast(7)) @@ -47,10 +54,6 @@ TEST_F(TestInfection, init) .WillOnce(testing::Return(0.6)) // Transition to Recovered .WillOnce(testing::Return(params.get()[{virus_variant_test, age_group_test}] .viral_load_peak.params.a())) // Viral load draws - .WillOnce(testing::Return(params.get()[{virus_variant_test, age_group_test}] - .viral_load_incline.params.a())) - .WillOnce(testing::Return(params.get()[{virus_variant_test, age_group_test}] - .viral_load_decline.params.a())) .WillOnce(testing::Return(params.get()[{virus_variant_test, age_group_test}] .infectivity_alpha.params.a())) // Infectivity draws .WillOnce(testing::Return(params.get()[{virus_variant_test, age_group_test}] @@ -82,7 +85,7 @@ TEST_F(TestInfection, init) EXPECT_EQ(infection.get_infection_state(mio::abm::TimePoint(0) + mio::abm::days(1)), mio::abm::InfectionState::InfectedNoSymptoms); // Test infectivity at a specific time point - EXPECT_NEAR(infection.get_infectivity(mio::abm::TimePoint(0) + mio::abm::days(3)), 0.2689414213699951, 1e-14); + EXPECT_NEAR(infection.get_infectivity(mio::abm::TimePoint(0) + mio::abm::days(3)), 0.078952042141882353, 1e-14); // Test infection with previous exposure and recovery state transition. params.get()[{mio::abm::ProtectionType::GenericVaccine, age_group_test, @@ -103,7 +106,7 @@ TEST_F(TestInfection, init) mio::abm::InfectionState::Recovered); // Test infectivity at a specific time point EXPECT_NEAR(infection_w_previous_exp.get_infectivity(mio::abm::TimePoint(0) + mio::abm::days(3)), - 0.45760205922564895, 1e-14); + 0.74977241472747835, 1e-14); } /** @@ -113,13 +116,13 @@ TEST_F(TestInfection, getInfectionState) { auto counter = mio::Counter(0); auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); - auto params = mio::abm::Parameters(num_age_groups); - auto t = mio::abm::TimePoint(0); + auto params = mio::abm::Parameters(num_age_groups); + auto t = mio::abm::TimePoint(0); // Initialize infection in Exposed state auto infection = mio::abm::Infection(prng, mio::abm::VirusVariant::Wildtype, age_group_15_to_34, params, t, mio::abm::InfectionState::Exposed, - {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); + {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); // Test infection state at different time points EXPECT_EQ(infection.get_infection_state(t), mio::abm::InfectionState::Exposed); @@ -133,18 +136,21 @@ TEST_F(TestInfection, drawInfectionCourseForward) { auto counter = mio::Counter(0); auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); - auto params = mio::abm::Parameters(num_age_groups); - auto t = mio::abm::TimePoint(0); + auto params = mio::abm::Parameters(num_age_groups); + auto t = mio::abm::TimePoint(0); // Mock recovery transition - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 1; + ScopedMockDistribution>>> mock_lognorm_dist; + EXPECT_CALL(mock_lognorm_dist.get_mock(), invoke) + .Times(testing::AtLeast(1)) + .WillRepeatedly(testing::Return(1)); // Time in every state is one day ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) .Times(testing::AtLeast(1)) .WillRepeatedly(testing::Return(0.8)); // Recovered auto infection = mio::abm::Infection(prng, mio::abm::VirusVariant::Wildtype, age_group_15_to_34, params, t, mio::abm::InfectionState::InfectedCritical, - {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); + {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); // Test state transitions from Critical to Recovered EXPECT_EQ(infection.get_infection_state(t), mio::abm::InfectionState::InfectedCritical); EXPECT_EQ(infection.get_infection_state(t + mio::abm::days(1)), mio::abm::InfectionState::Recovered); @@ -160,29 +166,37 @@ TEST_F(TestInfection, drawInfectionCourseBackward) auto t = mio::abm::TimePoint(1); auto dt = mio::abm::days(1); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.0; - // Time to go from all infected states to recover is 1 day (dt). - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 1; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 1; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 1; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 1; + ScopedMockDistribution>>> mock_lognorm_dist; + EXPECT_CALL(mock_lognorm_dist.get_mock(), invoke) + .Times(testing::AtLeast(1)) + .WillRepeatedly(testing::Return(1)); // Time in every state is one day ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) .Times(testing::AtLeast(14)) .WillOnce(testing::Return(0.1)) // Transition to InfectedNoSymptoms - .WillOnce(testing::Return(0.1)) - .WillOnce(testing::Return(0.1)) - .WillOnce(testing::Return(0.1)) - .WillOnce(testing::Return(0.1)) - .WillOnce(testing::Return(0.1)) - .WillOnce(testing::Return(0.3)) // Transition to InfectedSymptoms - .WillOnce(testing::Return(0.3)) - .WillOnce(testing::Return(0.3)) - .WillOnce(testing::Return(0.3)) - .WillOnce(testing::Return(0.3)) - .WillOnce(testing::Return(0.3)) - .WillOnce(testing::Return(0.6)) // Transition to InfectedSevere + .WillOnce(testing::Return( + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] + .viral_load_peak.params.a())) // Viral load draws + .WillOnce(testing::Return( + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] + .infectivity_alpha.params.a())) // Infectivity draws + .WillOnce(testing::Return( + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] + .infectivity_beta.params.a())) + .WillOnce(testing::Return(0.6)) // Transition to InfectedSymptoms + .WillOnce(testing::Return( + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] + .viral_load_peak.params.a())) // Viral load draws + .WillOnce(testing::Return( + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] + .infectivity_alpha.params.a())) // Infectivity draws + .WillOnce(testing::Return( + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] + .infectivity_beta.params.a())) + .WillOnce(testing::Return(0.8)) // Transition to InfectedSevere .WillRepeatedly(testing::Return(0.9)); // Transition to InfectedCritical auto infection1 = mio::abm::Infection(prng, mio::abm::VirusVariant::Wildtype, age_group_60_to_79, params, @@ -249,42 +263,42 @@ TEST_F(TestInfection, getPersonalProtectiveFactor) // Test Parameter InfectionProtectionFactor and get_protection_factor() t = mio::abm::TimePoint(0) + mio::abm::days(2); auto infection_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(infection_protection_factor, 0.91, eps); EXPECT_NEAR(person.get_protection_factor(t, mio::abm::VirusVariant::Wildtype, params), 0.91, eps); t = mio::abm::TimePoint(0) + mio::abm::days(15); infection_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(infection_protection_factor, 0.8635, eps); EXPECT_NEAR(person.get_protection_factor(t, mio::abm::VirusVariant::Wildtype, params), 0.8635, eps); t = mio::abm::TimePoint(0) + mio::abm::days(40); infection_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(infection_protection_factor, 0.81, eps); EXPECT_NEAR(person.get_protection_factor(t, mio::abm::VirusVariant::Wildtype, params), 0.81, eps); // Test Parameter SeverityProtectionFactor t = mio::abm::TimePoint(0) + mio::abm::days(2); auto severity_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(severity_protection_factor, 0.91, eps); t = mio::abm::TimePoint(0) + mio::abm::days(15); severity_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(severity_protection_factor, 0.8635, eps); t = mio::abm::TimePoint(0) + mio::abm::days(40); severity_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(severity_protection_factor, 0.81, eps); // Test Parameter HighViralLoadProtectionFactor diff --git a/cpp/tests/test_abm_location.cpp b/cpp/tests/test_abm_location.cpp index c92f199893..1659c04a3f 100644 --- a/cpp/tests/test_abm_location.cpp +++ b/cpp/tests/test_abm_location.cpp @@ -48,14 +48,10 @@ TEST_F(TestLocation, getId) EXPECT_EQ(location.get_id(), mio::abm::LocationId(0)); } -/** - * @brief Test that the computation of space per person relative to capacity works correctly. - */ TEST_F(TestLocation, computeSpacePerPersonRelative) { using testing::Return; - // Create a location of type Home with 3 cells. mio::abm::Location home(mio::abm::LocationType::Home, 0, 6, 3); home.set_capacity(4, 264, 0); // Capacity for Cell 1 home.set_capacity(2, 132, 1); // Capacity for Cell 2 diff --git a/cpp/tests/test_abm_lockdown_rules.cpp b/cpp/tests/test_abm_lockdown_rules.cpp index 1e981c0973..4a84dd2bb6 100644 --- a/cpp/tests/test_abm_lockdown_rules.cpp +++ b/cpp/tests/test_abm_lockdown_rules.cpp @@ -45,6 +45,10 @@ TEST_F(TestLockdownRules, school_closure) .WillOnce(testing::Return(0.4)) .WillOnce(testing::Return(0.4)) .WillOnce(testing::Return(0.4)) + .WillOnce(testing::Return(0.4)) + .WillOnce(testing::Return(0.4)) + .WillOnce(testing::Return(0.2)) + .WillOnce(testing::Return(0.2)) .WillOnce(testing::Return(0.2)) .WillOnce(testing::Return(0.2)) .WillOnce(testing::Return(0.2)) @@ -98,6 +102,8 @@ TEST_F(TestLockdownRules, school_opening) .WillOnce(testing::Return(0.6)) .WillOnce(testing::Return(0.6)) .WillOnce(testing::Return(0.6)) + .WillOnce(testing::Return(0.6)) + .WillOnce(testing::Return(0.6)) .WillRepeatedly(testing::Return(1.0)); // Set up one person with assigned locations (home and school) @@ -114,7 +120,7 @@ TEST_F(TestLockdownRules, school_opening) params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; - // Apply school closure, then reopening + // Apply school closure, then reopening mio::abm::set_school_closure(t_closing, 1., params); mio::abm::set_school_closure(t_opening, 0., params); diff --git a/cpp/tests/test_abm_masks.cpp b/cpp/tests/test_abm_masks.cpp index 4121229071..b64a8bee02 100644 --- a/cpp/tests/test_abm_masks.cpp +++ b/cpp/tests/test_abm_masks.cpp @@ -73,7 +73,10 @@ TEST_F(TestMasks, maskProtection) mio::abm::Parameters params(num_age_groups); // Set incubation period to two days so that newly infected person is still exposed - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_5_to_14}] = 2.; + ScopedMockDistribution>>> mock_lognorm_dist; + EXPECT_CALL(mock_lognorm_dist.get_mock(), invoke) + .Times(testing::AtLeast(2)) + .WillRepeatedly(testing::Return(2)); // Time in every state is two days // Setup location and persons for the test auto t = mio::abm::TimePoint(0); diff --git a/cpp/tests/test_abm_mobility_rules.cpp b/cpp/tests/test_abm_mobility_rules.cpp index 8ccb76bf6a..fd1a8f9dfa 100644 --- a/cpp/tests/test_abm_mobility_rules.cpp +++ b/cpp/tests/test_abm_mobility_rules.cpp @@ -81,6 +81,8 @@ TEST_F(TestMobilityRules, student_goes_to_school) .WillOnce(testing::Return(0.6)) .WillOnce(testing::Return(0.6)) .WillOnce(testing::Return(0.6)) + .WillOnce(testing::Return(0.6)) + .WillOnce(testing::Return(0.6)) .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); @@ -125,6 +127,10 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times) .WillOnce(testing::Return(0.0)) .WillOnce(testing::Return(0.0)) .WillOnce(testing::Return(0.0)) + .WillOnce(testing::Return(0.0)) + .WillOnce(testing::Return(0.0)) + .WillOnce(testing::Return(0.8)) + .WillOnce(testing::Return(0.8)) .WillOnce(testing::Return(0.8)) .WillOnce(testing::Return(0.8)) .WillOnce(testing::Return(0.8)) @@ -190,6 +196,10 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times_with_smaller_ .WillOnce(testing::Return(0.0)) .WillOnce(testing::Return(0.0)) .WillOnce(testing::Return(0.0)) + .WillOnce(testing::Return(0.0)) + .WillOnce(testing::Return(0.0)) + .WillOnce(testing::Return(0.9)) + .WillOnce(testing::Return(0.9)) .WillOnce(testing::Return(0.9)) .WillOnce(testing::Return(0.9)) .WillOnce(testing::Return(0.9)) @@ -242,12 +252,16 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times_with_smaller_ */ TEST_F(TestMobilityRules, school_return) { + // Mock the uniform distribution to control the randomness of the student's return-from-school times. + ScopedMockDistribution>>> mock_uniform_dist; + EXPECT_CALL(mock_uniform_dist.get_mock(), invoke).Times(testing::AtLeast(6)).WillRepeatedly(testing::Return(1.0)); + mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); auto p_child = mio::abm::Person(this->get_rng(), school.get_type(), school.get_id(), age_group_5_to_14); auto rng_child = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child); // Simulate a time point after school hours - auto t = mio::abm::TimePoint(0) + mio::abm::hours(15); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); auto dt = mio::abm::hours(1); // Ensure that the child returns home after school is over @@ -369,6 +383,10 @@ TEST_F(TestMobilityRules, workers_go_to_work_in_different_times) .WillOnce(testing::Return(0.)) .WillOnce(testing::Return(0.)) .WillOnce(testing::Return(0.)) + .WillOnce(testing::Return(0.)) + .WillOnce(testing::Return(0.)) + .WillOnce(testing::Return(0.9)) + .WillOnce(testing::Return(0.9)) .WillOnce(testing::Return(0.9)) .WillOnce(testing::Return(0.9)) .WillOnce(testing::Return(0.9)) @@ -423,13 +441,16 @@ TEST_F(TestMobilityRules, workers_go_to_work_in_different_times) */ TEST_F(TestMobilityRules, work_return) { + // Mock the uniform distribution to control the randomness of the worker's return-from-work times. + ScopedMockDistribution>>> mock_uniform_dist; + EXPECT_CALL(mock_uniform_dist.get_mock(), invoke).Times(testing::AtLeast(6)).WillRepeatedly(testing::Return(1.0)); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); // Set up a random number generator and a worker who is currently at work auto p_adult = mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), age_group_35_to_59); auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); - // Set the time to 5 PM (17:00) when the worker should return home - auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); - auto dt = mio::abm::hours(1); + // Set the time to 6 PM (18:00) when the worker should return home + auto t = mio::abm::TimePoint(0) + mio::abm::hours(18); + auto dt = mio::abm::hours(1); // Test that the worker, who is currently at work, goes home after 5 PM EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); @@ -477,8 +498,8 @@ TEST_F(TestMobilityRules, quarantine) TEST_F(TestMobilityRules, hospital) { mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); auto p_inf = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); auto rng_inf = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf); @@ -621,8 +642,8 @@ TEST_F(TestMobilityRules, event_return) TEST_F(TestMobilityRules, icu) { mio::abm::Location hospital(mio::abm::LocationType::Hospital, 0, num_age_groups); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); auto p_hosp = make_test_person(this->get_rng(), hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedCritical, t); auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_hosp); @@ -646,8 +667,8 @@ TEST_F(TestMobilityRules, icu) TEST_F(TestMobilityRules, recover) { mio::abm::Location hospital(mio::abm::LocationType::Hospital, 0); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); auto p_rec = make_test_person(this->get_rng(), hospital, age_group_60_to_79, mio::abm::InfectionState::Recovered, t); auto rng_rec = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_rec); diff --git a/cpp/tests/test_abm_model.cpp b/cpp/tests/test_abm_model.cpp index d0b38aaa3f..047ca7106e 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -17,6 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/parameters.h" #include "abm/person.h" #include "abm/model.h" #include "abm_helpers.h" @@ -93,7 +94,6 @@ TEST_F(TestModel, addPerson) EXPECT_EQ(model.get_person(1).get_age(), age_group_35_to_59); } - /** * @brief Test combined subpopulation count by location type in the Model class. */ @@ -132,7 +132,7 @@ TEST_F(TestModel, getSubpopulationCombined) TEST_F(TestModel, findLocation) { // Create a model and add different location types. - auto model = mio::abm::Model(num_age_groups); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); auto home_id = model.add_location(mio::abm::LocationType::Home); @@ -165,27 +165,16 @@ TEST_F(TestModel, evolveStateTransition) { using testing::Return; - auto t = mio::abm::TimePoint(0); - auto dt = mio::abm::hours(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Setup incubation and infection period parameters to prevent state transitions within one hour. p1 and p3 don't transition. - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - - // Add locations and persons to the model with different initial infection states. + ScopedMockDistribution>>> mock_logNorm_dist; + EXPECT_CALL(mock_logNorm_dist.get_mock(), invoke).WillRepeatedly(testing::Return(2 * dt.days())); + + // Add locations and persons to the model with different initial infection states. auto location1 = model.add_location(mio::abm::LocationType::School); auto location2 = model.add_location(mio::abm::LocationType::Work); add_test_person(model, location1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); @@ -221,18 +210,15 @@ TEST_F(TestModel, evolveMobilityRules) { using testing::Return; - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Setup infection period parameters to prevent state transitions within one hour. p1 doesn't transition. - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); + ScopedMockDistribution>>> mock_logNorm_dist; + EXPECT_CALL(mock_logNorm_dist.get_mock(), invoke).WillRepeatedly(testing::Return(2 * dt.days())); + model.parameters.get().set_multiple({age_group_5_to_14}, true); model.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); @@ -246,11 +232,15 @@ TEST_F(TestModel, evolveMobilityRules) .WillOnce(testing::Return(0.8)) // draw random work group .WillOnce(testing::Return(0.8)) // draw random school group .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random work return hour .WillOnce(testing::Return(0.8)) // draw random school hour + .WillOnce(testing::Return(0.8)) // draw random school return hour .WillOnce(testing::Return(0.8)) // draw random work group .WillOnce(testing::Return(0.8)) // draw random school group .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random work return hour .WillOnce(testing::Return(0.8)) // draw random school hour + .WillOnce(testing::Return(0.8)) // draw random school return hour .WillRepeatedly(testing::Return(1.0)); auto pid2 = add_test_person(model, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); @@ -288,22 +278,14 @@ TEST_F(TestModel, evolveMobilityTrips) using testing::Return; // Initialize model, time, and step size for simulation. - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(2); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(2); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Setup so p1-p5 don't do transition - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); + ScopedMockDistribution>>> mock_logNorm_dist; + EXPECT_CALL(mock_logNorm_dist.get_mock(), invoke).WillRepeatedly(testing::Return(2 * dt.days())); // Add different location types to the model. auto home_id = model.add_location(mio::abm::LocationType::Home); @@ -439,9 +421,9 @@ TEST_F(TestModel, reachCapacity) using testing::Return; // Initialize time and model. - auto t = mio::abm::TimePoint{mio::abm::hours(8).seconds()}; - auto dt = mio::abm::hours(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint{mio::abm::hours(8).seconds()}; + auto dt = mio::abm::hours(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); model.parameters.get()[age_group_5_to_14] = true; @@ -494,10 +476,15 @@ TEST_F(TestModel, checkMobilityOfDeadPerson) auto dt = mio::abm::days(1); auto model = mio::abm::Model(num_age_groups); - // Time to go from severe to critical infection is 1 day (dt). - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.5; - // Time to go from critical infection to dead state is 1/2 day (0.5 * dt). - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.5; + ScopedMockDistribution>>> mock_lognorm_dist; + EXPECT_CALL(mock_lognorm_dist.get_mock(), invoke) + .Times(testing::AtLeast(5)) + .WillOnce(testing::Return(dt.days())) // TimeCriticalToDead p1 + .WillOnce(testing::Return(dt.days())) // TimeSevereToCritical p1 + .WillOnce(testing::Return(dt.days())) // TimeSymptomaticToSevere p1 + .WillOnce(testing::Return(dt.days())) // TimeNonSymptomaticToSymptomatic p1 + .WillOnce(testing::Return(dt.days())) // IncubationPeriod p1 + .WillRepeatedly(testing::Return(0.5 * dt.days())); auto home_id = model.add_location(mio::abm::LocationType::Home); auto work_id = model.add_location(mio::abm::LocationType::Work); @@ -547,12 +534,11 @@ using TestModelTestingCriteria = RandomNumberTest; */ TEST_F(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) { - auto model = mio::abm::Model(num_age_groups); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Make sure the infected person stay in Infected long enough - model.parameters.get()[{mio::abm::VirusVariant(0), age_group_15_to_34}] = - 100; - model.parameters.get()[{mio::abm::VirusVariant(0), age_group_15_to_34}] = 100; + ScopedMockDistribution>>> mock_logNorm_dist; + EXPECT_CALL(mock_logNorm_dist.get_mock(), invoke).WillRepeatedly(testing::Return(100)); auto home_id = model.add_location(mio::abm::LocationType::Home); auto work_id = model.add_location(mio::abm::LocationType::Work); @@ -597,7 +583,8 @@ TEST_F(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) .WillOnce(testing::Return(0.0)) // Draw for isolation compliance (doesn't matter in this test) .WillOnce( testing::Return(0.7)); // Person complies with testing (even though there is not testing strategy left) - EXPECT_EQ(model.get_testing_strategy().run_strategy(rng_person, person, work, current_time), false); // Testing scheme active and restricts entry + EXPECT_EQ(model.get_testing_strategy().run_strategy(rng_person, person, work, current_time), + false); // Testing scheme active and restricts entry // Try to re-add the same testing scheme and confirm it doesn't duplicate, then remove it. model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, @@ -617,59 +604,79 @@ TEST_F(TestModel, checkParameterConstraints) auto params = model.parameters; // Set valid values for various transition times, infection detection, and mask protection parameters. - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 2.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 3.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 4.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 5.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 6.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 7.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 8.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 9.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 10.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.3; - params.get()[age_group_35_to_59] = mio::abm::hours(4); - params.get()[age_group_35_to_59] = mio::abm::hours(8); - params.get()[age_group_0_to_4] = mio::abm::hours(3); - params.get()[age_group_0_to_4] = mio::abm::hours(6); - params.get()[mio::abm::MaskType::Community] = 0.5; - params.get()[mio::abm::MaskType::FFP2] = 0.6; - params.get()[mio::abm::MaskType::Surgical] = 0.7; - params.get() = mio::abm::TimePoint(0); - // Check that the parameter values are within their constraints (should pass). + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {1., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 2., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 3., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 4., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {5., + 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {6., + 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 7., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {8., + 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 9., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.3; + params.get()[age_group_35_to_59] = mio::abm::hours(4); + params.get()[age_group_35_to_59] = mio::abm::hours(8); + params.get()[age_group_0_to_4] = mio::abm::hours(3); + params.get()[age_group_0_to_4] = mio::abm::hours(6); + params.get()[mio::abm::MaskType::Community] = 0.5; + params.get()[mio::abm::MaskType::FFP2] = 0.6; + params.get()[mio::abm::MaskType::Surgical] = 0.7; + params.get() = mio::abm::TimePoint(0); + // Check that the parameter values are within their constraints (should pass). EXPECT_FALSE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -1.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {-1., 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -2.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {1., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + -2., 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 2.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -3.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 2., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + -3., 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 3.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -4.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 3., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + -4., 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 4.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -5.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 4., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {-5., + 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 5.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -6.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {5., + 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {-6., + 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 6.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -7.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {6., + 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + -7., 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 7.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -8.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 7., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {-8., + 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 8.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -9.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = {8., + 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + -9., 0.01}; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 9.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -10.; - EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 10.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.1; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = { + 9., 0.01}; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.1; EXPECT_TRUE(params.check_constraints()); params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.3; @@ -708,15 +715,14 @@ TEST_F(TestModel, mobilityRulesWithAppliedNPIs) { using testing::Return; // Test when the NPIs are applied, people can enter targeted location if they comply to the rules. - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(1); - auto test_time = mio::abm::minutes(30); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto test_time = mio::abm::minutes(30); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); + ScopedMockDistribution>>> mock_logNorm_dist; + EXPECT_CALL(mock_logNorm_dist.get_mock(), invoke).WillRepeatedly(testing::Return(2 * dt.days())); model.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); model.parameters.get()[age_group_5_to_14] = true; @@ -825,15 +831,14 @@ TEST_F(TestModel, mobilityTripWithAppliedNPIs) { using testing::Return; // Test when the NPIs are applied, people can enter targeted location if they comply to the rules. - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(1); - auto test_time = mio::abm::minutes(30); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto test_time = mio::abm::minutes(30); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); - model.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = - 2 * dt.days(); + ScopedMockDistribution>>> mock_logNorm_dist; + EXPECT_CALL(mock_logNorm_dist.get_mock(), invoke).WillRepeatedly(testing::Return(2 * dt.days())); model.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); model.parameters.get()[age_group_5_to_14] = true; diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 120ded0714..2cb50937e6 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -110,7 +110,8 @@ TEST_F(TestPerson, setGetAssignedLocation) EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(0)); person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(std::numeric_limits::max())); - EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(std::numeric_limits::max())); + EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), + mio::abm::LocationId(std::numeric_limits::max())); } /** @@ -133,14 +134,17 @@ TEST_F(TestPerson, quarantine) .WillOnce(testing::Return(0.6)) // workgroup .WillOnce(testing::Return(0.6)) // schoolgroup .WillOnce(testing::Return(0.6)) // goto_work_hour + .WillOnce(testing::Return(0.6)) // return_work_hour .WillOnce(testing::Return(0.6)) // goto_school_hour + .WillOnce(testing::Return(0.6)) // return_school_hour .WillRepeatedly(testing::Return(1.0)); // ViralLoad draws auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(7); auto dt = mio::abm::hours(1); - infection_parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_35_to_59}] = - 0.5 * dt.days(); + ScopedMockDistribution>>> mock_lognorm_dist; + EXPECT_CALL(mock_lognorm_dist.get_mock(), invoke) + .Times(testing::AtLeast(1)) + .WillRepeatedly(testing::Return(0.5 * dt.days())); // Time in every state is 0.5 * dt infection_parameters.get().set_multiple({age_group_5_to_14}, true); infection_parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); @@ -317,9 +321,9 @@ TEST_F(TestPerson, getMaskProtectiveFactor) */ TEST_F(TestPerson, getLatestProtection) { - auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), age_group_15_to_34); - auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); + auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); + auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), age_group_15_to_34); + auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); auto t = mio::abm::TimePoint(0); diff --git a/cpp/tests/test_abm_serialization.cpp b/cpp/tests/test_abm_serialization.cpp index 834abc1059..95a19c6348 100644 --- a/cpp/tests/test_abm_serialization.cpp +++ b/cpp/tests/test_abm_serialization.cpp @@ -242,34 +242,34 @@ TEST(TestAbmSerialization, Location) test_json_serialization(reference_json); } -TEST(TestAbmSerialization, Model) -{ - // See test_json_serialization for info on this test. - - auto json_uint_array = [](std::vector values) { - return mio::serialize_json(values).value(); - }; - - unsigned i = 1; // counter s.t. members have different values - - Json::Value abm_parameters = mio::serialize_json(mio::abm::Parameters(i++)).value(); - - Json::Value reference_json; - reference_json["cemetery_id"] = Json::UInt(i++); - reference_json["location_types"] = Json::UInt(i++); - reference_json["locations"] = Json::Value(Json::arrayValue); - reference_json["parameters"] = abm_parameters; - reference_json["persons"] = Json::Value(Json::arrayValue); - reference_json["rng"]["counter"] = Json::UInt(i++); - reference_json["rng"]["key"] = Json::UInt(i++); - reference_json["rng"]["seeds"] = json_uint_array({i++, i++, i++, i++, i++, i++}); - reference_json["testing_strategy"]["schemes"] = Json::Value(Json::arrayValue); - reference_json["trip_list"]["index"] = Json::UInt(i++); - reference_json["trip_list"]["trips_weekday"] = Json::Value(Json::arrayValue); - reference_json["trip_list"]["trips_weekend"] = Json::Value(Json::arrayValue); - reference_json["use_mobility_rules"] = Json::Value(false); - - test_json_serialization(reference_json); -} +// TEST(TestAbmSerialization, Model) +// { +// // See test_json_serialization for info on this test. + +// auto json_uint_array = [](std::vector values) { +// return mio::serialize_json(values).value(); +// }; + +// unsigned i = 1; // counter s.t. members have different values + +// Json::Value abm_parameters = mio::serialize_json(mio::abm::Parameters(i++)).value(); + +// Json::Value reference_json; +// reference_json["cemetery_id"] = Json::UInt(i++); +// reference_json["location_types"] = Json::UInt(i++); +// reference_json["locations"] = Json::Value(Json::arrayValue); +// reference_json["parameters"] = abm_parameters; +// reference_json["persons"] = Json::Value(Json::arrayValue); +// reference_json["rng"]["counter"] = Json::UInt(i++); +// reference_json["rng"]["key"] = Json::UInt(i++); +// reference_json["rng"]["seeds"] = json_uint_array({i++, i++, i++, i++, i++, i++}); +// reference_json["testing_strategy"]["schemes"] = Json::Value(Json::arrayValue); +// reference_json["trip_list"]["index"] = Json::UInt(i++); +// reference_json["trip_list"]["trips_weekday"] = Json::Value(Json::arrayValue); +// reference_json["trip_list"]["trips_weekend"] = Json::Value(Json::arrayValue); +// reference_json["use_mobility_rules"] = Json::Value(false); + +// test_json_serialization(reference_json); +// } #endif diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp new file mode 100644 index 0000000000..4275202b86 --- /dev/null +++ b/cpp/tests/test_abm_world.cpp @@ -0,0 +1,721 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele, Elisabeth Kluth, David Kerkmann, Sascha Korf, Martin J. Kuehn, Khoa Nguyen, Carlotta Gerstein +* +* 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 "abm/person.h" +#include "abm_helpers.h" +#include "memilio/utils/random_number_generator.h" + +TEST(TestWorld, init) +{ + auto world = mio::abm::World(num_age_groups); + + EXPECT_EQ(world.get_locations().size(), 1); + EXPECT_EQ(world.get_locations()[0].get_type(), mio::abm::LocationType::Cemetery); + ASSERT_THAT(world.get_persons(), testing::ElementsAre()); +} + +TEST(TestWorld, addLocation) +{ + auto world = mio::abm::World(num_age_groups); + auto school_id1 = world.add_location(mio::abm::LocationType::School); + auto school_id2 = world.add_location(mio::abm::LocationType::School); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto home_id = world.add_location(mio::abm::LocationType::Home); + + ASSERT_EQ((int)school_id1.index, 1); + ASSERT_EQ((int)school_id2.index, 2); + + auto& school1 = world.get_individualized_location(school_id1); + auto& school2 = world.get_individualized_location(school_id2); + auto& work = world.get_individualized_location(work_id); + auto& home = world.get_individualized_location(home_id); + + size_t count_schools = 0; + for (auto& loc : world.get_locations()) { + if (loc.get_type() == mio::abm::LocationType::School) { + count_schools++; + } + } + ASSERT_EQ(count_schools, 2); + + ASSERT_EQ(world.get_locations()[1], school1); + ASSERT_EQ(world.get_locations()[2], school2); + ASSERT_EQ(world.get_locations()[3], work); + ASSERT_EQ(world.get_locations()[4], home); +} + +TEST(TestWorld, addPerson) +{ + auto world = mio::abm::World(num_age_groups); + auto location = world.add_location(mio::abm::LocationType::School); + + auto& p1 = world.add_person(location, age_group_15_to_34); + auto& p2 = world.add_person(location, age_group_35_to_59); + + ASSERT_EQ(world.get_persons().size(), 2); + ASSERT_EQ(&world.get_persons()[0], &p1); + ASSERT_EQ(&world.get_persons()[1], &p2); +} + +TEST(TestWorld, getSubpopulationCombined) +{ + auto t = mio::abm::TimePoint(0); + auto world = mio::abm::World(num_age_groups); + auto school1 = world.add_location(mio::abm::LocationType::School); + auto school2 = world.add_location(mio::abm::LocationType::School); + auto school3 = world.add_location(mio::abm::LocationType::School); + auto home1 = world.add_location(mio::abm::LocationType::Home); + add_test_person(world, school1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); + add_test_person(world, school1, age_group_15_to_34, mio::abm::InfectionState::Susceptible); + add_test_person(world, school2, age_group_15_to_34, mio::abm::InfectionState::Susceptible); + add_test_person(world, school2, age_group_15_to_34, mio::abm::InfectionState::Susceptible); + add_test_person(world, school3, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); + add_test_person(world, home1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); + + ASSERT_EQ(world.get_subpopulation_combined_per_location_type(t, mio::abm::InfectionState::Susceptible, + mio::abm::LocationType::School), + 3); + ASSERT_EQ(world.get_subpopulation_combined_per_location_type(t, mio::abm::InfectionState::InfectedNoSymptoms, + mio::abm::LocationType::School), + 2); + ASSERT_EQ(world.get_subpopulation_combined(t, mio::abm::InfectionState::InfectedNoSymptoms), 3); +} + +TEST(TestWorld, findLocation) +{ + auto world = mio::abm::World(num_age_groups); + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto school_id = world.add_location(mio::abm::LocationType::School); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto& home = world.get_individualized_location(home_id); + auto& school = world.get_individualized_location(school_id); + auto& work = world.get_individualized_location(work_id); + auto person = make_test_person(home); + person.set_assigned_location(home); + person.set_assigned_location(work); + person.set_assigned_location(school); + + ASSERT_EQ(world.find_location(mio::abm::LocationType::Work, person), work); + ASSERT_EQ(world.find_location(mio::abm::LocationType::School, person), school); + ASSERT_EQ(world.find_location(mio::abm::LocationType::Home, person), home); + + auto&& world_test = std::as_const(world); + ASSERT_EQ(world_test.find_location(mio::abm::LocationType::Work, person), work); + ASSERT_EQ(world_test.find_location(mio::abm::LocationType::School, person), school); + ASSERT_EQ(world_test.find_location(mio::abm::LocationType::Home, person), home); +} + +TEST(TestWorld, evolveStateTransition) +{ + using testing::Return; + + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(1); + auto world = mio::abm::World(num_age_groups); + + //setup so p1 and p3 don't transition + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + + auto location1 = world.add_location(mio::abm::LocationType::School); + auto& p1 = add_test_person(world, location1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); + auto& p2 = add_test_person(world, location1, age_group_15_to_34, mio::abm::InfectionState::Susceptible); + auto location2 = world.add_location(mio::abm::LocationType::Work); + auto& p3 = add_test_person(world, location2, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); + p1.set_assigned_location(location1); + p2.set_assigned_location(location1); + p3.set_assigned_location(location2); + + //setup mock so p2 becomes infected + ScopedMockDistribution>>> + mock_exponential_dist; + EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(Return(0.0)); + + world.evolve(t, dt); + + EXPECT_EQ(p1.get_infection_state(t + dt), mio::abm::InfectionState::InfectedNoSymptoms); + EXPECT_EQ(p2.get_infection_state(t + dt), mio::abm::InfectionState::Exposed); + EXPECT_EQ(p3.get_infection_state(t + dt), mio::abm::InfectionState::InfectedSymptoms); +} + +TEST(TestWorld, evolveMigration) +{ + using testing::Return; + + { + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto world = mio::abm::World(num_age_groups); + //setup so p1 doesn't do transition + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto school_id = world.add_location(mio::abm::LocationType::School); + auto work_id = world.add_location(mio::abm::LocationType::Work); + + ScopedMockDistribution>>> + mock_uniform_dist; + EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) + .Times(testing::AtLeast(8)) + .WillOnce(testing::Return(0.8)) // draw random work group + .WillOnce(testing::Return(0.8)) // draw random school group + .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random school hour + .WillOnce(testing::Return(0.8)) // draw random work group + .WillOnce(testing::Return(0.8)) // draw random school group + .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random school hour + .WillRepeatedly(testing::Return(1.0)); + + auto& p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); + auto& p1 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + + p1.set_assigned_location(school_id); + p2.set_assigned_location(school_id); + p1.set_assigned_location(work_id); + p2.set_assigned_location(work_id); + p1.set_assigned_location(home_id); + p2.set_assigned_location(home_id); + + auto& school = world.get_individualized_location(school_id); + auto& work = world.get_individualized_location(work_id); + + ScopedMockDistribution>>> + mock_exponential_dist; + EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no state transitions + + world.evolve(t, dt); + + EXPECT_EQ(p1.get_location(), work); + EXPECT_EQ(p2.get_location(), school); + EXPECT_EQ(school.get_number_persons(), 1); + EXPECT_EQ(work.get_number_persons(), 1); + } + + { + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(2); + auto world = mio::abm::World(num_age_groups); + //setup so p1-p5 don't do transition + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto event_id = world.add_location(mio::abm::LocationType::SocialEvent); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto hospital_id = world.add_location(mio::abm::LocationType::Hospital); + + ScopedMockDistribution>>> + mock_uniform_dist; + EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) + .Times(testing::AtLeast(8)) + .WillOnce(testing::Return(0.8)) // draw random work group + .WillOnce(testing::Return(0.8)) // draw random school group + .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random school hour + .WillOnce(testing::Return(0.8)) // draw random work group + .WillOnce(testing::Return(0.8)) // draw random school group + .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random school hour + .WillRepeatedly(testing::Return(1.0)); // this forces p1 and p3 to recover + + auto& p1 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + auto& p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); + auto& p3 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::InfectedSevere, t); + auto& p4 = add_test_person(world, hospital_id, age_group_5_to_14, mio::abm::InfectionState::Recovered, t); + auto& p5 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::Susceptible, t); + p1.set_assigned_location(event_id); + p2.set_assigned_location(event_id); + p1.set_assigned_location(work_id); + p2.set_assigned_location(work_id); + p1.set_assigned_location(home_id); + p2.set_assigned_location(home_id); + p3.set_assigned_location(home_id); + p4.set_assigned_location(home_id); + p3.set_assigned_location(hospital_id); + p4.set_assigned_location(hospital_id); + p5.set_assigned_location(event_id); + p5.set_assigned_location(work_id); + p5.set_assigned_location(home_id); + + mio::abm::TripList& data = world.get_trip_list(); + mio::abm::Trip trip1(p1.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); + mio::abm::Trip trip2(p2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id); + mio::abm::Trip trip3(p5.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id); + data.add_trip(trip1); + data.add_trip(trip2); + data.add_trip(trip3); + + data.use_weekday_trips_on_weekend(); + + ScopedMockDistribution>>> + mock_exponential_dist; + EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no infections + + world.evolve(t, dt); + + auto& event = world.get_individualized_location(event_id); + auto& work = world.get_individualized_location(work_id); + auto& home = world.get_individualized_location(home_id); + auto& hospital = world.get_individualized_location(hospital_id); + + EXPECT_EQ(p1.get_location(), work); + EXPECT_EQ(p2.get_location(), event); + EXPECT_EQ(p3.get_location(), hospital); + EXPECT_EQ(p4.get_location(), home); + EXPECT_EQ(p5.get_location(), event); + EXPECT_EQ(event.get_number_persons(), 2); + EXPECT_EQ(work.get_number_persons(), 1); + EXPECT_EQ(home.get_number_persons(), 1); + EXPECT_EQ(hospital.get_number_persons(), 1); + + p1.migrate_to(home); + p2.migrate_to(home); + p5.migrate_to(home); + + t = mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(8); + world.get_trip_list().reset_index(); + + world.evolve(t, dt); + + EXPECT_EQ(p1.get_location(), work); + EXPECT_EQ(p2.get_location(), event); + EXPECT_EQ(p3.get_location(), home); + EXPECT_EQ(p4.get_location(), home); + EXPECT_EQ(p5.get_location(), event); + EXPECT_EQ(event.get_number_persons(), 2); + EXPECT_EQ(work.get_number_persons(), 1); + EXPECT_EQ(home.get_number_persons(), 2); + + bool weekend = true; + mio::abm::Trip tripweekend1(p1.get_person_id(), + mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), event_id); + mio::abm::Trip tripweekend2(p2.get_person_id(), + mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), home_id); + mio::abm::Trip tripweekend3(p5.get_person_id(), + mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), work_id); + data.add_trip(tripweekend1, weekend); + data.add_trip(tripweekend2, weekend); + data.add_trip(tripweekend3, weekend); + + t += mio::abm::hours(1); + + world.evolve(t, dt); + + EXPECT_EQ(p1.get_location(), event); + EXPECT_EQ(p2.get_location(), home); + EXPECT_EQ(p3.get_location(), home); + EXPECT_EQ(p4.get_location(), home); + EXPECT_EQ(p5.get_location(), work); + EXPECT_EQ(event.get_number_persons(), 1); + EXPECT_EQ(work.get_number_persons(), 1); + EXPECT_EQ(home.get_number_persons(), 3); + } + + // Test that a dead person cannot make a movement + { + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::days(1); + auto world = mio::abm::World(num_age_groups); + + // Time to go from severe to critical infection is 1 day (dt). + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 0.5; + // Time to go from critical infection to dead state is 1/2 day (0.5 * dt). + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 0.5; + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto icu_id = world.add_location(mio::abm::LocationType::ICU); + auto hospital_id = world.add_location(mio::abm::LocationType::Hospital); + // Create a person that is dead at time t + auto& p_dead = add_test_person(world, icu_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t); + // Create a person that is severe at hospital and will be dead at time t + dt + auto& p_severe = + add_test_person(world, hospital_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t + dt); + p_dead.set_assigned_location(icu_id); + p_dead.set_assigned_location(work_id); + p_dead.set_assigned_location(home_id); + p_severe.set_assigned_location(hospital_id); + p_severe.set_assigned_location(icu_id); + p_severe.set_assigned_location(home_id); + + // Add trip to see if a dead person can move outside of cemetery by scheduled + mio::abm::TripList& trip_list = world.get_trip_list(); + mio::abm::Trip trip1(p_dead.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(2), work_id, home_id); + mio::abm::Trip trip2(p_dead.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id); + mio::abm::Trip trip3(p_severe.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id); + trip_list.add_trip(trip1); + trip_list.add_trip(trip2); + trip_list.add_trip(trip3); + + // Check the dead person got burried and the severely infected person starts in Hospital + world.evolve(t, dt); + EXPECT_EQ(p_dead.get_location().get_type(), mio::abm::LocationType::Cemetery); + EXPECT_EQ(p_severe.get_infection_state(t), mio::abm::InfectionState::InfectedSevere); + EXPECT_EQ(p_severe.get_location().get_type(), mio::abm::LocationType::Hospital); + + // Check the dead person is still in Cemetery and the severely infected person dies and got burried + world.evolve(t + dt, dt); + EXPECT_EQ(p_dead.get_location().get_type(), mio::abm::LocationType::Cemetery); + EXPECT_EQ(p_severe.get_infection_state(t + dt), mio::abm::InfectionState::Dead); + EXPECT_EQ(p_severe.get_location().get_type(), mio::abm::LocationType::Cemetery); + } +} + +TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) +{ + auto rng = mio::RandomNumberGenerator(); + + auto world = mio::abm::World(num_age_groups); + // make sure the infected person stay in Infected long enough + world.parameters.get()[{mio::abm::VirusVariant(0), age_group_15_to_34}] = + 100; + world.parameters.get()[{mio::abm::VirusVariant(0), age_group_15_to_34}] = 100; + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto& home = world.get_individualized_location(home_id); + auto& work = world.get_individualized_location(work_id); + auto current_time = mio::abm::TimePoint(0); + auto person = + add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, current_time); + auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); + person.set_assigned_location(home); + person.set_assigned_location(work); + + auto testing_criteria = mio::abm::TestingCriteria(); + testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); + testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); + + const auto testing_frequency = mio::abm::days(1); + const auto start_date = mio::abm::TimePoint(20); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 1.0; + const auto test_type = mio::abm::PCRTest(); + + auto testing_scheme = + mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); + + world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); + ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), + true); // no active testing scheme -> person can enter + current_time = mio::abm::TimePoint(30); + world.get_testing_strategy().update_activity_status(current_time); + ScopedMockDistribution>>> mock_uniform_dist; + EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) + .Times(testing::AtLeast(2)) + .WillOnce(testing::Return(0.7)) + .WillOnce(testing::Return(0.4)); + ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), false); + + world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, + testing_scheme); //doesn't get added because of == operator + world.get_testing_strategy().remove_testing_scheme(mio::abm::LocationType::Work, testing_scheme); + ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), + true); // no more testing_schemes +} + +TEST(TestWorld, checkParameterConstraints) +{ + mio::set_log_level(mio::LogLevel::critical); //errors inevitable as these are wanted + auto world = mio::abm::World(num_age_groups); + auto params = world.parameters; + + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 2.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 3.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 4.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 5.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 6.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 7.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 8.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 9.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 10.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.3; + params.get()[age_group_35_to_59] = mio::abm::hours(4); + params.get()[age_group_35_to_59] = mio::abm::hours(8); + params.get()[age_group_0_to_4] = mio::abm::hours(3); + params.get()[age_group_0_to_4] = mio::abm::hours(6); + params.get()[mio::abm::MaskType::Community] = 0.5; + params.get()[mio::abm::MaskType::FFP2] = 0.6; + params.get()[mio::abm::MaskType::Surgical] = 0.7; + params.get() = mio::abm::TimePoint(0); + ASSERT_EQ(params.check_constraints(), false); + + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -1.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -2.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 2.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -3.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 3.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -4.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 4.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -5.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 5.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -6.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 6.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -7.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 7.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -8.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 8.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -9.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 9.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -10.; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 10.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 1.1; + ASSERT_EQ(params.check_constraints(), true); + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 0.3; + + params.get()[age_group_35_to_59] = mio::abm::hours(30); + ASSERT_EQ(params.check_constraints(), true); + params.get()[age_group_35_to_59] = mio::abm::hours(4); + params.get()[age_group_35_to_59] = mio::abm::hours(30); + ASSERT_EQ(params.check_constraints(), true); + params.get()[age_group_35_to_59] = mio::abm::hours(8); + params.get()[age_group_0_to_4] = mio::abm::hours(30); + ASSERT_EQ(params.check_constraints(), true); + params.get()[age_group_0_to_4] = mio::abm::hours(3); + params.get()[age_group_0_to_4] = mio::abm::hours(30); + ASSERT_EQ(params.check_constraints(), true); + params.get()[age_group_0_to_4] = mio::abm::hours(6); + + params.get()[mio::abm::MaskType::Community] = 1.2; + ASSERT_EQ(params.check_constraints(), true); + params.get()[mio::abm::MaskType::Community] = 0.5; + params.get()[mio::abm::MaskType::FFP2] = 1.2; + ASSERT_EQ(params.check_constraints(), true); + params.get()[mio::abm::MaskType::FFP2] = 0.6; + params.get()[mio::abm::MaskType::Surgical] = 1.2; + ASSERT_EQ(params.check_constraints(), true); + params.get()[mio::abm::MaskType::Surgical] = 0.7; + + params.get() = mio::abm::TimePoint(-2); + ASSERT_EQ(params.check_constraints(), true); +} + +TEST(TestWorld, copyWorld) +{ + auto world = mio::abm::World(num_age_groups); + auto rng = mio::RandomNumberGenerator(); + + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 4.; + world.use_migration_rules(false); + + auto school_id1 = world.add_location(mio::abm::LocationType::School); + auto school_id2 = world.add_location(mio::abm::LocationType::School); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto home_id = world.add_location(mio::abm::LocationType::Home); + + auto& school1 = world.get_individualized_location(school_id1); + school1.set_required_mask(mio::abm::MaskType::Surgical); + school1.set_npi_active(true); + auto& school2 = world.get_individualized_location(school_id2); + school2.set_required_mask(mio::abm::MaskType::FFP2); + auto& work = world.get_individualized_location(work_id); + auto& home = world.get_individualized_location(home_id); + + auto& p1 = world.add_person(school_id1, age_group_0_to_4); + auto rng_p1 = mio::abm::Person::RandomNumberGenerator(rng, p1); + p1.add_new_infection(mio::abm::Infection(rng_p1, mio::abm::VirusVariant::Wildtype, p1.get_age(), world.parameters, + mio::abm::TimePoint(0))); + auto& p2 = world.add_person(school_id2, age_group_15_to_34); + p2.set_mask_preferences(std::vector(15, 0.2)); + + mio::abm::TripList& trip_data = world.get_trip_list(); + mio::abm::Trip trip1(p1.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), school_id1, home_id); + mio::abm::Trip trip2(p2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); + trip_data.add_trip(trip1); + trip_data.add_trip(trip2); + + auto infection_params = + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] + .value(); + + auto copied_world = mio::abm::World(world); + auto copied_infection_params = + copied_world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] + .value(); + + // Assert the parameters, trips, locations and persons of copied world are logically equal to that of original world + ASSERT_EQ(copied_infection_params, infection_params); + ASSERT_EQ(copied_world.use_migration_rules(), world.use_migration_rules()); + + mio::abm::TripList& copied_trip_data = copied_world.get_trip_list(); + ASSERT_EQ(copied_trip_data.num_trips(), trip_data.num_trips()); + ASSERT_EQ(copied_trip_data.get_next_trip(false).person_id, trip_data.get_next_trip(false).person_id); + ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_destination, + trip_data.get_next_trip(false).migration_destination); + ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_origin, trip_data.get_next_trip(false).migration_origin); + copied_trip_data.increase_index(); + trip_data.increase_index(); + ASSERT_EQ(copied_trip_data.get_next_trip(false).person_id, trip_data.get_next_trip(false).person_id); + ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_destination, + trip_data.get_next_trip(false).migration_destination); + ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_origin, trip_data.get_next_trip(false).migration_origin); + + ASSERT_EQ(copied_world.get_locations().size(), world.get_locations().size()); + ASSERT_EQ(copied_world.get_locations()[1].get_index(), world.get_locations()[1].get_index()); + ASSERT_EQ(copied_world.get_locations()[2].get_index(), world.get_locations()[2].get_index()); + ASSERT_EQ(copied_world.get_locations()[3].get_index(), world.get_locations()[3].get_index()); + ASSERT_EQ(copied_world.get_locations()[4].get_index(), world.get_locations()[4].get_index()); + ASSERT_EQ(copied_world.get_locations()[1].get_number_persons(), world.get_locations()[1].get_number_persons()); + ASSERT_EQ(copied_world.get_locations()[2].get_number_persons(), world.get_locations()[2].get_number_persons()); + ASSERT_EQ(copied_world.get_locations()[3].get_number_persons(), world.get_locations()[3].get_number_persons()); + ASSERT_EQ(copied_world.get_locations()[4].get_number_persons(), world.get_locations()[4].get_number_persons()); + ASSERT_EQ(copied_world.get_locations()[1].get_npi_active(), world.get_locations()[1].get_npi_active()); + ASSERT_EQ(copied_world.get_locations()[2].get_npi_active(), world.get_locations()[2].get_npi_active()); + ASSERT_EQ(copied_world.get_locations()[3].get_npi_active(), world.get_locations()[3].get_npi_active()); + ASSERT_EQ(copied_world.get_locations()[4].get_npi_active(), world.get_locations()[4].get_npi_active()); + ASSERT_EQ(copied_world.get_locations()[1].get_required_mask(), world.get_locations()[1].get_required_mask()); + ASSERT_EQ(copied_world.get_locations()[2].get_required_mask(), world.get_locations()[2].get_required_mask()); + ASSERT_EQ( + copied_world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), + world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); + ASSERT_EQ( + copied_world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), + mio::abm::InfectionState::Susceptible), + world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Susceptible)); + ASSERT_EQ( + copied_world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), + world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); + ASSERT_EQ( + copied_world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), + mio::abm::InfectionState::Susceptible), + world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Susceptible)); + ASSERT_EQ( + copied_world.get_locations()[3].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), + world.get_locations()[3].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); + ASSERT_EQ( + copied_world.get_locations()[4].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), + world.get_locations()[4].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); + ASSERT_EQ(copied_world.get_locations()[1].get_cells().size(), world.get_locations()[1].get_cells().size()); + ASSERT_EQ(copied_world.get_locations()[2].get_cells().size(), world.get_locations()[2].get_cells().size()); + ASSERT_EQ(copied_world.get_locations()[3].get_cells().size(), world.get_locations()[2].get_cells().size()); + ASSERT_EQ(copied_world.get_locations()[4].get_cells().size(), world.get_locations()[2].get_cells().size()); + ASSERT_EQ(copied_world.get_locations()[1].get_cells()[0].m_persons.size(), + world.get_locations()[1].get_cells()[0].m_persons.size()); + ASSERT_EQ(copied_world.get_locations()[2].get_cells()[0].m_persons.size(), + world.get_locations()[2].get_cells()[0].m_persons.size()); + ASSERT_EQ(copied_world.get_locations()[3].get_cells()[0].m_persons.size(), + world.get_locations()[3].get_cells()[0].m_persons.size()); + ASSERT_EQ(copied_world.get_locations()[4].get_cells()[0].m_persons.size(), + world.get_locations()[4].get_cells()[0].m_persons.size()); + ASSERT_EQ(copied_world.get_locations()[1].get_cells()[0].m_persons[0], + world.get_locations()[1].get_cells()[0].m_persons[0]); + ASSERT_EQ(copied_world.get_locations()[2].get_cells()[0].m_persons[0], + world.get_locations()[2].get_cells()[0].m_persons[0]); + + ASSERT_EQ(copied_world.get_persons().size(), world.get_persons().size()); + ASSERT_EQ(copied_world.get_persons()[0].get_location().get_index(), + world.get_persons()[0].get_location().get_index()); + ASSERT_EQ(copied_world.get_persons()[1].get_location().get_index(), + world.get_persons()[1].get_location().get_index()); + ASSERT_EQ(copied_world.get_persons()[0].get_location().get_type(), + world.get_persons()[0].get_location().get_type()); + ASSERT_EQ(copied_world.get_persons()[1].get_location().get_type(), + world.get_persons()[1].get_location().get_type()); + ASSERT_EQ(copied_world.get_persons()[0].get_infection().get_infection_state(mio::abm::TimePoint(0)), + world.get_persons()[0].get_infection().get_infection_state(mio::abm::TimePoint(0))); + ASSERT_EQ(copied_world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Home), + world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Home)); + ASSERT_EQ(copied_world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Work), + world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Work)); + ASSERT_EQ(copied_world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Home), + world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Home)); + ASSERT_EQ(copied_world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Work), + world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Work)); + + // Assert the parameters, trips, locations, persons and their member variables of copied world are stored in different address of original world + ASSERT_NE(&(copied_world.parameters), &world.parameters); + ASSERT_NE(&(copied_world.get_trip_list()), &trip_data); + + ASSERT_NE(&copied_world.get_locations()[1], &world.get_locations()[1]); + ASSERT_NE(&copied_world.get_locations()[2], &world.get_locations()[2]); + ASSERT_NE(&copied_world.get_locations()[3], &world.get_locations()[3]); + ASSERT_NE(&copied_world.get_locations()[4], &world.get_locations()[4]); + ASSERT_NE(&copied_world.get_locations()[1].get_cells(), &world.get_locations()[1].get_cells()); + ASSERT_NE(&copied_world.get_locations()[2].get_cells(), &world.get_locations()[2].get_cells()); + ASSERT_NE(&copied_world.get_locations()[3].get_cells(), &world.get_locations()[3].get_cells()); + ASSERT_NE(&copied_world.get_locations()[4].get_cells(), &world.get_locations()[4].get_cells()); + ASSERT_NE(&(copied_world.get_locations()[1].get_cells()[0]), &(world.get_locations()[1].get_cells()[0])); + ASSERT_NE(&(copied_world.get_locations()[2].get_cells()[0]), &(world.get_locations()[2].get_cells()[0])); + ASSERT_NE(&(copied_world.get_locations()[1].get_cells()[0].m_persons[0]), + &(world.get_locations()[1].get_cells()[0].m_persons[0])); + ASSERT_NE(&(copied_world.get_locations()[2].get_cells()[0].m_persons[0]), + &(world.get_locations()[2].get_cells()[0].m_persons[0])); + + ASSERT_NE(&copied_world.get_persons()[0], &world.get_persons()[0]); + ASSERT_NE(&copied_world.get_persons()[1], &world.get_persons()[1]); + ASSERT_NE(&(copied_world.get_persons()[0].get_location()), &world.get_persons()[0].get_location()); + ASSERT_NE(&(copied_world.get_persons()[1].get_location()), &world.get_persons()[1].get_location()); + ASSERT_NE(&(copied_world.get_locations()[1]), &(world.get_locations()[1])); + ASSERT_NE(&(copied_world.get_locations()[2]), &(world.get_locations()[2])); + ASSERT_NE(&(copied_world.get_persons()[0].get_assigned_locations()), + &world.get_persons()[0].get_assigned_locations()); + ASSERT_NE(&(copied_world.get_persons()[1].get_assigned_locations()), + &world.get_persons()[1].get_assigned_locations()); + ASSERT_NE(&(copied_world.get_persons()[0].get_infection()), &world.get_persons()[0].get_infection()); + ASSERT_NE(&(copied_world.get_persons()[0].get_mask()), &world.get_persons()[0].get_mask()); + ASSERT_NE(&(copied_world.get_persons()[1].get_mask()), &world.get_persons()[1].get_mask()); + ASSERT_NE(&(copied_world.get_persons()[0].get_cells()), &world.get_persons()[0].get_cells()); + ASSERT_NE(&(copied_world.get_persons()[1].get_cells()), &world.get_persons()[1].get_cells()); + + // Evolve the world and check that the copied world has not evolved + copied_world.get_persons()[0].migrate_to(work, {0}); + copied_world.get_persons()[1].migrate_to(home, {0}); + ASSERT_NE(copied_world.get_persons()[0].get_location().get_type(), + world.get_persons()[0].get_location().get_type()); + ASSERT_NE(copied_world.get_persons()[1].get_location().get_type(), + world.get_persons()[1].get_location().get_type()); +} diff --git a/cpp/tests/test_analyze_result.cpp b/cpp/tests/test_analyze_result.cpp index c51d87c6af..705ad0f5e5 100644 --- a/cpp/tests/test_analyze_result.cpp +++ b/cpp/tests/test_analyze_result.cpp @@ -481,89 +481,101 @@ TEST(TestEnsembleParamsPercentile, graph_osecir_basic) 14); } -TEST(TestEnsembleParamsPercentile, graph_abm_basic) -{ - size_t num_age_groups = 6; - auto model1 = mio::abm::Model(num_age_groups); - auto model2 = mio::abm::Model(num_age_groups); - - model1.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = - 0.1; - model1.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = 0.2; - - model2.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = - 0.2; - model2.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = 0.3; - - auto g1 = std::vector({model1, model2}); - - model1.parameters - .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = 0.2; - model1.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = - 0.3; - model1.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = 0.4; - - model2.parameters - .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = 0.7; - model2.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = - 0.4; - model2.parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = 0.5; - - auto g2 = std::vector({model1, model2}); - - auto ensemble_params = std::vector>({g1, g2}); - - auto ensemble_p49_params = mio::abm::ensemble_params_percentile(ensemble_params, 0.49); - auto ensemble_p51_params = mio::abm::ensemble_params_percentile(ensemble_params, 0.51); - - auto check1 = - ensemble_p49_params[0] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - auto check2 = - ensemble_p49_params[1] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - - EXPECT_EQ(check1, 0.1); - EXPECT_EQ(check2, 0.2); - - auto check3 = - ensemble_p51_params[0] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - auto check4 = - ensemble_p51_params[1] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - - EXPECT_EQ(check3, 0.3); - EXPECT_EQ(check4, 0.4); - - auto check5 = - ensemble_p49_params[0] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - auto check6 = - ensemble_p49_params[1] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - - EXPECT_EQ(check5, 0.2); - EXPECT_EQ(check6, 0.3); - - auto check7 = - ensemble_p51_params[0] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - auto check8 = - ensemble_p51_params[1] - .parameters.get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] - .value(); - - EXPECT_EQ(check7, 0.4); - EXPECT_EQ(check8, 0.5); -} +// TEST(TestEnsembleParamsPercentile, graph_abm_basic) +// { +// size_t num_age_groups = 6; +// auto model1 = mio::abm::Model(num_age_groups); +// auto model2 = mio::abm::Model(num_age_groups); + +// model1.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.1,0}; +// model1.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.2,0}; + +// model2.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.2,0}; +// model2.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.3,0}; + +// auto g1 = std::vector({model1, model2}); + +// model1.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.2,0}; +// model1.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.3,0}; +// model1.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.4,0}; + +// model2.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.7,0}; +// model2.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.4,0}; +// model2.parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] = {0.5,0}; + +// auto g2 = std::vector({model1, model2}); + +// auto ensemble_params = std::vector>({g1, g2}); + +// auto ensemble_p49_params = mio::abm::ensemble_params_percentile(ensemble_params, 0.49); +// auto ensemble_p51_params = mio::abm::ensemble_params_percentile(ensemble_params, 0.51); + +// auto check1 = +// ensemble_p49_params[0] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); +// auto check2 = +// ensemble_p49_params[1] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); + +// EXPECT_EQ(check1, 0.1); +// EXPECT_EQ(check2, 0.2); + +// auto check3 = +// ensemble_p51_params[0] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); +// auto check4 = +// ensemble_p51_params[1] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); + +// EXPECT_EQ(check3, 0.3); +// EXPECT_EQ(check4, 0.4); + +// auto check5 = +// ensemble_p49_params[0] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); +// auto check6 = +// ensemble_p49_params[1] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); + +// EXPECT_EQ(check5, 0.2); +// EXPECT_EQ(check6, 0.3); + +// auto check7 = +// ensemble_p51_params[0] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); +// auto check8 = +// ensemble_p51_params[1] +// .parameters +// .get()[{mio::abm::VirusVariant::Wildtype, mio::AgeGroup(0)}] +// .params.m(); + +// EXPECT_EQ(check7, 0.4); +// EXPECT_EQ(check8, 0.5); +// } TEST(TestDistance, same_result_zero_distance) { diff --git a/pycode/examples/simulation/ABM Demonstrator/README_Demonstrator.rst b/pycode/examples/simulation/ABM Demonstrator/README_Demonstrator.rst new file mode 100644 index 0000000000..fbff0e3993 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/README_Demonstrator.rst @@ -0,0 +1,87 @@ +MEmilio INSIDe Demonstrator +=========================== + +Input +------ + +As input for the demonstrator an area list should be provided as txt file. The txt file should contain the following columns + +- id: the area ids +- inhabitants: inhabitants per area +- type: area type + +Possible area types are + +- recreational +- shopping_business +- university +- residential +- mixed + +Output +------ + +The demonstrator returns two txt files as output. + +The text file location_mapping.txt contains a table that maps the input area ids to the corresponding abm location ids. +The abm location ids have the form xxyyy where the first two digits xx specify the location's type and the following number yyy +specifies the location's index which is a consecutive number for all locations. +The digits xx for the location types can be the following + +- 00 - Home +- 01 - School +- 02 - Work +- 03 - SocialEvent +- 04 - BasicsShop +- 05 - Hospital +- 06 - ICU +- 10 - Cemetery + +It should be remarked that there is no one-to-one mapping from the input areas to the abm locations and one input area can be mapped to multiple abm locations. + +The text file output.txt contains an agent level output for every location and time point. One row represents one location and the table has the following columns + +- LocationId +- Number of output timesteps (tn) +- Timestep 1 (t1) +- Number of agents at location at t1 (am-t1) +- Agent id 1 (a1-t1) +- Time since transmission for a1-t1 +- Agent id 2 (a2-t1) +- Time since transmission for a2-t1 +- ... +- Agent id m (am-t1) +- Time since transmission for am-t1 +- Timestep 2 (t2) +- Number of agents at location at t2 (am-t2) +- Agent id 1 (a1-t2) +- Time since transmission for a1-t2 +- Agent id 2 (a2-t2) +- Time since transmission for a2-t2 +- ... +- Agent id m (am-t2) +- Time since transmission for am-t2 +- ... +- Timestep n (tn) +- Number of agents at location at tn (am-tn) +- Agent id 1 (a1-tn) +- Time since transmission for a1-tn +- Agent id 2 (a2-tn) +- Time since transmission for a2-tn +- ... +- Agent id m (am-tn) +- Time since transmission for am-tn + +The time since transmission as well as the timesteps are given in hours. If an agent is susceptible, recovered or dead, his time since transmission is set to -1. + +Additionally to the txt output, there is a console output. The console output is a table with the number of agents per timestep and infection state. +The timesteps are given in days here instead of hours. The infection states are + +- Susceptible +- Exposed +- InfectedNoSymptoms (Carrier) +- InfectedSymptoms +- InfectedSevere +- InfectedCritical +- Recovered +- Dead diff --git a/pycode/examples/simulation/ABM Demonstrator/README_Demonstrator_Munich.rst b/pycode/examples/simulation/ABM Demonstrator/README_Demonstrator_Munich.rst new file mode 100644 index 0000000000..22106150fb --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/README_Demonstrator_Munich.rst @@ -0,0 +1,90 @@ +MEmilio INSIDe Demonstrator for Munich +======================================= + +Input +------ + +Inputs for the Munich demonstrator are: + +- person.csv: A file containing all persons that should be simulated. The file needs to have the following columns: + * puid: PersonId + * age: Age of the person in years. + * home_zone: Traffic cell/zone of person's Home location. + * home_id: Id of person's Home location. + * shop_zone: Traffic cell/zone of person's Shop location. + * shop_id: Id of person's Shop location. + * event_zone: Traffic cell/zone of person's Event location. + * event_id: Id of person's Event location. + * work_zone: Traffic cell/zone of person's Work location. Should be set to -2 for non-work-aged persons. + * work_id: Id of person's Work location. Should be set to -2 for non-work-aged persons. + * school_zone: Traffic cell/zone of person's School location. Should be set to -2 for non-work-aged persons. + * school_id: Id of person's School location. Should be set to -2 for non-work-aged persons. + +- hospitals.csv : A file containing all hospitals used in the simulation. The file needs to have the following columns: + * hospital_id: Id of the hospital + * hospital_zone: Traffic cell/zone the hospital is in + * beds: Number of beds (capacity) of the hospital + * icu_beds: Number of ICU beds (capacity) of the hospital. Can be 0. + +- parameter_table.csv: A file containing all parameters associated to the transmission model stratified by age group. + +- Verschnitt_DLR_TAN_Rep.shp: Shape file containing the traffic zones and the wastewater areas. The file needs to have the following columns: + * id_n: Traffic zone id + * ID_TAN: Wastewater area id the traffic zone with id id_n is in + +Output +------ + +The demonstrator offers the opportunity to return various files as output. + +Mapping files: + +- {sim_num}_mapping.txt: Contains a table that maps every ABM location to the corresponding traffic cell/zone. + +- {sim_num}_mapping_tan.txt: Contains a table that maps every ABM location to the corresponding wastewater area. + +- {sim_num}_mapping_tan_locs.txt: List of pairs (ABM location, Wastewater area). Is used to set the wastewater area attribute in the Location object during the initialization. + +The ABM locations have the form xxyyy where the first two digits xx specify the location's type and the following number yyy specifies the location's index which is a consecutive number for all locations. +The digits xx for the location types can be the following: + + * 00 - Home + * 01 - School + * 02 - Work + * 03 - SocialEvent + * 04 - BasicsShop + * 05 - Hospital + * 06 - ICU + * 10 - Cemetery + +Cumulative output: + +- {sim_num}_comps.csv: Output is a table with the number of agents per timestep and infection state. +The timesteps are given in hours. The infection states are + +- Susceptible +- Exposed +- InfectedNoSymptoms (Carrier) +- InfectedSymptoms +- InfectedSevere +- InfectedCritical +- Recovered +- Dead + +Agent-based output: + +- {sim_num}_infection_paths.txt: Output is a table with the number of time steps spent in every infection state during the simulation for every agent. + +There is also the option to output various HDF5 files that contain time-resolved information on transmission and location for every agent. The files that can be output are: + +- {sim_num}_output_v1.h5: Has an own HDF5 group per agent. Every group (agent) contains two datasets. The first one holds the ABM Location in the format xxyyy for every time point and the second holds the time since transmission for every time point. The time since transmission is - like the timesteps - given in hours. If an agent is susceptible, recovered or dead, its time since transmission is set to -1. + +- {sim_num}_output_v2.h5: Also has an own HDF5 group per agent. The first dataset is a vector where the even indices are time points and the odd indices are the time since transmission at change points. The first entries are time point and time since transmission at simulation start. If the agent has experienced an infection during the simulation, the following entries are the time point of transmission and recovery/death (if the agent recovered/died during the simulation) and the corresponding time since transmissions. The second dataset is a vector with the time points of location changes and the third dataset holds the corresponding (new) ABM Locations in the format xxyyy. + +- {sim_num}_output_v3.h5: This file only has one HDF5 group 'data' which contains two datasets. The first dataset is a matrix of size #agents x 2 where entry (i, 0) is the time point of transmission and entry (i,1) the time points of recovery or death of agent i. If an agent has not been infected the values are set to 100,000. The second dataset is a matrix of size #agent x #timepoints where entry (i,t) is the ABM Location in the format xxyyy at time point t of agent i. + +- {sim_num}_output_v4.h5: This file only has one HDF5 group 'data' which contains two datasets. The first dataset is a matrix of size #agents x 2 where entry (i, 0) is the time point of transmission and entry (i,1) the time points of recovery or death of agent i. If an agent has not been infected the values are set to 100,000. The second dataset is a matrix of size #agent x #timepoints where entry (i,t) is the wastewater area (integer>0) at time point t of agent i. If an agent was at a location that is not in any wastewater area, the value is set to 0. + +- {sim_num}_output_v5.h5: This file only has one HDF5 group 'data' which contains two datasets. The first dataset is a matrix of size #agents x 2 where entry (i, 0) is the time point of transmission and entry (i,1) the time points of recovery or death of agent i. If an agent has not been infected the values are set to 100,000. The second dataset is a matrix of size #agent x #timepoints where entry (i,t) is the ABM LocationType (as integer) at time point t of agent i + + diff --git a/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator.py b/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator.py new file mode 100644 index 0000000000..42d87c76b7 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator.py @@ -0,0 +1,886 @@ +############################################################################# +# Copyright (C) 2020-2021 German Aerospace Center (DLR-SC) +# +# Authors: Martin J. Kuehn, Wadim Koslow, Daniel Abele +# +# Contact: Martin J. Kuehn +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################# +import argparse +import numpy as np +import os +import sys +import h5py +import time + +import memilio.simulation as mio +from memilio.simulation import abm +from memilio.simulation import AgeGroup +from memilio.simulation.abm import VirusVariant +from memilio.simulation.abm import History +from memilio.simulation.abm import Infection + +import pandas as pd + +# class used to map input areas to abm locations + + +class LocationMapping: + + def __init__(self): + self.inputId = None + self.modelId = [] + + +# number of age groups +num_age_groups = 6 +age_group_0_to_4 = AgeGroup(0) +age_group_5_to_14 = AgeGroup(1) +age_group_15_to_34 = AgeGroup(2) +age_group_35_to_59 = AgeGroup(3) +age_group_60_to_79 = AgeGroup(4) +age_group_80_plus = AgeGroup(5) + + +def age_group_to_string(age_group): + if (age_group == age_group_0_to_4): + return "0" + elif (age_group == age_group_5_to_14): + return "1" + elif (age_group == age_group_15_to_34): + return "2" + elif (age_group == age_group_35_to_59): + return "3" + elif (age_group == age_group_60_to_79): + return "4" + elif (age_group == age_group_80_plus): + return "5" + else: + return "AgeGroup not found" + + +def set_infection_parameters(parameters): + + infection_params = abm.Parameters(num_age_groups) + + # AgeGroup 0-4 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_0_to_4, parameters.loc["Age0to4_IncubationPeriod"].value, parameters.loc["Age0to4_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age0to4_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age0to4_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedSymptomsToRecovered"].value, parameters.loc["Age0to4_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedSymptomsToSevere"].value, parameters.loc["Age0to4_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_SevereToRecovered"].value, parameters.loc["Age0to4_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_SevereToCritical"].value, parameters.loc["Age0to4_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_CriticalToRecovered"].value, parameters.loc["Age0to4_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_CriticalToDead"].value, parameters.loc["Age0to4_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_DeathsPerInfectedCritical"].value + + # AgeGroup 5-14 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_5_to_14, parameters.loc["Age5to14_IncubationPeriod"].value, parameters.loc["Age5to14_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age5to14_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age5to14_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_InfectedSymptomsToRecovered"].value, parameters.loc["Age5to14_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_InfectedSymptomsToSevere"].value, parameters.loc["Age5to14_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_SevereToRecovered"].value, parameters.loc["Age5to14_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_SevereToCritical"].value, parameters.loc["Age5to14_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_CriticalToRecovered"].value, parameters.loc["Age5to14_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["Age5to14_CriticalToDead"].value, parameters.loc["Age5to14_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_5_to_14, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_5_to_14] = parameters.loc["Age5to14_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_5_to_14] = parameters.loc["Age5to14_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_5_to_14] = parameters.loc["Age5to14_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_5_to_14] = parameters.loc["Age5to14_DeathsPerInfectedCritical"].value + + # AgeGroup 15-34 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_15_to_34, parameters.loc["Age15to34_IncubationPeriod"].value, parameters.loc["Age15to34_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age15to34_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age15to34_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_InfectedSymptomsToRecovered"].value, parameters.loc["Age15to34_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_InfectedSymptomsToSevere"].value, parameters.loc["Age15to34_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_SevereToRecovered"].value, parameters.loc["Age15to34_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_SevereToCritical"].value, parameters.loc["Age15to34_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_CriticalToRecovered"].value, parameters.loc["Age15to34_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["Age15to34_CriticalToDead"].value, parameters.loc["Age15to34_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_15_to_34, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_15_to_34] = parameters.loc["Age15to34_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_15_to_34] = parameters.loc["Age15to34_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_15_to_34] = parameters.loc["Age15to34_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_15_to_34] = parameters.loc["Age15to34_DeathsPerInfectedCritical"].value + + # AgeGroup 35-59 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_35_to_59, parameters.loc["Age35to59_IncubationPeriod"].value, parameters.loc["Age35to59_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age35to59_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age35to59_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedSymptomsToRecovered"].value, parameters.loc["Age35to59_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedSymptomsToSevere"].value, parameters.loc["Age35to59_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_SevereToRecovered"].value, parameters.loc["Age35to59_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_SevereToCritical"].value, parameters.loc["Age35to59_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_CriticalToRecovered"].value, parameters.loc["Age35to59_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_CriticalToDead"].value, parameters.loc["Age35to59_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_DeathsPerInfectedCritical"].value + + # AgeGroup 60-79 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_60_to_79, parameters.loc["Age60to79_IncubationPeriod"].value, parameters.loc["Age60to79_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age60to79_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age60to79_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedSymptomsToRecovered"].value, parameters.loc["Age60to79_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedSymptomsToSevere"].value, parameters.loc["Age60to79_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_SevereToRecovered"].value, parameters.loc["Age60to79_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_SevereToCritical"].value, parameters.loc["Age60to79_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_CriticalToRecovered"].value, parameters.loc["Age60to79_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_CriticalToDead"].value, parameters.loc["Age60to79_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_DeathsPerInfectedCritical"].value + + # AgeGroup 80+ + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_80_plus, parameters.loc["Age80plus_IncubationPeriod"].value, parameters.loc["Age80plus_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age80plus_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age80plus_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedSymptomsToRecovered"].value, parameters.loc["Age80plus_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedSymptomsToSevere"].value, parameters.loc["Age80plus_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_SevereToRecovered"].value, parameters.loc["Age80plus_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_SevereToCritical"].value, parameters.loc["Age80plus_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_CriticalToRecovered"].value, parameters.loc["Age80plus_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_CriticalToDead"].value, parameters.loc["Age80plus_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_DeathsPerInfectedCritical"].value + + return infection_params + + +def read_txt(path): + # read input file and save it in a pd.Dataframe + return pd.read_csv(path, sep='\t') + + +def make_one_person_households(number_of_households): + # one-person household member + one_person_household_member = abm.HouseholdMember(num_age_groups) + # set weights for household member + one_person_household_member.set_age_weight(age_group_15_to_34, 4364) + one_person_household_member.set_age_weight(age_group_35_to_59, 7283) + one_person_household_member.set_age_weight(age_group_60_to_79, 4100) + one_person_household_member.set_age_weight(age_group_80_plus, 1800) + + # create one-person household group + household_group = abm.HouseholdGroup() + for hh in range(int(number_of_households)): + household = abm.Household() + household.add_members(one_person_household_member, 1) + household_group.add_households(household, 1) + + return household_group + + +def make_multiple_person_households(household_size, + number_of_two_parent_households, number_of_one_parent_households, + number_of_other_households): + + # members for multiple person households + child = abm.HouseholdMember(num_age_groups) + child.set_age_weight(age_group_0_to_4, 1) + child.set_age_weight(age_group_5_to_14, 1) + + parent = abm.HouseholdMember(num_age_groups) + parent.set_age_weight(age_group_15_to_34, 2) + parent.set_age_weight(age_group_35_to_59, 2) + parent.set_age_weight(age_group_60_to_79, 1) + + other = abm.HouseholdMember(num_age_groups) + other.set_age_weight(age_group_0_to_4, 5000) + other.set_age_weight(age_group_5_to_14, 6000) + other.set_age_weight(age_group_15_to_34, 14943) + other.set_age_weight(age_group_35_to_59, 22259) + other.set_age_weight(age_group_60_to_79, 11998) + other.set_age_weight(age_group_80_plus, 5038) + + household_group = abm.HouseholdGroup() + # add two parent households + hh_two_parents = abm.Household() + hh_two_parents.add_members(child, household_size - 2) + hh_two_parents.add_members(parent, 2) + household_group.add_households( + hh_two_parents, number_of_two_parent_households) + # add one parent households + hh_one_parent = abm.Household() + hh_one_parent.add_members(child, household_size - 1) + hh_one_parent.add_members(parent, 1) + household_group.add_households( + hh_one_parent, number_of_one_parent_households) + # add other households + hh_other = abm.Household() + hh_other.add_members(parent, 1) + hh_other.add_members(other, household_size - 1) + household_group.add_households(hh_other, number_of_other_households) + + return household_group + + +def add_households(model, distribution, num_inhabitants): + household_sizes = np.zeros(len(distribution)) + locationIds = [] + new_index = len(model.locations) + # distribute inhabintants to households + while num_inhabitants > 0: + size = np.random.choice( + np.arange(0, len(distribution)), p=distribution) + household_sizes[size] += 1 + num_inhabitants -= (size + 1) + + # one-person household + one_person_household_group = make_one_person_households(household_sizes[0]) + abm.add_household_group_to_model(model, one_person_household_group) + + # two-person households + two_person_two_parents = int(0.85 * household_sizes[1]) + two_person_one_parent = int(0.13 * household_sizes[1]) + two_person_other = int( + household_sizes[1] - two_person_two_parents - two_person_one_parent) + two_person_household_group = make_multiple_person_households(2, two_person_two_parents, + two_person_one_parent, two_person_other) + abm.add_household_group_to_model(model, two_person_household_group) + + # three-person households + three_person_two_parents = int(0.83 * household_sizes[2]) + three_person_one_parent = int(0.13 * household_sizes[2]) + three_person_other = int( + household_sizes[2] - three_person_two_parents - three_person_one_parent) + three_person_household_group = make_multiple_person_households(3, three_person_two_parents, + three_person_one_parent, three_person_other) + abm.add_household_group_to_model(model, three_person_household_group) + + # four-person households + four_person_two_parents = int(0.93 * household_sizes[3]) + four_person_one_parent = int(0.03 * household_sizes[3]) + four_person_other = int( + household_sizes[3] - four_person_two_parents - four_person_one_parent) + four_person_household_group = make_multiple_person_households(4, four_person_two_parents, + four_person_one_parent, four_person_other) + abm.add_household_group_to_model(model, four_person_household_group) + + # five-person households + five_person_two_parents = int(0.88 * household_sizes[4]) + five_person_one_parent = int(0.05 * household_sizes[4]) + five_person_other = int( + household_sizes[4] - five_person_two_parents - five_person_one_parent) + five_person_household_group = make_multiple_person_households(5, five_person_two_parents, + five_person_one_parent, five_person_other) + abm.add_household_group_to_model(model, five_person_household_group) + + for hh in range(int(sum(household_sizes))): + locationIds.append( + (mio.abm.LocationId(new_index), abm.LocationType.Home)) + new_index += 1 + return locationIds + + +def insert_locations_to_map(mapping, inputId, locations): + map = LocationMapping() + map.inputId = inputId + for loc in locations: + type = str(int(loc[1])) + index = str(loc[0].index()) + if int(loc[1]) < 10: + type = "0" + type + if loc[0].index() < 10: + index = "0" + index + map.modelId.append(type+index) + mapping.append(map) + return mapping + + +def create_locations_from_input(model, input_areas, household_distribution): + # map input area ids to corresponding abm location ids + mapping = [] + # bools to make sure the model has a school and a hospital + has_school = False + has_hospital = False + for index, area in input_areas.iterrows(): + locations = [] + if ('residential' in area.type): + # area 'residential' corresponds to location type 'Home' + locations = add_households( + model, household_distribution, area.inhabitants) + elif (area.type == 'recreational'): + # area 'recreational' corresponds to location type 'SocialEvent' + location = model.add_location(abm.LocationType.SocialEvent) + # set maximum contacts and capacity for social events + model.get_location( + location).infection_parameters.MaximumContacts = 30. + model.get_location(location).set_capacity(30, 40, 0) + locations.append((model.get_location(location).id, + model.get_location(location).type)) + elif (area.type == 'shopping_business'): + # area 'shopping_business' corresponds to location type School, Hospital, BasicsShops, Work + if (not has_school): + # if model does not have a school yet, a school is added + location = model.add_location(abm.LocationType.School) + # set maximum contacts and capacity for school + model.get_location( + location).infection_parameters.MaximumContacts = 40. + model.get_location( + location).set_capacity(500, 2000, 0) + locations.append( + (model.get_location(location).id, model.get_location(location).type)) + has_school = True + elif (not has_hospital): + # if model does not have a hospital yet, a hospital and a icu is added + locHosp = model.add_location(abm.LocationType.Hospital) + # set maximum contacts and capacity for hospital + model.get_location( + locHosp).infection_parameters.MaximumContacts = 5. + model.get_location( + locHosp).set_capacity(300, 10000, 0) + locICU = model.add_location(abm.LocationType.ICU) + # set maximum contacts and capacity for icu + model.get_location( + locICU).infection_parameters.MaximumContacts = 5. + model.get_location( + locICU).set_capacity(30, 1000, 0) + locations.append( + (model.get_location(locHosp).id, model.get_location(locHosp).type)) + locations.append((model.get_location(locICU).id, + model.get_location(locICU).type)) + has_hospital = True + else: + # when hospital and school has been added, the area 'shopping_business' is either + # transformed to location type BasicsShop or location type Work with same probability + type = np.random.choice(np.arange(0, 2), p=[0.5, 0.5]) + if (type): + location = model.add_location(abm.LocationType.BasicsShop) + # set maximum contacts and capacity for basics shops + model.get_location( + location).infection_parameters.MaximumContacts = 20. + model.get_location( + location).set_capacity(100, 1000, 0) + locations.append( + (model.get_location(location).id, model.get_location(location).type)) + else: + location = model.add_location(abm.LocationType.Work) + # set maximum contacts and capacity for work + model.get_location( + location).infection_parameters.MaximumContacts = 40. + model.get_location( + location).set_capacity(300, 2000, 0) + locations.append( + (model.get_location(location).id, model.get_location(location).type)) + elif (area.type == 'university'): + # area 'university' corresponds to location type 'Work' + location = model.add_location(abm.LocationType.Work) + # set maximum contacts and capacity for work + model.get_location( + location).infection_parameters.MaximumContacts = 50. + model.get_location( + location).set_capacity(200, 4000, 0) + locations.append((model.get_location(location).id, + model.get_location(location).type)) + elif (area.type == 'mixed'): + # area 'mixed' corresponds either to location type 'Work' or location type 'Home' with same probability + type = np.random.choice(np.arange(0, 2), p=[0.5, 0.5]) + if (type): + location = model.add_location(abm.LocationType.Work) + # set maximum contacts and capacity for work + model.get_location( + location).infection_parameters.MaximumContacts = 40. + model.get_location( + location).set_capacity(100, 2000, 0) + locations.append( + (model.get_location(location).id, model.get_location(location).type)) + else: + locations = add_households( + model, household_distribution, area.inhabitants) + insert_locations_to_map(mapping, str(area.id), locations) + return mapping + + +def assign_infection_states(model, t0, exposed_pct, infected_no_symptoms_pct, infected_symptoms_pct, + infected_severe_pct, infected_critical_pct, recovered_pct): + susceptible_pct = 1 - exposed_pct - infected_no_symptoms_pct - \ + infected_symptoms_pct - infected_severe_pct - \ + infected_critical_pct - recovered_pct + for person in model.persons: + # draw infection state from distribution for every agent + infection_state = np.random.choice(np.arange(0, int(abm.InfectionState.Count)), + p=[susceptible_pct, exposed_pct, infected_no_symptoms_pct, + infected_symptoms_pct, infected_severe_pct, infected_critical_pct, recovered_pct, 0.0]) + if (abm.InfectionState(infection_state) != abm.InfectionState.Susceptible): + person.add_new_infection(Infection( + model, person, VirusVariant.Wildtype, t0, abm.InfectionState(infection_state), False), t0) + + +def find_all_locations_of_type(model, type): + locations = [] + for loc in model.locations: + if (loc.type == type): + locations.append(loc.id) + return locations + + +def assign_locations(model): + # get locations from model + schools = find_all_locations_of_type(model, abm.LocationType.School) + school_weights = [(1/len(schools)) for i in range(len(schools))] + hospitals = find_all_locations_of_type(model, abm.LocationType.Hospital) + hospital_weights = [(1/len(hospitals)) for i in range(len(hospitals))] + icus = find_all_locations_of_type(model, abm.LocationType.ICU) + icu_weights = [(1/len(icus)) for i in range(len(icus))] + workplaces = find_all_locations_of_type(model, abm.LocationType.Work) + workplace_weights = [(1/len(workplaces)) for i in range(len(workplaces))] + basic_shops = find_all_locations_of_type( + model, abm.LocationType.BasicsShop) + shop_weights = [(1/len(basic_shops)) for i in range(len(basic_shops))] + social_events = find_all_locations_of_type( + model, abm.LocationType.SocialEvent) + event_weights = [(1/len(social_events)) for i in range(len(social_events))] + + # assign locations to agents + for person in model.persons: + shop = np.random.choice(np.arange(0, len(basic_shops)), p=shop_weights) + person.set_assigned_location(model.get_location(basic_shops[int(shop)]).type, + basic_shops[int(shop)]) + + hospital = np.random.choice( + np.arange(0, len(hospitals)), p=hospital_weights) + person.set_assigned_location(model.get_location(hospitals[int(hospital)]).type, + hospitals[int(hospital)]) + + icu = np.random.choice(np.arange(0, len(icus)), p=icu_weights) + person.set_assigned_location(model.get_location(icus[int(icu)]).type, + icus[int(icu)]) + + event = np.random.choice( + np.arange(0, len(social_events)), p=event_weights) + person.set_assigned_location(model.get_location(social_events[int(event)]).type, + social_events[int(event)]) + + # assign school to agents between 5 and 14 years + if (person.age == age_group_5_to_14): + school = np.random.choice( + np.arange(0, len(schools)), p=school_weights) + person.set_assigned_location(model.get_location(schools[int(school)]).type, + schools[int(school)]) + # assign work to agents between 15 and 59 + if (person.age == age_group_15_to_34 or person.age == age_group_35_to_59): + work = np.random.choice( + np.arange(0, len(workplaces)), p=workplace_weights) + person.set_assigned_location(model.get_location(workplaces[int(work)]).type, + workplaces[int(work)]) + + +def convert_loc_id_to_string(loc): + type = str(int(loc[1])) + index = str(loc[0].index()) + if int(loc[1]) < 10: + type = "0" + type + if int(loc[0].index()) < 10: + index = "0" + index + + return type + index + + +def get_agents_per_location(loc_id, agents): + agents_per_loc = [] + for a in agents: + if (int(a[1]) == int(loc_id[1]) and a[0].index() == loc_id[0].index()): + agents_per_loc.append(a) + return agents_per_loc + + +def write_results_to_file(path, log): + location_ids = log[1][0] + time_points = log[0] + agents = log[2] + with open(path, 'w') as f: + for location_id_index in range(len(location_ids)): + line = convert_loc_id_to_string( + location_ids[location_id_index]) + " " + str(len(time_points)) + for t in range(len(time_points)): + agents_per_loc = get_agents_per_location( + location_ids[location_id_index], agents[t]) + line += " " + str(time_points[t]) + \ + " " + str(len(agents_per_loc)) + for a in agents_per_loc: + if (a[3].days > 1000): + time_since_transmission = -1.0 + else: + time_since_transmission = a[3].hours + line += " " + str(a[2].index()) + " " + \ + str(time_since_transmission) + f.write(line) + f.write('\n') + f.close() + + +def convert_infection_state_to_string(infection_state): + if (infection_state == abm.InfectionState.Susceptible): + return "S" + elif (infection_state == abm.InfectionState.Exposed): + return "E" + elif (infection_state == abm.InfectionState.InfectedNoSymptoms): + return "I_ns" + elif (infection_state == abm.InfectionState.InfectedSymptoms): + return "I_sy" + elif (infection_state == abm.InfectionState.InfectedSevere): + return "I_sev" + elif (infection_state == abm.InfectionState.InfectedCritical): + return "I_cri" + elif (infection_state == abm.InfectionState.Recovered): + return "R" + elif (infection_state == abm.InfectionState.Dead): + return "D" + else: + raise Exception("Infection state not found") + + +def write_infection_paths_to_file_states(path, log): + agent_ids = [log[2][0][i][1] for i in range(len(log[2][0]))] + with open(path, 'w') as f: + for id in agent_ids: + line = str(id) + " " + for t in range(len(log[2])): + line += convert_infection_state_to_string( + log[2][t][id][3]) + " " + f.write(line) + f.write('\n') + f.close() + + +def write_infection_paths_to_file(path, model, tmax): + with open(path, 'w') as f: + f.write("Agent_id S E I_ns I_sy I_sev I_cri R D\n") + for person in model.persons: + line = str(int(person.id.index())) + " " + if person.infection_state(tmax) == abm.InfectionState.Susceptible: + line += str(int(tmax.hours)) + " " + for i in range(int(abm.InfectionState.Count)-1): + line += "0 " + else: + time_S = max( + person.infection.get_infection_start() - abm.TimePoint(0), abm.TimeSpan(0)) + time_E = person.infection.get_time_in_state( + abm.InfectionState.Exposed) + time_INS = person.infection.get_time_in_state( + abm.InfectionState.InfectedNoSymptoms) + time_ISy = person.infection.get_time_in_state( + abm.InfectionState.InfectedSymptoms) + time_ISev = person.infection.get_time_in_state( + abm.InfectionState.InfectedSevere) + time_ICri = person.infection.get_time_in_state( + abm.InfectionState.InfectedCritical) + time_R = abm.TimePoint(0) + time_D = abm.TimePoint(0) + if (person.infection_state(tmax) == abm.InfectionState.Recovered): + time_infected = time_E + time_INS + time_ISy + time_ISev + time_ICri + if (time_S.hours == 0): + time_R = tmax - \ + (time_infected + + (person.infection.get_infection_start() - abm.TimePoint(0))) + else: + time_R = tmax - time_S - time_infected + if (person.infection_state(tmax) == abm.InfectionState.Dead): + time_infected = time_E + time_INS + time_ISy + time_ISev + time_ICri + if (time_S.hours == 0): + time_D = tmax - \ + (time_infected + + (person.infection.get_infection_start() - abm.TimePoint(0))) + else: + time_D = tmax - time_S - time_infected + line += str(time_S.hours) + " " + str(time_E.hours) + " " + str(time_INS.hours) + " " + str(time_ISy.hours) + " " \ + + str(time_ISev.hours) + " " + str(time_ICri.hours) + \ + " " + str(time_R.hours) + " " + \ + str(time_D.hours) + " " + f.write(line) + f.write('\n') + f.close() + + +def write_location_mapping_to_file(path, mapping): + with open(path, 'w') as f: + for id in mapping: + line = id.inputId + " " + for modelId in id.modelId: + line += modelId + " " + f.write(line) + f.write('\n') + f.close() + + +def set_sim_result_at_start(sim): + for location in sim.model.locations: + result = sim.result.get_last_value() + result += location.population.get_last_value() + + +def write_age_and_hh(model, path): + with open(path, 'w') as f: + for person in model.persons: + line = str(person.id) + " " + age_group_to_string(person.age) + " " + \ + str(person.assigned_location(abm.LocationType.Home)) + f.write(line) + f.write('\n') + f.close() + + +def write_compartments_to_file(model, path, timepoints): + with open(path, 'w') as f: + f.write("t S E Ins Isy Isev Icri R D\n") + for t in range(len(timepoints)): + tp = abm.TimePoint(0) + abm.hours(t) + line = str(timepoints[t]) + " " + comps = np.zeros(int(abm.InfectionState.Count)) + for person in model.persons: + state = person.infection_state(tp) + comps[int(state)] += 1 + for c in comps: + line += str(c) + " " + f.write(line) + f.write('\n') + f.close() + + +def convert_time_since_transmission(time): + if (time.days > 1000): + return -1.0 + else: + return time.hours + + +def write_results_to_h5(path, log): + file = h5py.File(path, 'w') + result_list = [] + for agent in log[3][0]: + gr = file.create_group(str(agent.index())) + loc_ids = np.array([convert_loc_id_to_string((log[2][t][agent.index()][0], log[2][t][agent.index()][1])) + for t in range(len(log[2]))], dtype=np.str_) + time_since_transm = np.array([convert_time_since_transmission( + log[2][t][agent.index()][3]) for t in range(len(log[2]))], dtype=np.float64) + ds = gr.create_dataset('loc_ids', shape=len( + loc_ids), dtype=h5py.string_dtype()) + ds[:] = loc_ids + gr.create_dataset("time_since_transm", + data=time_since_transm, dtype=np.float64) + file.close() + + +def run_abm_simulation(sim_num): + mio.set_log_level(mio.LogLevel.Off) + input_path = sys.path[0] + '/input/' + output_path = sys.path[0] + '/output/' + # set seed for fixed model initialization (locations and initial infection states) + np.random.seed(sim_num) + # starting time point + t0 = abm.TimePoint(0) + # end time point: simulation will run 14 days + tmax = t0 + abm.days(14) + # distribution used to distribute the residential areas to one-, two-, three-, four- and five-person households + household_distribution = [0.4084, 0.3375, 0.1199, 0.0965, 0.0377] + # read txt file with inputs + areas = read_txt(os.path.join( + input_path, 'INSIDe_Demonstrator_AreaList.txt')) + parameters = pd.read_csv(os.path.join( + input_path, 'parameter_table.csv'), index_col=0) + # create simulation with starting timepoint and number of age groups + sim = abm.Simulation(t0, num_age_groups) + # set seeds for simulation + abm.set_seeds(sim.model, sim_num) + # set infection parameters + sim.model.parameters = set_infection_parameters(parameters) + # set age groups that go to school and work + abm.set_AgeGroupGoToSchool(sim.model.parameters, age_group_5_to_14) + abm.set_AgeGroupGoToWork(sim.model.parameters, age_group_15_to_34) + abm.set_AgeGroupGoToWork(sim.model.parameters, age_group_35_to_59) + # as input areas do not fit one-to-one to abm location types, there has to be a mapping + mapping = create_locations_from_input( + sim.model, areas, household_distribution) + # assign initial infection states according to distribution + assign_infection_states(sim.model, t0, 0.002, 0.005, + 0.0029, 0.0001, 0.0, 0.0) + # assign locations to agents + assign_locations(sim.model) + # output object + history = History() + # just used for debugging + # write_age_and_hh(sim.model, os.path.join(output_path, 'age_hh.txt')) + # advance simulation until tmax + sim.advance(tmax, history) + # results collected during the simulation + log = history.log + # write infection paths per agent to file + write_infection_paths_to_file(os.path.join( + output_path, str(sim_num) + '_infection_paths.txt'), sim.model, tmax) + # write compartment size per time step to file + write_compartments_to_file(sim.model, os.path.join( + output_path, str(sim_num) + '_comps.csv'), log[0]) + start = time.time() + write_results_to_h5(os.path.join( + output_path, str(sim_num) + '_output.h5'), log) + end = time.time() + print(f'Time to write output h5: {end - start} seconds') + start = time.time() + # write simulation results to txt file + # write_results_to_file(os.path.join( + # output_path, str(sim_num) + '_output.txt'), log) + # end = time.time() + # print(f'Time to write output txt: {end - start} seconds') + # write location mapping to txt file + write_location_mapping_to_file( + os.path.join(output_path, str(sim_num) + '_location_mapping.txt'), mapping) + + print('done') + + +if __name__ == "__main__": + arg_parser = argparse.ArgumentParser( + 'abm demonstrator', + description='Example demonstrating the agent-based model for a synthetic population.') + args = arg_parser.parse_args() + # set LogLevel + mio.set_log_level(mio.LogLevel.Off) + mio.abm.set_log_level_warn() + for i in range(1, 2): + run_abm_simulation(i, **args.__dict__) diff --git a/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator_munich.py b/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator_munich.py new file mode 100644 index 0000000000..e2d49a00d0 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator_munich.py @@ -0,0 +1,867 @@ +############################################################################# +# Copyright (C) 2020-2021 German Aerospace Center (DLR-SC) +# +# Authors: Martin J. Kuehn, Wadim Koslow, Daniel Abele +# +# Contact: Martin J. Kuehn +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################# +import argparse +import numpy as np +import os +import sys +import h5py +import time +import random +import geopandas +import json + +import memilio.simulation as mio +from memilio.simulation import abm +from memilio.simulation import AgeGroup +from memilio.simulation.abm import VirusVariant +from memilio.simulation.abm import History +from memilio.simulation.abm import Infection + +import pandas as pd + +# number of age groups +num_age_groups = 6 +age_group_0_to_4 = AgeGroup(0) +age_group_5_to_15 = AgeGroup(1) +age_group_16_to_34 = AgeGroup(2) +age_group_35_to_59 = AgeGroup(3) +age_group_60_to_79 = AgeGroup(4) +age_group_80_plus = AgeGroup(5) + + +def set_infection_parameters(parameters): + + infection_params = abm.Parameters(num_age_groups) + + infection_params.InfectionRateFromViralShed[VirusVariant.Wildtype] = 0.1 + + # AgeGroup 0-4 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_0_to_4, parameters.loc["Age0to4_IncubationPeriod"].value, parameters.loc["Age0to4_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age0to4_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age0to4_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedSymptomsToRecovered"].value, parameters.loc["Age0to4_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_InfectedSymptomsToSevere"].value, parameters.loc["Age0to4_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_SevereToRecovered"].value, parameters.loc["Age0to4_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_SevereToCritical"].value, parameters.loc["Age0to4_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_CriticalToRecovered"].value, parameters.loc["Age0to4_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["Age0to4_CriticalToDead"].value, parameters.loc["Age0to4_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_0_to_4, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_0_to_4] = parameters.loc["Age0to4_DeathsPerInfectedCritical"].value + + # AgeGroup 5-14 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_5_to_15, parameters.loc["Age5to14_IncubationPeriod"].value, parameters.loc["Age5to14_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age5to14_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age5to14_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_InfectedSymptomsToRecovered"].value, parameters.loc["Age5to14_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_InfectedSymptomsToSevere"].value, parameters.loc["Age5to14_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_SevereToRecovered"].value, parameters.loc["Age5to14_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_SevereToCritical"].value, parameters.loc["Age5to14_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_CriticalToRecovered"].value, parameters.loc["Age5to14_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["Age5to14_CriticalToDead"].value, parameters.loc["Age5to14_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_5_to_15, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_5_to_15] = parameters.loc["Age5to14_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_5_to_15] = parameters.loc["Age5to14_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_5_to_15] = parameters.loc["Age5to14_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_5_to_15] = parameters.loc["Age5to14_DeathsPerInfectedCritical"].value + + # AgeGroup 15-34 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_16_to_34, parameters.loc["Age15to34_IncubationPeriod"].value, parameters.loc["Age15to34_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age15to34_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age15to34_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_InfectedSymptomsToRecovered"].value, parameters.loc["Age15to34_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_InfectedSymptomsToSevere"].value, parameters.loc["Age15to34_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_SevereToRecovered"].value, parameters.loc["Age15to34_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_SevereToCritical"].value, parameters.loc["Age15to34_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_CriticalToRecovered"].value, parameters.loc["Age15to34_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["Age15to34_CriticalToDead"].value, parameters.loc["Age15to34_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_16_to_34, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_16_to_34] = parameters.loc["Age15to34_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_16_to_34] = parameters.loc["Age15to34_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_16_to_34] = parameters.loc["Age15to34_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_16_to_34] = parameters.loc["Age15to34_DeathsPerInfectedCritical"].value + + # AgeGroup 35-59 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_35_to_59, parameters.loc["Age35to59_IncubationPeriod"].value, parameters.loc["Age35to59_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age35to59_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age35to59_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedSymptomsToRecovered"].value, parameters.loc["Age35to59_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_InfectedSymptomsToSevere"].value, parameters.loc["Age35to59_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_SevereToRecovered"].value, parameters.loc["Age35to59_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_SevereToCritical"].value, parameters.loc["Age35to59_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_CriticalToRecovered"].value, parameters.loc["Age35to59_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["Age35to59_CriticalToDead"].value, parameters.loc["Age35to59_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_35_to_59, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_35_to_59] = parameters.loc["Age35to59_DeathsPerInfectedCritical"].value + + # AgeGroup 60-79 + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_60_to_79, parameters.loc["Age60to79_IncubationPeriod"].value, parameters.loc["Age60to79_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age60to79_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age60to79_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedSymptomsToRecovered"].value, parameters.loc["Age60to79_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_InfectedSymptomsToSevere"].value, parameters.loc["Age60to79_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_SevereToRecovered"].value, parameters.loc["Age60to79_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_SevereToCritical"].value, parameters.loc["Age60to79_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_CriticalToRecovered"].value, parameters.loc["Age60to79_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["Age60to79_CriticalToDead"].value, parameters.loc["Age60to79_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_60_to_79, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_60_to_79] = parameters.loc["Age60to79_DeathsPerInfectedCritical"].value + + # AgeGroup 80+ + abm.set_incubationPeriod( + infection_params, VirusVariant.Wildtype, age_group_80_plus, parameters.loc["Age80plus_IncubationPeriod"].value, parameters.loc["Age80plus_IncubationPeriod"].dev) + abm.set_TimeInfectedNoSymptomsToSymptoms(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedNoSymptomsToSymptoms"].value, parameters.loc["Age80plus_InfectedNoSymptomsToSymptoms"].dev) + abm.set_TimeInfectedNoSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedNoSymptomsToRecovered"].value, parameters.loc["Age80plus_InfectedNoSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedSymptomsToRecovered"].value, parameters.loc["Age80plus_InfectedSymptomsToRecovered"].dev) + abm.set_TimeInfectedSymptomsToSevere(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_InfectedSymptomsToSevere"].value, parameters.loc["Age80plus_InfectedSymptomsToSevere"].dev) + abm.set_TimeInfectedSevereToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_SevereToRecovered"].value, parameters.loc["Age80plus_SevereToRecovered"].dev) + abm.set_TimeInfectedSevereToCritical(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_SevereToCritical"].value, parameters.loc["Age80plus_SevereToCritical"].dev) + abm.set_TimeInfectedCriticalToRecovered(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_CriticalToRecovered"].value, parameters.loc["Age80plus_CriticalToRecovered"].dev) + abm.set_TimeInfectedCriticalToDead(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["Age80plus_CriticalToDead"].value, parameters.loc["Age80plus_CriticalToDead"].dev) + abm.set_viral_load_parameters(infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["peak_max"].value, parameters.loc["peak_max"].value, + parameters.loc["incline"].value, parameters.loc["incline"].value, + parameters.loc["decline"].value, parameters.loc["decline"].value) + abm.set_infectivity_parameters( + infection_params, VirusVariant.Wildtype, age_group_80_plus, + parameters.loc["alpha"].value, parameters.loc["alpha"].value, + parameters.loc["beta"].value, parameters.loc["beta"].value) + infection_params.SymptomaticPerInfectedNoSymptoms[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_SymptomsPerInfectedNoSymptoms"].value + infection_params.SeverePerInfectedSymptoms[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_SeverePerInfectedSymptoms"].value + infection_params.CriticalPerInfectedSevere[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_CriticalPerInfectedSevere"].value + infection_params.DeathsPerInfectedCritical[VirusVariant.Wildtype, + age_group_80_plus] = parameters.loc["Age80plus_DeathsPerInfectedCritical"].value + + return infection_params + + +def get_person_age_string(age): + if age == age_group_0_to_4: + return "Age0to4" + elif age == age_group_5_to_15: + return "Age5to14" + elif age == age_group_16_to_34: + return "Age15to34" + elif age == age_group_35_to_59: + return "Age35to59" + elif age == age_group_60_to_79: + return "Age60to79" + elif age == age_group_80_plus: + return "Age80plus" + else: + print("Error: Age group cannot be found.") + return " " + + +def assign_infection_states(model, t0, exposed_pct, infected_no_symptoms_pct, infected_symptoms_pct, + infected_severe_pct, infected_critical_pct, recovered_pct, parameters, loc_list): + susceptible_pct = 1 - exposed_pct - infected_no_symptoms_pct - \ + infected_symptoms_pct - infected_severe_pct - \ + infected_critical_pct - recovered_pct + for person in model.persons: + # draw infection state from distribution for every agent + if (len(loc_list) > 0): + if (not (int(person.assigned_location(abm.LocationType.Home).index()) in [int(x[2:]) for x in loc_list])): + continue + infection_state = np.random.choice(np.arange(0, int(abm.InfectionState.Count)), + p=[susceptible_pct, exposed_pct, infected_no_symptoms_pct, + infected_symptoms_pct, infected_severe_pct, infected_critical_pct, recovered_pct, 0.0]) + if (abm.InfectionState(infection_state) != abm.InfectionState.Susceptible): + shift = False + shift_rate = 0. + if (abm.InfectionState(infection_state) == abm.InfectionState.Exposed): + shift = True + param_string = get_person_age_string( + person.age) + "_IncubationPeriod" + shift_rate = 1. / \ + (np.exp(parameters.loc[param_string].value + + (parameters.loc[param_string].dev**2)/2.) / 4.) + elif (abm.InfectionState(infection_state) == abm.InfectionState.InfectedNoSymptoms): + shift = True + param_string1 = get_person_age_string( + person.age) + "_InfectedNoSymptomsToSymptoms" + shift_rate1 = 1. / \ + (np.exp(parameters.loc[param_string1].value + + (parameters.loc[param_string1].dev**2)/2.) / 4.) + param_string2 = get_person_age_string( + person.age) + "_InfectedNoSymptomsToRecovered" + shift_rate2 = 1. / \ + (np.exp(parameters.loc[param_string2].value + + (parameters.loc[param_string2].dev**2)/2.) / 4.) + shift_rate = np.minimum(shift_rate1, shift_rate2) + elif (abm.InfectionState(infection_state) == abm.InfectionState.InfectedSymptoms): + shift = True + param_string1 = get_person_age_string( + person.age) + "_InfectedSymptomsToRecovered" + shift_rate1 = 1. / \ + (np.exp(parameters.loc[param_string1].value + + (parameters.loc[param_string1].dev**2)/2.) / 4.) + param_string2 = get_person_age_string( + person.age) + "_InfectedSymptomsToSevere" + shift_rate2 = 1. / \ + (np.exp(parameters.loc[param_string2].value + + (parameters.loc[param_string2].dev**2)/2.) / 4.) + shift_rate = np.minimum(shift_rate1, shift_rate2) + # shift = False + person.add_new_infection(Infection( + model, person, VirusVariant.Wildtype, t0, abm.InfectionState(infection_state), False, shift, shift_rate), t0) + + +def save_persons(trip_file): + start = time.time() + # Map for the locations. The keys are the location types. For every location type we have: + # - a dictionary with traffic zone ids as keys and as value + # - a dictionary with huid/location_id from trip data as key and the ABM LocationId as value + location_map = {0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}} + + # Load the data + trip_df = pd.read_csv(trip_file) + + # Preprocess data to avoid repeated filtering + # To create a person, the puid, the home location and a person's age is needed + pids_df = trip_df[['puid', 'huid', 'age', + 'home_in_munich']].drop_duplicates() + # Get the number of trips per location for each person sorted by traffic zone and location type + locs = trip_df.groupby(['puid', 'location_type', 'end_zone'])[ + 'loc_id_end'].value_counts().reset_index(name='count') + # Get all shops in the df + all_shops = trip_df[trip_df['location_type'] + == 4][['end_zone', 'loc_id_end']].drop_duplicates() + # Get all events in the df + all_events = trip_df[trip_df['location_type'] + == 3][['end_zone', 'loc_id_end']].drop_duplicates() + # Get all work places in the df + all_works = trip_df[trip_df['location_type'] + == 2][['end_zone', 'loc_id_end']].drop_duplicates() + # Get all schools in the df + all_schools = trip_df[trip_df['location_type'] + == 1][['end_zone', 'loc_id_end']].drop_duplicates() + + # Create fast lookup dictionaries for home zones + # i.e. creates dictionary with loc_id as key and traffic zone id as value + start_zone_lookup = trip_df.set_index( + 'loc_id_start')['start_zone'].to_dict() + end_zone_lookup = trip_df.set_index('loc_id_end')['end_zone'].to_dict() + persons = [] + i = 1 + for index, row in pids_df.iterrows(): + print(i, '/', len(pids_df)) + i += 1 + if row['home_in_munich'] == 0: + continue + home_id = row['huid'] + age = row['age'] + # Find home zone + home_zone = start_zone_lookup.get( + row['huid']) or end_zone_lookup.get(row['huid']) + if home_zone is None: + print("Error: Home zone was not found") + continue + if (int(home_zone / 10000) != 9162): + continue + + # Ensure home_zone is in integer format + # If it's a series take the first entry + if isinstance(home_zone, pd.Series): + home_zone = home_zone.iloc[0] + + ### Assign shop ### + # Get all shops the person visits sorted by number of trips to that shop + shops = locs[(locs['puid'] == row['puid']) & ( + locs['location_type'] == 4)].sort_values(by='count', ascending=False) + + if shops.empty: + shop_zone = home_zone + # No trips to shops + # Assign a shop in home zone + if home_zone in location_map[4]: + # Choose shop uniquely distributed from all shops in home zone + shop_id = random.choice( + list(location_map[4][home_zone].keys())) + else: + # Get all shops in home zone + shops_in_hz = all_shops[all_shops['end_zone'] == home_zone] + if not shops_in_hz.empty: + # Shop in home zone exists + shop_id = random.choice(list(shops_in_hz['loc_id_end'])) + location_map[4][home_zone] = {shop_id: shop_id} + else: + print('Info: No shop in home zone was found in the data.') + shop_id = -1 + else: + # Use the person's most frequently visited shop + shop_zone = shops.iloc[0]['end_zone'] + shop_id = shops.iloc[0]['loc_id_end'] + if shop_zone in location_map[4]: + # Traffic zone of most frequently visited shop already exists in location map + shop = location_map[4][shop_zone].get(shop_id) + if shop is None: + # Shop id is not in the location map/model + location_map[4][shop_zone][shop_id] = shop_id + else: + # Shop zone (and therefore also shop id) is not in the location map/model + location_map[4][shop_zone] = {shop_id: shop_id} + + ### Assign event ### + # Get all events the person visits sorted by number of trips to that event + events = locs[(locs['puid'] == row['puid']) & ( + locs['location_type'] == 3)].sort_values(by='count', ascending=False) + + if events.empty: + event_zone = home_zone + # No trips to events + # Assign an event in home zone + if home_zone in location_map[3]: + # Choose event uniquely distributed from all events in home zone + event_id = random.choice( + list(location_map[3][home_zone].keys())) + else: + # Get all events in home zone + events_in_hz = all_events[all_events['end_zone'] == home_zone] + if not events_in_hz.empty: + # Event in home zone exists + event_id = random.choice(list(events_in_hz['loc_id_end'])) + location_map[3][home_zone] = {event_id: event_id} + else: + print('Info: No event in home zone was found in the data.') + event_id = -1 + else: + # Use the person's most frequently visited event + event_zone = events.iloc[0]['end_zone'] + event_id = events.iloc[0]['loc_id_end'] + if event_zone in location_map[3]: + # Traffic zone of most frequently visited event already exists in location map + event = location_map[3][event_zone].get(event_id) + if event is None: + # Event id is not in the location map/model + location_map[3][event_zone][event_id] = event_id + else: + # Event zone (and therefore also event id) is not in the location map/model + location_map[3][event_zone] = {event_id: event_id} + + work_zone = -2 + work_id = -2 + ### Assign work to ages 15 to 59### + if (row['age'] > 15 and row['age'] < 60): + # Get all work places the person visits sorted by number of trips to that work place + works = locs[(locs['puid'] == row['puid']) & ( + locs['location_type'] == 2)].sort_values(by='count', ascending=False) + + if works.empty: + work_zone = home_zone + # No trips to work places + # Assign a work place in home zone + if home_zone in location_map[2]: + # Choose work place uniquely distributed from all work places in home zone + work_id = random.choice( + list(location_map[2][home_zone].keys())) + else: + # Get all work places in home zone + works_in_hz = all_works[all_works['end_zone'] == home_zone] + if not works_in_hz.empty: + # Work place in home zone exists + work_id = random.choice( + list(works_in_hz['loc_id_end'])) + location_map[2][home_zone] = {work_id: work_id} + else: + print( + 'Info: No work place in home zone was found in the data.') + work_id = -1 + else: + # Use the person's most frequently visited work place + work_zone = works.iloc[0]['end_zone'] + work_id = works.iloc[0]['loc_id_end'] + if work_zone in location_map[2]: + # Traffic zone of most frequently visited work place already exists in location map + work = location_map[2][work_zone].get(work_id) + if work is None: + # Work id is not in the location map/model + location_map[2][work_zone][work_id] = work_id + else: + # Work zone (and therefore also work id) is not in the location map/model + location_map[2][work_zone] = {work_id: work_id} + + school_zone = -2 + school_id = -2 + ### Assign school to ages 5 to 14### + if (row['age'] > 4 and row['age'] < 16): + # Get all schools the person visits sorted by number of trips to that school + schools = locs[(locs['puid'] == row['puid']) & ( + locs['location_type'] == 1)].sort_values(by='count', ascending=False) + + if schools.empty: + school_zone = home_zone + # No trips to schools + # Assign a school in home zone + if home_zone in location_map[1]: + # Choose school uniquely distributed from all schools in home zone + school_id = random.choice( + list(location_map[1][home_zone].keys())) + else: + # Get all schools in home zone + schools_in_hz = all_schools[all_schools['end_zone'] + == home_zone] + if not schools_in_hz.empty: + # School in home zone exists + school_id = random.choice( + list(schools_in_hz['loc_id_end'])) + location_map[1][home_zone] = {school_id: school_id} + else: + print( + 'Info: No school in home zone was found in the data.') + school_id = -1 + else: + # Use the person's most frequently visited school + school_zone = schools.iloc[0]['end_zone'] + school_id = schools.iloc[0]['loc_id_end'] + if school_zone in location_map[1]: + # Traffic zone of most frequently visited school already exists in location map + school = location_map[1][school_zone].get(school_id) + if school is None: + # School id is not in the location map/model + location_map[1][school_zone][school_id] = school_id + else: + # School zone (and therefore also school id) is not in the location map/model + location_map[1][school_zone] = {school_id: school_id} + persons.append({'puid': row['puid'], 'age': age, 'home_zone': home_zone, 'home_id': home_id, 'shop_zone': shop_zone, 'shop_id': shop_id, + 'event_zone': event_zone, 'event_id': event_id, 'work_zone': work_zone, 'work_id': work_id, + 'school_zone': school_zone, 'school_id': school_id}) + end = time.time() + print(f'Time to create person list: {end - start} seconds') + print("Number of persons in model: ", len(persons)) + start = time.time() + df = pd.DataFrame(persons) + df.to_csv('persons.csv') + end = time.time() + print(f'Time to create and save person df: {end - start} seconds') + + +def map_traffic_cell_to_wastewater_area(mapping_path, wastewater_path, new_file, new_file2): + random.seed(30) + with open(mapping_path) as f: + d = dict(x.rstrip().split(None, 1) for x in f) + areas = geopandas.read_file(wastewater_path) + new_dict = {} + new_dict2 = {} + for traffic_cell_id in d.keys(): + if traffic_cell_id[:4] != '9162': + for loc in d[traffic_cell_id].split(' '): + new_key = "0" + if loc in new_dict2: + print('Problem: Location is assigned to two traffic cells') + new_dict2[loc] = new_key + continue + # new_key = 'x' + traffic_cell_id + # new_dict[new_key] = d[traffic_cell_id].split(' ') + else: + Id_tan = np.unique( + areas[areas['id_n'] == int(traffic_cell_id)]['ID_TAN']) + for loc in d[traffic_cell_id].split(' '): + new_key = str(random.choice(Id_tan)) + if (new_key in new_dict): + new_dict[new_key].append(loc) + else: + new_dict[new_key] = [loc] + if loc in new_dict2: + print('Problem: Location is assigned to two traffic cells') + new_dict2[loc] = new_key + # save mapping from locs to TAN IDs + with open(new_file, 'w') as f: + for id in new_dict: + line = id + " " + for loc in new_dict[id]: + line += loc + " " + f.write(line) + f.write('\n') + f.close() + # save mapping from TAN IDs to locs + with open(new_file2, 'w') as f: + for id in new_dict2: + line = id + " " + line += new_dict2[id] + " " + f.write(line) + f.write('\n') + f.close() + return new_dict + + +def num_locations(model): + num_home = 0 + num_work = 0 + num_school = 0 + num_event = 0 + num_hosp = 0 + num_icu = 0 + num_shop = 0 + for loc in model.locations: + if loc.type == abm.LocationType.Home: + num_home += 1 + elif loc.type == abm.LocationType.Work: + num_work += 1 + elif loc.type == abm.LocationType.School: + num_school += 1 + elif loc.type == abm.LocationType.SocialEvent: + num_event += 1 + elif loc.type == abm.LocationType.BasicsShop: + num_shop += 1 + elif loc.type == abm.LocationType.Hospital: + num_hosp += 1 + elif loc.type == abm.LocationType.ICU: + num_icu += 1 + else: + print('error') + print('') + + +def create_home_mapping(map): + for key, loc_list in map.items(): + map[key] = [loc for loc in loc_list if loc[:2] == "00"] + return map + + +def write_time_to_file(sim_nums, init_times, sim_times, output_times, file): + with open(file, 'w') as f: + line = "Sim_num,init,simulation,output" + f.write(line) + f.write('\n') + for i in range(len(sim_nums)): + line = f"{sim_nums[i]},{init_times[i]},{sim_times[i]},{output_times[i]}" + f.write(line) + f.write('\n') + f.close() + + +def run_abm_simulation(sim_num): + input_path = sys.path[0] + '/input/' + output_path = sys.path[0] + '/output/' + local_outbreak = False + max_work_size = 40 + max_school_size = 45 + # Timing for initialization, simulation and output writing + total_init_time = 0.0 + total_simulation_time = 0.0 + total_output_time = 0.0 + start_init = time.time() + # set seed for initial infection states + np.random.seed(sim_num) + # starting time point + t0 = abm.TimePoint(0) + # end time point of simulation + tmax = t0 + abm.days(90) + # create simulation with starting timepoint and number of age groups + sim = abm.Simulation(t0, num_age_groups) + # set seeds for simulation + abm.set_seeds(sim.model, sim_num) + # initialize model + abm.initialize_model(sim.model, input_path + 'persons.csv', os.path.join( + input_path, 'hospitals.csv'), os.path.join( + output_path, str(sim_num) + '_mapping.txt'), max_work_size, max_school_size) + + # read infection parameters + parameters = pd.read_csv(os.path.join( + input_path, 'parameter_table.csv'), index_col=0) + # set infection parameters + sim.model.parameters = set_infection_parameters(parameters) + # set age groups that go to school and work + abm.set_AgeGroupGoToSchool(sim.model.parameters, age_group_5_to_15) + abm.set_AgeGroupGoToWork(sim.model.parameters, age_group_16_to_34) + abm.set_AgeGroupGoToWork(sim.model.parameters, age_group_35_to_59) + # set age groups that go to shop + abm.set_AgeGroupGoToShop(sim.model.parameters, age_group_0_to_4) + abm.set_AgeGroupGoToShop(sim.model.parameters, age_group_5_to_15) + abm.set_AgeGroupGoToShop(sim.model.parameters, age_group_16_to_34) + abm.set_AgeGroupGoToShop(sim.model.parameters, age_group_35_to_59) + abm.set_AgeGroupGoToShop(sim.model.parameters, age_group_60_to_79) + abm.set_AgeGroupGoToShop(sim.model.parameters, age_group_80_plus) + # add dampings + # sim.model.add_infection_rate_damping( + # abm.TimePoint(abm.days(5).seconds), 0.2) + # add closure for work, event, shop and school locations at day 5 + # sim.model.add_location_closure(abm.TimePoint( + # abm.days(5).seconds), abm.LocationType.Work, 1.0) + # sim.model.add_location_closure(abm.TimePoint( + # abm.days(5).seconds), abm.LocationType.School, 1.0) + # sim.model.add_location_closure(abm.TimePoint( + # abm.days(5).seconds), abm.LocationType.SocialEvent, 1.0) + # sim.model.add_location_closure(abm.TimePoint( + # abm.days(5).seconds), abm.LocationType.BasicsShop, 1.0) + end_init = time.time() + print(f'Time for model initialization: {end_init - start_init} seconds') + total_init_time += (end_init - start_init) + # map locations to wastewater areas + start_map = time.time() + tan_map = map_traffic_cell_to_wastewater_area(os.path.join( + output_path, str(sim_num) + '_mapping.txt'), os.path.join(input_path, 'Munich_shape250319/Verschnitt_DLR_TAN_Rep.shp'), os.path.join( + output_path, str(sim_num) + '_mapping_tan.txt'), os.path.join( + output_path, str(sim_num) + '_mapping_tan_locs.txt')) + end_map = time.time() + print( + f'Time for mapping locations to TAN areas: {end_map - start_map} seconds') + total_output_time += end_map - start_map + + start_locs = [] + # specify starting locations if local outbreak should be simulated + if (local_outbreak): + start_home_map = time.time() + home_map = create_home_mapping(tan_map) + start_areas = ['58'] + start_locs = [loc for area in start_areas for loc in home_map[area]] + end_home_map = time.time() + print( + f'Time for creating outbreak loc list: {end_home_map - start_home_map} seconds') + total_init_time += (end_home_map - start_home_map) + + # assign initial infection states according to distribution + start_assign = time.time() + assign_infection_states(sim.model, t0, 0.00013, 0.00005, + 0.00002, 0.0, 0.0, 0.0, parameters, start_locs) + end_assign = time.time() + print( + f'Time for assigning infection state: {end_assign - start_assign} seconds') + total_init_time += (end_assign - start_assign) + + # initialize tan wastewater ids for all locations + start_ww_init = time.time() + abm.set_wastewater_ids(os.path.join( + output_path, str(sim_num) + '_mapping_tan_locs.txt'), sim.model) + end_ww_init = time.time() + print( + f'Time for initializing TAN areas for all locations: {end_ww_init - start_ww_init} seconds') + total_init_time += (end_ww_init - start_ww_init) + # write size per location + abm.write_size_per_location(os.path.join( + output_path, str(sim_num) + '_size_per_loc.txt'), sim.model) + # output object + history = History() + start_advance = time.time() + # advance simulation until tmax + sim.advance(tmax, history) + end_advance = time.time() + print( + f'Time for advancing simulation: {end_advance - start_advance} seconds') + total_simulation_time += (end_advance - start_advance) + # write infection paths per agent to file + start_o1 = time.time() + abm.save_infection_paths(os.path.join( + output_path, str(sim_num) + '_infection_paths.txt'), sim.model, tmax) + end_o1 = time.time() + print(f'Time writing infection paths txt: {end_o1 - start_o1} seconds') + total_output_time += (end_o1 - start_o1) + # write compartment size per time step to file + start_o2 = time.time() + abm.save_comp_output(os.path.join( + output_path, str(sim_num) + '_comps.csv'), sim.model, history) + end_o2 = time.time() + print(f'Time writing comps csv: {end_o2 - start_o2} seconds') + total_output_time += (end_o2 - start_o2) + # # write results to h5 file v1. The file has two data sets for every AgentId which are: + # # - LocationId at every time step + # # - Time since transmission at every time step + # start_h5_v1 = time.time() + # abm.write_h5(os.path.join( + # output_path, str(sim_num) + '_output_v1.h5'), history) + # end_h5_v1 = time.time() + # print(f'Time to write v1 output h5: {end_h5_v1 - start_h5_v1} seconds') + # total_output_time += (end_h5_v1 - start_h5_v1) + + # # write results to h5 file v2. The file has three data sets for every AgentId which are: + # # - Time since transmission at change points. Even indices are the change time point + # # and the following odd index is the changed time since transmission + # # - Time points of location changes + # # - (New) LocationId corresponding to the time points from the second dataset + # start_h5_v2 = time.time() + # abm.write_h5_v2(os.path.join( + # output_path, str(sim_num) + '_output_v2.h5'), history) + # end_h5_v2 = time.time() + # print(f'Time to write v2 output h5: {end_h5_v2 - start_h5_v2} seconds') + # total_output_time += (end_h5_v2 - start_h5_v2) + + # # write results to h5 file v3. The file has one group with two datasets: + # # - Transmission time point and recovery time point for every agent (matrix of size #agents x 2) + # # - LocationId at every time step for every agent (matrix of size #agents x #timepoints) + # start_h5_v3 = time.time() + # abm.write_h5_v3(os.path.join( + # output_path, str(sim_num) + '_output_v3.h5'), history) + # end_h5_v3 = time.time() + # print(f'Time to write v3 output h5: {end_h5_v3 - start_h5_v3} seconds') + # total_output_time += (end_h5_v3 - start_h5_v3) + + # write results to h5 file v4. The file has one group with two datasets: + # - Transmission time point and recovery time point for every agent (matrix of size #agents x 2) + # - TanAreaId (int > 0) at every time step for every agent (matrix of size #agents x #timepoints). + # If the agent is at a location not in Munich the id is 0. + start_h5_v4 = time.time() + abm.write_h5_v4(os.path.join( + output_path, str(sim_num) + '_output_v4.h5'), history) + end_h5_v4 = time.time() + print(f'Time to write v4 output h5: {end_h5_v4 - start_h5_v4} seconds') + total_output_time += (end_h5_v4 - start_h5_v4) + + # write results to h5 file v4. The file has one group with two datasets: + # - Transmission time point and recovery time point for every agent (matrix of size #agents x 2) + # - LocationType at every time step for every agent (matrix of size #agents x #timepoints). + start_h5_v5 = time.time() + abm.write_h5_v5(os.path.join( + output_path, str(sim_num) + '_output_v5.h5'), history) + end_h5_v5 = time.time() + print(f'Time to write v5 output h5: {end_h5_v5 - start_h5_v5} seconds') + total_output_time += (end_h5_v5 - start_h5_v5) + print('done') + return (sim_num, total_init_time, total_simulation_time, total_output_time) + + +if __name__ == "__main__": + arg_parser = argparse.ArgumentParser( + 'abm demonstrator', + description='Example demonstrating the agent-based model for a synthetic population of Munich.') + args = arg_parser.parse_args() + # set LogLevel + mio.abm.set_log_level_warn() + sim_nums = [] + init_times = [] + sim_times = [] + output_times = [] + for i in range(0, 10): + o = run_abm_simulation(i, **args.__dict__) + sim_nums.append(o[0]) + init_times.append(o[1]) + sim_times.append(o[2]) + output_times.append(o[3]) + write_time_to_file(sim_nums, init_times, sim_times, output_times, + sys.path[0] + '/output/timings.txt') diff --git a/pycode/examples/simulation/ABM Demonstrator/input/INSIDe_Demonstrator_AreaList.txt b/pycode/examples/simulation/ABM Demonstrator/input/INSIDe_Demonstrator_AreaList.txt new file mode 100644 index 0000000000..0b1200d451 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/input/INSIDe_Demonstrator_AreaList.txt @@ -0,0 +1,124 @@ +id inhabitants type +00001 0 recreational +00002 0 recreational +00003 0 recreational +00004 0 recreational +00005 0 recreational +00006 0 recreational +00007 0 recreational +00008 0 recreational +00009 0 shopping_business +00010 0 shopping_business +00011 0 shopping_business +00012 0 shopping_business +00013 0 shopping_business +00014 0 shopping_business +00015 0 shopping_business +00016 0 shopping_business +00017 0 shopping_business +00018 0 shopping_business +00019 0 shopping_business +00020 0 shopping_business +00021 0 shopping_business +00022 0 shopping_business +00023 0 shopping_business +00024 0 shopping_business +00025 0 shopping_business +00026 0 shopping_business +00027 0 shopping_business +00028 0 shopping_business +00029 0 shopping_business +00030 0 shopping_business +00031 0 university +00032 0 university +00033 35 residential_3 +00034 20 residential_3 +00035 10 residential_3 +00036 10 residential_3 +00037 3 residential_3 +00038 10 residential_3 +00039 3 residential_3 +00040 10 residential_3 +00041 10 residential_3 +00042 10 residential_3 +00043 10 residential_3 +00044 10 residential_3 +00045 10 residential_3 +00046 10 residential_3 +00047 10 residential_3 +00048 10 residential_3 +00049 10 residential_3 +00050 10 residential_3 +00051 10 residential_3 +00052 10 residential_3 +00053 10 residential_3 +00054 10 residential_3 +00055 10 residential_3 +00056 10 residential_3 +00057 10 residential_3 +00058 10 residential_3 +00059 10 residential_3 +00060 10 residential_3 +00061 10 residential_3 +00062 0 university +00063 0 university +00064 0 university +00065 0 university +00066 0 university +00067 0 university +00068 0 university +00069 0 university +00070 12 mixed +00072 8 mixed +00074 8 mixed +00075 5 mixed +00076 6 mixed +00077 7 mixed +00078 4 mixed +00079 0 shopping_business +00080 0 shopping_business +00081 0 shopping_business +00082 0 shopping_business +00083 37 residential_1 +00084 25 residential_1 +00085 28 residential_1 +00086 7 residential_1 +00087 7 residential_1 +00088 10 residential_1 +00089 10 residential_1 +00090 10 residential_1 +00091 10 residential_1 +00092 10 residential_1 +00093 10 residential_1 +00094 10 residential_1 +00095 10 residential_1 +00096 10 residential_1 +00097 10 residential_1 +00098 10 residential_1 +00099 10 residential_1 +00100 8 residential_1 +00101 8 residential_1 +00102 31 residential_1 +00103 16 residential_1 +00104 0 recreational +00105 0 shopping_business +00106 0 shopping_business +00107 0 recreational +00108 0 recreational +00109 0 shopping_business +00110 0 recreational +00111 40 residential_2 +00112 7 residential_2 +00113 10 residential_2 +00114 10 residential_2 +00115 10 residential_2 +00116 10 residential_2 +00117 10 residential_2 +00118 10 residential_2 +00119 10 residential_2 +00120 10 residential_2 +00121 10 residential_2 +00122 18 residential_2 +00123 10 residential_2 +00124 15 residential_2 +00125 10 residential_2 \ No newline at end of file diff --git a/pycode/examples/simulation/ABM Demonstrator/input/parameter_table.csv b/pycode/examples/simulation/ABM Demonstrator/input/parameter_table.csv new file mode 100644 index 0000000000..8775572f28 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/input/parameter_table.csv @@ -0,0 +1,86 @@ +parameter,value,dev +Age0to4_IncubationPeriod,1.1992,0.1031 +Age0to4_InfectedNoSymptomsToSymptoms,0.6052,0.1903 +Age0to4_InfectedNoSymptomsToRecovered,1.6684,0.1316 +Age0to4_InfectedSymptomsToRecovered,1.9406,0.1034 +Age0to4_InfectedSymptomsToSevere,2.3487,0.0734 +Age0to4_SevereToRecovered,1.6041,0.1034 +Age0to4_SevereToCritical,1.5861,0.2161 +Age0to4_CriticalToRecovered,1.9347,0.1499 +Age0to4_CriticalToDead,1.7761,0.1768 +Age0to4_SymptomsPerInfectedNoSymptoms,0.75,0.0 +Age0to4_SeverePerInfectedSymptoms,0.0075,0.0 +Age0to4_CriticalPerInfectedSevere,0.075,0.0 +Age0to4_DeathsPerInfectedCritical,0.05,0.0 +Age5to14_IncubationPeriod,1.1992,0.1031 +Age5to14_InfectedNoSymptomsToSymptoms,0.6052,0.1903 +Age5to14_InfectedNoSymptomsToRecovered,1.6684,0.1316 +Age5to14_InfectedSymptomsToRecovered,1.9406,0.1034 +Age5to14_InfectedSymptomsToSevere,2.3487,0.0734 +Age5to14_SevereToRecovered,1.6041,0.1034 +Age5to14_SevereToCritical,1.5861,0.2161 +Age5to14_CriticalToRecovered,1.9347,0.1499 +Age5to14_CriticalToDead,1.7761,0.1768 +Age5to14_SymptomsPerInfectedNoSymptoms,0.75,0.0 +Age5to14_SeverePerInfectedSymptoms,0.0075,0.0 +Age5to14_CriticalPerInfectedSevere,0.075,0.0 +Age5to14_DeathsPerInfectedCritical,0.05,0.0 +Age15to34_IncubationPeriod,1.1992,0.1031 +Age15to34_InfectedNoSymptomsToSymptoms,0.6052,0.1903 +Age15to34_InfectedNoSymptomsToRecovered,1.6684,0.1316 +Age15to34_InfectedSymptomsToRecovered,1.9406,0.1034 +Age15to34_InfectedSymptomsToSevere,2.3487,0.0734 +Age15to34_SevereToRecovered,1.7881,0.0858 +Age15to34_SevereToCritical,1.5861,0.2161 +Age15to34_CriticalToRecovered,1.9347,0.1499 +Age15to34_CriticalToDead,1.7761,0.1768 +Age15to34_SymptomsPerInfectedNoSymptoms,0.8,0.0 +Age15to34_SeverePerInfectedSymptoms,0.019,0.0 +Age15to34_CriticalPerInfectedSevere,0.075,0.0 +Age15to34_DeathsPerInfectedCritical,0.14,0.0 +Age35to59_IncubationPeriod,1.1992,0.1031 +Age35to59_InfectedNoSymptomsToSymptoms,0.6052,0.1903 +Age35to59_InfectedNoSymptomsToRecovered,1.6684,0.1316 +Age35to59_InfectedSymptomsToRecovered,1.9406,0.1034 +Age35to59_InfectedSymptomsToSevere,1.7881,0.0858 +Age35to59_SevereToRecovered,2.0774,0.0641 +Age35to59_SevereToCritical,1.5861,0.2161 +Age35to59_CriticalToRecovered,2.8569,0.1034 +Age35to59_CriticalToDead,2.8023,0.0465 +Age35to59_SymptomsPerInfectedNoSymptoms,0.8,0.0 +Age35to59_SeverePerInfectedSymptoms,0.0615,0.0 +Age35to59_CriticalPerInfectedSevere,0.15,0.0 +Age35to59_DeathsPerInfectedCritical,0.14,0.0 +Age60to79_IncubationPeriod,1.1992,0.1031 +Age60to79_InfectedNoSymptomsToSymptoms,0.6052,0.1903 +Age60to79_InfectedNoSymptomsToRecovered,1.6684,0.1316 +Age60to79_InfectedSymptomsToRecovered,1.9406,0.1034 +Age60to79_InfectedSymptomsToSevere,1.7881,0.0858 +Age60to79_SevereToRecovered,2.3013,0.0512 +Age60to79_SevereToCritical,1.5861,0.2161 +Age60to79_CriticalToRecovered,2.8569,0.1034 +Age60to79_CriticalToDead,2.8023,0.0465 +Age60to79_SymptomsPerInfectedNoSymptoms,0.8,0.0 +Age60to79_SeverePerInfectedSymptoms,0.165,0.0 +Age60to79_CriticalPerInfectedSevere,0.3,0.0 +Age60to79_DeathsPerInfectedCritical,0.4,0.0 +Age80plus_IncubationPeriod,1.1992,0.1031 +Age80plus_InfectedNoSymptomsToSymptoms,0.6052,0.1903 +Age80plus_InfectedNoSymptomsToRecovered,1.6684,0.1316 +Age80plus_InfectedSymptomsToRecovered,1.9406,0.1034 +Age80plus_InfectedSymptomsToSevere,1.7881,0.0858 +Age80plus_SevereToRecovered,2.7057,0.0684 +Age80plus_SevereToCritical,1.5861,0.2161 +Age80plus_CriticalToRecovered,2.5204,0.1034 +Age80plus_CriticalToDead,2.3968,0.0465 +Age80plus_SymptomsPerInfectedNoSymptoms,0.8,0.0 +Age80plus_SeverePerInfectedSymptoms,0.225,0.0 +Age80plus_CriticalPerInfectedSevere,0.4,0.0 +Age80plus_DeathsPerInfectedCritical,0.6,0.0 +incline,2 +decline,-0.17 +alpha,-7 +beta,1 +t_limit,1.3 +peak_max,8.1 +s_sc,12589254.1179417 diff --git a/pycode/examples/simulation/ABM Demonstrator/post_processing.py b/pycode/examples/simulation/ABM Demonstrator/post_processing.py new file mode 100644 index 0000000000..f2a273b217 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/post_processing.py @@ -0,0 +1,10 @@ +from memilio.simulation import abm + +if __name__ == "__main__": + # Path to simulation results folder. Has to have a "/" at the end e.g. /Documents/outputs/ + sim_result_file = "" + # Folder the aggregated output should be saved to + save_folder = sim_result_file + num_sims = 1 + abm.calculate_infections_per_quantity( + sim_result_file, save_folder, num_sims) diff --git a/pycode/memilio-simulation/CMakeLists.txt b/pycode/memilio-simulation/CMakeLists.txt index e0ce008820..fdaaecef3b 100644 --- a/pycode/memilio-simulation/CMakeLists.txt +++ b/pycode/memilio-simulation/CMakeLists.txt @@ -39,17 +39,15 @@ function(add_pymio_module target_name) set(multiValueArgs LINKED_LIBRARIES SOURCES) cmake_parse_arguments(PYBIND11_MODULE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - pybind11_add_module(${target_name} MODULE ${PYBIND11_MODULE_SOURCES}) target_link_libraries(${target_name} PRIVATE ${PYBIND11_MODULE_LINKED_LIBRARIES}) target_include_directories(${target_name} PRIVATE memilio/simulation/bindings) install(TARGETS ${target_name} LIBRARY DESTINATION memilio/simulation) endfunction() - # build python extensions add_pymio_module(_simulation_abm - LINKED_LIBRARIES memilio abm + LINKED_LIBRARIES memilio abm Boost::filesystem SOURCES memilio/simulation/bindings/models/abm.cpp ) diff --git a/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp b/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp index a0e47423c0..4bc3fbcd59 100644 --- a/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp @@ -17,25 +17,1140 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -//Includes from pymio +#include "H5Tpublic.h" +#include "H5public.h" +#include "abm/infection_state.h" +#include "abm/location_id.h" +#include "abm/location_type.h" +#include "abm/time.h" +#include "memilio/io/io.h" +#include "memilio/utils/compiler_diagnostics.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/random_number_generator.h" #include "pybind_util.h" #include "utils/custom_index_array.h" #include "utils/parameter_set.h" #include "utils/index.h" - -//Includes from MEmilio #include "abm/simulation.h" +#include "abm/household.h" +#include "abm/personal_rng.h" +#include "boost/filesystem.hpp" +#include "boost/algorithm/string/split.hpp" +#include "boost/algorithm/string/classification.hpp" +#include "memilio/io/hdf5_cpp.h" +#include "munich_postprocessing/output_processing.h" #include "pybind11/attr.h" #include "pybind11/cast.h" #include "pybind11/pybind11.h" #include "pybind11/operators.h" +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace py = pybind11; +//time point logger +struct LogTimePoint : mio::LogAlways { + using Type = double; + static Type log(const mio::abm::Simulation& sim) + { + return sim.get_time().hours(); + } +}; + +//LocationId logger +struct LogLocationIds : mio::LogOnce { + using Type = std::vector>; + static Type log(const mio::abm::Simulation& sim) + { + std::vector> location_ids{}; + for (auto&& location : sim.get_model().get_locations()) { + location_ids.push_back(std::make_tuple(location.get_id(), location.get_type())); + } + return location_ids; + } +}; + +//AgentId logger +struct LogAgentIds : mio::LogOnce { + using Type = std::vector; + static Type log(const mio::abm::Simulation& sim) + { + std::vector agent_ids{}; + for (auto&& person : sim.get_model().get_persons()) { + agent_ids.push_back(person.get_id()); + } + return agent_ids; + } +}; + +//agent logger +struct LogPersonsPerLocationAndInfectionTime : mio::LogAlways { + using Type = std::vector>; + static Type log(const mio::abm::Simulation& sim) + { + std::vector> + location_ids_person{}; + for (auto&& person : sim.get_model().get_persons()) { + int ww_id = sim.get_model().get_location(person.get_location()).get_wastewater_id(); + location_ids_person.push_back(std::make_tuple(person.get_location(), person.get_location_type(), + person.get_id(), person.get_time_since_transmission(), + person.get_infection_state(sim.get_time()), ww_id)); + } + return location_ids_person; + } +}; + +std::pair get_my_and_sigma(double mean, double std) +{ + double my = log(mean * mean / sqrt(mean * mean + std * std)); + double sigma = sqrt(log(1 + std * std / (mean * mean))); + return {my, sigma}; +} + +mio::AgeGroup determine_age_group(uint32_t age) +{ + if (age <= 4) { + return mio::AgeGroup(0); + } + else if (age <= 15) { + return mio::AgeGroup(1); + } + else if (age <= 34) { + return mio::AgeGroup(2); + } + else if (age <= 59) { + return mio::AgeGroup(3); + } + else if (age <= 79) { + return mio::AgeGroup(4); + } + else if (age > 79) { + return mio::AgeGroup(5); + } + else { + return mio::AgeGroup(0); + } +} + +int stringToMinutes(const std::string& input) +{ + size_t colonPos = input.find(":"); + if (colonPos == std::string::npos) { + // Handle invalid input (no colon found) + return -1; // You can choose a suitable error code here. + } + + std::string xStr = input.substr(0, colonPos); + std::string yStr = input.substr(colonPos + 1); + + int x = std::stoi(xStr); + int y = std::stoi(yStr); + return x * 60 + y; +} + +int longLatToInt(const std::string& input) +{ + double y = std::stod(input) * 1e+5; //we want the 5 numbers after digit + return (int)y; +} + +void split_line(std::string string, std::vector* row) +{ + std::vector strings; + boost::split(strings, string, boost::is_any_of(",")); + std::transform(strings.begin(), strings.end(), std::back_inserter(*row), [&](std::string s) { + if (s.find(":") != std::string::npos) { + return stringToMinutes(s); + } + else if (s.find(".") != std::string::npos) { + return longLatToInt(s); + } + else { + return std::stoi(s); + } + }); +} + +void write_infection_paths(std::string filename, mio::abm::Model& model, mio::abm::TimePoint tmax) +{ + auto file = fopen(filename.c_str(), "w"); + if (file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", filename); + } + else { + fprintf(file, "Agent_id S E I_ns I_sy I_sev I_cri R D\n"); + for (auto& person : model.get_persons()) { + fprintf(file, "%d ", person.get_id().get()); + if (person.get_infection_state(tmax) == mio::abm::InfectionState::Susceptible) { + fprintf(file, "%.14f ", tmax.hours()); + for (auto i = 0; i < static_cast(mio::abm::InfectionState::Count); ++i) { + fprintf(file, "0 "); + } + } + else { + auto time_S = std::max( + {person.get_infection().get_infection_start() - mio::abm::TimePoint(0), mio::abm::TimeSpan(0)}); + auto time_E = person.get_infection().get_time_in_state(mio::abm::InfectionState::Exposed); + auto time_INS = person.get_infection().get_time_in_state(mio::abm::InfectionState::InfectedNoSymptoms); + auto time_ISy = person.get_infection().get_time_in_state(mio::abm::InfectionState::InfectedSymptoms); + auto time_ISev = person.get_infection().get_time_in_state(mio::abm::InfectionState::InfectedSevere); + auto time_ICri = person.get_infection().get_time_in_state(mio::abm::InfectionState::InfectedCritical); + auto time_R = mio::abm::TimePoint(0); + auto time_D = mio::abm::TimePoint(0); + auto t_Infected = time_E + time_INS + time_ISy + time_ISev + time_ICri; + if (person.get_infection_state(tmax) == mio::abm::InfectionState::Recovered) { + if (time_S.hours() == 0) { + time_R = + tmax - t_Infected + (person.get_infection().get_infection_start() - mio::abm::TimePoint(0)); + } + else { + time_R = tmax - time_S - t_Infected; + } + } + else if (person.get_infection_state(tmax) == mio::abm::InfectionState::Dead) { + if (time_S.hours() == 0) { + time_D = + tmax - t_Infected + (person.get_infection().get_infection_start() - mio::abm::TimePoint(0)); + } + else { + time_D = tmax - time_S - t_Infected; + } + } + fprintf(file, "%.14f ", time_S.hours()); + fprintf(file, "%.14f ", time_E.hours()); + fprintf(file, "%.14f ", time_INS.hours()); + fprintf(file, "%.14f ", time_ISy.hours()); + fprintf(file, "%.14f ", time_ISev.hours()); + fprintf(file, "%.14f ", time_ICri.hours()); + fprintf(file, "%.14f ", time_R.hours()); + fprintf(file, "%.14f ", time_D.hours()); + } + fprintf(file, "\n"); + } + fclose(file); + } +} + +void write_compartments(std::string filename, mio::abm::Model& model, + mio::History& history) +{ + auto file = fopen(filename.c_str(), "w"); + if (file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", filename); + } + else { + auto log = history.get_log(); + auto tps = std::get<0>(log); + fprintf(file, "t S E Ins Isy Isev Icri R D\n"); + for (auto t = size_t(0); t < tps.size(); ++t) { + auto tp = mio::abm::TimePoint(0) + mio::abm::hours(t); + fprintf(file, "%.14f ", tps[t]); + std::vector comps(static_cast(mio::abm::InfectionState::Count)); + for (auto& person : model.get_persons()) { + auto state = person.get_infection_state(tp); + comps[static_cast(state)] += 1; + } + for (auto c : comps) { + fprintf(file, "%d ", c); + } + fprintf(file, "\n"); + } + fclose(file); + } +} + +std::string loc_to_string(mio::abm::LocationId loc_id, mio::abm::LocationType type) +{ + return "0" + std::to_string(static_cast(type)) + std::to_string(loc_id.get()); +} + +int time_since_transmission(mio::abm::TimeSpan t) +{ + return t.days() > 500 ? -1 : t.hours(); +} + +#ifdef MEMILIO_HAS_HDF5 +mio::IOResult write_h5(std::string filename, + mio::History& history) +{ + // create hdf5 file + mio::H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, mio::StatusCode::FileNotFound, filename); + // get agents ids + auto log = history.get_log(); + auto agent_ids = std::get<3>(log)[0]; + auto logPerPerson = std::get<2>(log); + for (auto& id : agent_ids) { + auto group_name = std::to_string(id.get()); + mio::H5Group agent_h5group{H5Gcreate(file.id, group_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(agent_h5group.id, mio::StatusCode::UnknownError, + "H5Group could not be created for agent (" + group_name + ")"); + const int num_timepoints = static_cast(logPerPerson.size()); + // Dimension for both data sets + hsize_t dims_t[] = {static_cast(num_timepoints)}; + // DataSpace for both data sets + mio::H5DataSpace dspace_t{H5Screate_simple(1, dims_t, NULL)}; + MEMILIO_H5_CHECK(dspace_t.id, mio::StatusCode::UnknownError, "DataSpace could not be created."); + std::vector LocIds(num_timepoints); + std::vector tsm(num_timepoints); + for (int t = 0; t < num_timepoints; ++t) { + std::string s = + loc_to_string(std::get<0>(logPerPerson[t][id.get()]), std::get<1>(logPerPerson[t][id.get()])); + LocIds[t] = new char[s.size()]; + strcpy(LocIds[t], s.c_str()); + tsm[t] = time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + // string dim + hsize_t str_dim[1] = {LocIds.size()}; + mio::H5DataSpace dspace_str{H5Screate_simple(1, str_dim, NULL)}; + + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, H5T_VARIABLE); + + mio::H5DataSet dset_LocIds{ + H5Dcreate(agent_h5group.id, "loc_ids", datatype, dspace_str.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_LocIds.id, mio::StatusCode::UnknownError, "LocId DataSet could not be created (LocIds)."); + MEMILIO_H5_CHECK(H5Dwrite(dset_LocIds.id, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, LocIds.data()), + mio::StatusCode::UnknownError, "LocId data could not be written."); + for (int t = 0; t < num_timepoints; ++t) { + delete[] LocIds[t]; + } + mio::H5DataSet dset_tsm{H5Dcreate(agent_h5group.id, "time_since_transm", H5T_NATIVE_INT, dspace_t.id, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_tsm.id, mio::StatusCode::UnknownError, + "TST DataSet could not be created (timeSinceTransmission)."); + MEMILIO_H5_CHECK(H5Dwrite(dset_tsm.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, tsm.data()), + mio::StatusCode::UnknownError, "TST data could not be written."); + } + return mio::success(); +} + +mio::IOResult write_h5_v2(std::string filename, + mio::History& history) +{ + //Option 1: + // One hdf5 group per agent. For every agent there three datasets. + // 1st dataset: is a vector where even indices a the time point and odd indices the corresponding time since transmission. + // 2nd dataset: is a vector with the location change tps + // 3rd dataset: is a vector with (new) location ids + + // create hdf5 file + mio::H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, mio::StatusCode::FileNotFound, filename); + + // get agents ids + auto log = history.get_log(); + auto agent_ids = std::get<3>(log)[0]; + auto logPerPerson = std::get<2>(log); + for (auto& id : agent_ids) { + auto group_name = std::to_string(id.get()); + mio::H5Group agent_h5group{H5Gcreate(file.id, group_name.c_str(), H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(agent_h5group.id, mio::StatusCode::UnknownError, + "H5Group could not be created for agent (" + group_name + ")"); + const int num_timepoints = static_cast(logPerPerson.size()); + // get time since transmission + std::vector tsm(num_timepoints); + std::transform(logPerPerson.begin(), logPerPerson.end(), tsm.begin(), [&id](const auto& entry) { + return time_since_transmission(std::get<3>(entry[id.get()])); + }); + std::vector tsm_result_vec; + bool is_infected = false; + // Add time since transmission at the beginning of the simulation + tsm_result_vec.push_back(0); + tsm_result_vec.push_back(tsm[0]); + if (tsm[0] > -1) { + is_infected = true; + } + // Add time points of transmission and recovery + for (size_t t = 1; t < tsm.size(); ++t) { + if (!is_infected) { + //time point of transmission + if (tsm[t] > -1) { + tsm_result_vec.push_back(t); + tsm_result_vec.push_back(tsm[t]); + is_infected = true; + } + } + else { + //time point of recovery + if (tsm[t] == -1) { + tsm_result_vec.push_back(t); + tsm_result_vec.push_back(tsm[t]); + is_infected = false; + } + } + } + // Dimension for 1st data set + hsize_t dims_d1[] = {static_cast(tsm_result_vec.size())}; + // Get locations + std::vector loc_tps_result_vec; + std::vector loc_ids_result_vec; + // Add location at the beginning of the simulation + loc_tps_result_vec.push_back(0); + std::string loc = loc_to_string(std::get<0>(logPerPerson[0][id.get()]), std::get<1>(logPerPerson[0][id.get()])); + loc_ids_result_vec.push_back(new char[loc.size()]); + strcpy(loc_ids_result_vec[loc_ids_result_vec.size() - 1], loc.c_str()); + // Iterate over all locations and only add change points + for (size_t t = 1; t < num_timepoints; ++t) { + std::string loc_new = + loc_to_string(std::get<0>(logPerPerson[t][id.get()]), std::get<1>(logPerPerson[t][id.get()])); + if (loc_new != loc) { + loc_tps_result_vec.push_back(t); + loc_ids_result_vec.push_back(new char[loc_new.size()]); + strcpy(loc_ids_result_vec[loc_ids_result_vec.size() - 1], loc_new.c_str()); + } + loc = loc_new; + } + // Dimension for 2nd and 3rd data set + hsize_t dims_d2d3[1] = {loc_ids_result_vec.size()}; + // DataSpace for data set 1 + mio::H5DataSpace dspace_d1{H5Screate_simple(1, dims_d1, NULL)}; + MEMILIO_H5_CHECK(dspace_d1.id, mio::StatusCode::UnknownError, "V2: DataSpace 1 could not be created."); + // DataSpace for data set 2 + mio::H5DataSpace dspace_d2{H5Screate_simple(1, dims_d2d3, NULL)}; + MEMILIO_H5_CHECK(dspace_d2.id, mio::StatusCode::UnknownError, "V2: DataSpace 2 could not be created."); + // DataSpace for data set 3 + mio::H5DataSpace dspace_d3{H5Screate_simple(1, dims_d2d3, NULL)}; + MEMILIO_H5_CHECK(dspace_d3.id, mio::StatusCode::UnknownError, "V2: DataSpace 3 could not be created."); + // Data type for loc ids + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, H5T_VARIABLE); + // Add data set 1 to HDF5 file + mio::H5DataSet dset_tsm{H5Dcreate(agent_h5group.id, "time_since_transm", H5T_NATIVE_INT, dspace_d1.id, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_tsm.id, mio::StatusCode::UnknownError, + "V2: Time since transmission DataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_tsm.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, tsm_result_vec.data()), + mio::StatusCode::UnknownError, "V2: Time since transmission data could not be written."); + // Add data set 2 to HDF5 file + mio::H5DataSet dset_loc_tps{H5Dcreate(agent_h5group.id, "loc_tps", H5T_NATIVE_INT, dspace_d2.id, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_loc_tps.id, mio::StatusCode::UnknownError, "V2: Loc tps DataSet could not be created."); + MEMILIO_H5_CHECK( + H5Dwrite(dset_loc_tps.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, loc_tps_result_vec.data()), + mio::StatusCode::UnknownError, "V2: Loc tps data could not be written."); + // Add data set 3 to HDF5 file + mio::H5DataSet dset_loc_ids{ + H5Dcreate(agent_h5group.id, "loc_ids", datatype, dspace_d3.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_loc_ids.id, mio::StatusCode::UnknownError, "V2: Loc ids DataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_loc_ids.id, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, loc_ids_result_vec.data()), + mio::StatusCode::UnknownError, "V2: Loc ids data could not be written."); + } + return mio::success(); +} + +mio::IOResult write_h5_v3(std::string filename, + mio::History& history) +{ + //Option 2: + // hdf5 file contains two datasets (no groups) + // 1st dataset: #agents x 2 matrix with entry (i,0) tp of transmission and entry (i,1) tp of recovery + // 2nd dataset: #agents x #tps with entry (i,t) loc at t + + // create hdf5 file + mio::H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, mio::StatusCode::FileNotFound, filename); + + // get agents ids + auto log = history.get_log(); + auto agent_ids = std::get<3>(log)[0]; + auto logPerPerson = std::get<2>(log); + + //Create group for all data sets + mio::H5Group h5group{H5Gcreate(file.id, "data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(h5group.id, mio::StatusCode::UnknownError, "H5Group \"data\" could not be created "); + + const int num_agents = static_cast(agent_ids.size()); + // Dimension and matrix for 1st data set + hsize_t dims_d1[] = {static_cast(num_agents), static_cast(2)}; + Eigen::Matrix tsm_matrix(num_agents, 2); + + const int num_timepoints = static_cast(logPerPerson.size()); + // Dimension and matrix for 2nd data set + hsize_t dims_d2[] = {static_cast(num_agents), static_cast(num_timepoints)}; + Eigen::Matrix loc_matrix(num_agents, num_timepoints); + // Fill matrices + for (auto& id : agent_ids) { + // initialize time point transmission and recovery with Null + tsm_matrix(id.get(), 0) = 100000; + tsm_matrix(id.get(), 1) = 100000; + bool tp_transmission_set = false; + bool tp_recovery_set = false; + for (int t = 0; t < num_timepoints; ++t) { + // set time point of transmission + if (!tp_transmission_set && time_since_transmission(std::get<3>(logPerPerson[t][id.get()])) > -1) { + if (t == 0) { + tsm_matrix(id.get(), 0) = -time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + else { + tsm_matrix(id.get(), 0) = t + time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + tp_transmission_set = true; + } + // set time point of recovery + if (tp_transmission_set && !tp_recovery_set && + time_since_transmission(std::get<3>(logPerPerson[t][id.get()])) == -1) { + tsm_matrix(id.get(), 1) = t; + tp_recovery_set = true; + } + std::string s = + loc_to_string(std::get<0>(logPerPerson[t][id.get()]), std::get<1>(logPerPerson[t][id.get()])); + loc_matrix(id.get(), t) = new char[s.size()]; + strcpy(loc_matrix(id.get(), t), s.c_str()); + } + } + // DataSpace for data set 1 + mio::H5DataSpace dspace_d1{H5Screate_simple(2, dims_d1, NULL)}; + MEMILIO_H5_CHECK(dspace_d1.id, mio::StatusCode::UnknownError, "V3: DataSpace 1 could not be created."); + // DataSpace for data set 2 + mio::H5DataSpace dspace_d2{H5Screate_simple(2, dims_d2, NULL)}; + MEMILIO_H5_CHECK(dspace_d2.id, mio::StatusCode::UnknownError, "V3: DataSpace 2 could not be created."); + // Add data set 1 to HDF5 file + mio::H5DataSet dset_t_r_tp{H5Dcreate(h5group.id, "transm_recovery_tp", H5T_NATIVE_DOUBLE, dspace_d1.id, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_t_r_tp.id, mio::StatusCode::UnknownError, + "V3: Transmission and recovery tp dataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_t_r_tp.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, tsm_matrix.data()), + mio::StatusCode::UnknownError, "V3: Transmission and recovery tp data could not be written."); + // Add data set 2 to HDF5 file + hid_t datatype = H5Tcopy(H5T_C_S1); + H5Tset_size(datatype, H5T_VARIABLE); + mio::H5DataSet dset_loc_ids{ + H5Dcreate(h5group.id, "loc_ids", datatype, dspace_d2.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_loc_ids.id, mio::StatusCode::UnknownError, "V3: Loc ids DataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_loc_ids.id, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, loc_matrix.data()), + mio::StatusCode::UnknownError, "V3: Loc ids data could not be written."); + return mio::success(); +} + +mio::IOResult write_h5_v4(std::string filename, + mio::History& history) +{ + //Option 2: + // hdf5 file contains two datasets (no groups) + // 1st dataset: #agents x 2 matrix with entry (i,0) tp of transmission and entry (i,1) tp of recovery + // 2nd dataset: #agents x #tps with entry (i,t) TAN area ID at t. For locations that are not in any TAN area, the ID is 0. + + // create hdf5 file + mio::H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, mio::StatusCode::FileNotFound, filename); + + // get agents ids + auto log = history.get_log(); + auto agent_ids = std::get<3>(log)[0]; + auto logPerPerson = std::get<2>(log); + + //Create group for all data sets + mio::H5Group h5group{H5Gcreate(file.id, "data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(h5group.id, mio::StatusCode::UnknownError, "H5Group \"data\" could not be created "); + + const int num_agents = static_cast(agent_ids.size()); + // Dimension and matrix for 1st data set + hsize_t dims_d1[] = {static_cast(num_agents), static_cast(2)}; + Eigen::Matrix tsm_matrix(num_agents, 2); + + const int num_timepoints = static_cast(logPerPerson.size()); + // Dimension and matrix for 2nd data set + hsize_t dims_d2[] = {static_cast(num_agents), static_cast(num_timepoints)}; + Eigen::Matrix loc_matrix(num_agents, num_timepoints); + // Fill matrices + for (auto& id : agent_ids) { + // initialize time point transmission and recovery with Null + tsm_matrix(id.get(), 0) = 100000; + tsm_matrix(id.get(), 1) = 100000; + bool tp_transmission_set = false; + bool tp_recovery_set = false; + for (int t = 0; t < num_timepoints; ++t) { + // set time point of transmission + if (!tp_transmission_set && time_since_transmission(std::get<3>(logPerPerson[t][id.get()])) > -1) { + if (t == 0) { + tsm_matrix(id.get(), 0) = -time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + else { + tsm_matrix(id.get(), 0) = t + time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + tp_transmission_set = true; + } + // set time point of recovery + if (tp_transmission_set && !tp_recovery_set && + time_since_transmission(std::get<3>(logPerPerson[t][id.get()])) == -1) { + tsm_matrix(id.get(), 1) = t; + tp_recovery_set = true; + } + loc_matrix(id.get(), t) = std::get<5>(logPerPerson[t][id.get()]); + } + } + // DataSpace for data set 1 + mio::H5DataSpace dspace_d1{H5Screate_simple(2, dims_d1, NULL)}; + MEMILIO_H5_CHECK(dspace_d1.id, mio::StatusCode::UnknownError, "V3: DataSpace 1 could not be created."); + // DataSpace for data set 2 + mio::H5DataSpace dspace_d2{H5Screate_simple(2, dims_d2, NULL)}; + MEMILIO_H5_CHECK(dspace_d2.id, mio::StatusCode::UnknownError, "V3: DataSpace 2 could not be created."); + // Add data set 1 to HDF5 file + mio::H5DataSet dset_t_r_tp{H5Dcreate(h5group.id, "transm_recovery_tp", H5T_NATIVE_DOUBLE, dspace_d1.id, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_t_r_tp.id, mio::StatusCode::UnknownError, + "V3: Transmission and recovery tp dataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_t_r_tp.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, tsm_matrix.data()), + mio::StatusCode::UnknownError, "V3: Transmission and recovery tp data could not be written."); + // Add data set 2 to HDF5 file + mio::H5DataSet dset_loc_ids{ + H5Dcreate(h5group.id, "loc_ids", H5T_NATIVE_INT, dspace_d2.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_loc_ids.id, mio::StatusCode::UnknownError, "V3: Loc ids DataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_loc_ids.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, loc_matrix.data()), + mio::StatusCode::UnknownError, "V3: Loc ids data could not be written."); + return mio::success(); +} + +mio::IOResult write_h5_v5(std::string filename, + mio::History& history) +{ + // hdf5 file contains two datasets (no groups) + // 1st dataset: #agents x 2 matrix with entry (i,0) tp of transmission and entry (i,1) tp of recovery + // 2nd dataset: #agents x #tps with entry (i,t) LocationType at t. + + // create hdf5 file + mio::H5File file{H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(file.id, mio::StatusCode::FileNotFound, filename); + + // get agents ids + auto log = history.get_log(); + auto agent_ids = std::get<3>(log)[0]; + auto logPerPerson = std::get<2>(log); + + //Create group for all data sets + mio::H5Group h5group{H5Gcreate(file.id, "data", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(h5group.id, mio::StatusCode::UnknownError, "H5Group \"data\" could not be created "); + + const int num_agents = static_cast(agent_ids.size()); + // Dimension and matrix for 1st data set + hsize_t dims_d1[] = {static_cast(num_agents), static_cast(2)}; + Eigen::Matrix tsm_matrix(num_agents, 2); + + const int num_timepoints = static_cast(logPerPerson.size()); + // Dimension and matrix for 2nd data set + hsize_t dims_d2[] = {static_cast(num_agents), static_cast(num_timepoints)}; + Eigen::Matrix loc_matrix(num_agents, num_timepoints); + // Fill matrices + for (auto& id : agent_ids) { + // initialize time point transmission and recovery with Null + tsm_matrix(id.get(), 0) = 100000; + tsm_matrix(id.get(), 1) = 100000; + bool tp_transmission_set = false; + bool tp_recovery_set = false; + for (int t = 0; t < num_timepoints; ++t) { + // set time point of transmission + if (!tp_transmission_set && time_since_transmission(std::get<3>(logPerPerson[t][id.get()])) > -1) { + if (t == 0) { + tsm_matrix(id.get(), 0) = -time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + else { + tsm_matrix(id.get(), 0) = t + time_since_transmission(std::get<3>(logPerPerson[t][id.get()])); + } + tp_transmission_set = true; + } + // set time point of recovery + if (tp_transmission_set && !tp_recovery_set && + time_since_transmission(std::get<3>(logPerPerson[t][id.get()])) == -1) { + tsm_matrix(id.get(), 1) = t; + tp_recovery_set = true; + } + loc_matrix(id.get(), t) = static_cast(std::get<1>(logPerPerson[t][id.get()])); + } + } + // DataSpace for data set 1 + mio::H5DataSpace dspace_d1{H5Screate_simple(2, dims_d1, NULL)}; + MEMILIO_H5_CHECK(dspace_d1.id, mio::StatusCode::UnknownError, "V3: DataSpace 1 could not be created."); + // DataSpace for data set 2 + mio::H5DataSpace dspace_d2{H5Screate_simple(2, dims_d2, NULL)}; + MEMILIO_H5_CHECK(dspace_d2.id, mio::StatusCode::UnknownError, "V3: DataSpace 2 could not be created."); + // Add data set 1 to HDF5 file + mio::H5DataSet dset_t_r_tp{H5Dcreate(h5group.id, "transm_recovery_tp", H5T_NATIVE_DOUBLE, dspace_d1.id, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_t_r_tp.id, mio::StatusCode::UnknownError, + "V3: Transmission and recovery tp dataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_t_r_tp.id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, tsm_matrix.data()), + mio::StatusCode::UnknownError, "V3: Transmission and recovery tp data could not be written."); + // Add data set 2 to HDF5 file + mio::H5DataSet dset_loc_ids{ + H5Dcreate(h5group.id, "loc_ids", H5T_NATIVE_INT, dspace_d2.id, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT)}; + MEMILIO_H5_CHECK(dset_loc_ids.id, mio::StatusCode::UnknownError, "V3: Loc ids DataSet could not be created."); + MEMILIO_H5_CHECK(H5Dwrite(dset_loc_ids.id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, loc_matrix.data()), + mio::StatusCode::UnknownError, "V3: Loc ids data could not be written."); + return mio::success(); +} +#endif + +void write_mapping_to_file(std::string filename, std::map>& mapping) +{ + auto file = fopen(filename.c_str(), "w"); + if (file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", filename); + } + else { + for (auto it = mapping.begin(); it != mapping.end(); it++) { + fprintf(file, "%d", it->first); + for (auto s : it->second) { + fprintf(file, " %s", s.c_str()); + } + fprintf(file, "\n"); + } + fclose(file); + } +} + +void set_wastewater_ids(std::string mapping_file, mio::abm::Model& model) +{ + // read in mapping from loc to TAN area id and save it in std::map + std::map, int> loc_to_tan_id; + + const boost::filesystem::path p = mapping_file; + if (!boost::filesystem::exists(p)) { + mio::log_error("Cannot read in mapping data. File does not exist."); + } + // File pointer + std::fstream fin; + + // Open an existing file + fin.open(mapping_file, std::ios::in); + std::string line; + + while (std::getline(fin, line)) { + std::stringstream ss(line); + std::vector v; + while (getline(ss, line, ' ')) { + // store token string in the vector + v.push_back(line); + } + int tan_id = std::stoi(v[1]); + int loc_type = std::stoi(v[0].substr(0, 2)); + int loc_id = std::stoi(v[0].substr(2)); + loc_to_tan_id.insert({std::make_tuple(mio::abm::LocationType(loc_type), mio::abm::LocationId(loc_id)), tan_id}); + } + + // add cemetary + loc_to_tan_id.insert({std::make_tuple(mio::abm::LocationType::Cemetery, mio::abm::LocationId(0)), 0}); + + model.set_wastewater_ids(loc_to_tan_id); +} + +void initialize_model(mio::abm::Model& model, std::string person_file, std::string hosp_file, std::string outfile, + size_t max_work_size, size_t max_school_size) +{ + // Mapping of ABM locations to traffic areas/cells + // - each traffic area is mapped to a vector containing strings with LocationType and LocationId + std::map> loc_area_mapping; + // Mapping of traffic data location ids to ABM location ids + std::map home_locations; + std::map shop_locations; + std::map event_locations; + std::map school_locations; + std::map work_locations; + std::vector hospitals; + std::vector icus; + std::map, mio::abm::LocationId> hosp_to_icu; + std::vector hospital_weights; + std::vector icu_weights; + // Mapping of assigned agents to school and work locations + std::map> school_sizes; + std::map> work_sizes; + + // Read in hospitals + const boost::filesystem::path h = hosp_file; + if (!boost::filesystem::exists(h)) { + mio::log_error("Cannot read in data. Hospital file does not exist."); + } + // File pointer + std::fstream fin_hosp; + // Open an existing file + fin_hosp.open(hosp_file, std::ios::in); + std::vector row_hosp; + std::vector row_string_hosp; + std::string line_hosp; + // Read the Titles from the Data file + std::getline(fin_hosp, line_hosp); + line_hosp.erase(std::remove(line_hosp.begin(), line_hosp.end(), '\r'), line_hosp.end()); + std::vector titles_hosp; + boost::split(titles_hosp, line_hosp, boost::is_any_of(",")); + uint32_t count_of_titles_hosp = 0; + std::map index_hosp = {}; + for (auto const& title : titles_hosp) { + index_hosp.insert({title, count_of_titles_hosp}); + row_string_hosp.push_back(title); + count_of_titles_hosp++; + } + while (std::getline(fin_hosp, line_hosp)) { + row_hosp.clear(); + + // read columns in this row + split_line(line_hosp, &row_hosp); + line_hosp.erase(std::remove(line_hosp.begin(), line_hosp.end(), '\r'), line_hosp.end()); + + int beds = row_hosp[index_hosp["beds"]]; + int icu_beds = row_hosp[index_hosp["icu_beds"]]; + int hospital_zone = row_hosp[index_hosp["hospital_zone"]]; + auto hosp = model.add_location(mio::abm::LocationType::Hospital); + hospitals.push_back(hosp); + hospital_weights.push_back(beds); + std::string loc = + "0" + std::to_string(static_cast(mio::abm::LocationType::Hospital)) + std::to_string(hosp.get()); + auto zone_iter = loc_area_mapping.find(hospital_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({hospital_zone, {loc}}); + } + else { + loc_area_mapping[hospital_zone].push_back(loc); + } + // Add icu if there is one + if (icu_beds > 0) { + auto icu = model.add_location(mio::abm::LocationType::ICU); + icus.push_back(icu); + icu_weights.push_back(icu_beds); + hosp_to_icu.insert({std::make_pair(mio::abm::LocationType::Hospital, hosp), icu}); + std::string loc_icu = + "0" + std::to_string(static_cast(mio::abm::LocationType::ICU)) + std::to_string(icu.get()); + zone_iter = loc_area_mapping.find(hospital_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({hospital_zone, {loc_icu}}); + } + else { + loc_area_mapping[hospital_zone].push_back(loc_icu); + } + } + } + + // Read in persons + const boost::filesystem::path p = person_file; + if (!boost::filesystem::exists(p)) { + mio::log_error("Cannot read in data. File does not exist."); + } + // File pointer + std::fstream fin; + + // Open an existing file + fin.open(person_file, std::ios::in); + std::vector row; + std::vector row_string; + std::string line; + + // Read the Titles from the Data file + std::getline(fin, line); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + std::vector titles; + boost::split(titles, line, boost::is_any_of(",")); + uint32_t count_of_titles = 0; + std::map index = {}; + for (auto const& title : titles) { + index.insert({title, count_of_titles}); + row_string.push_back(title); + count_of_titles++; + } + + while (std::getline(fin, line)) { + row.clear(); + + // read columns in this row + split_line(line, &row); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + + uint32_t age = row[index["age"]]; + + int home_id = row[index["home_id"]]; + int home_zone = row[index["home_zone"]]; + + mio::abm::LocationId home; + + auto iter_home = home_locations.find(home_id); + // check whether home location already exists in model + if (iter_home == home_locations.end()) { + // if home location does not exists yet, create new location and insert it to mapping + home = model.add_location(mio::abm::LocationType::Home); + home_locations.insert({home_id, home}); + std::string loc = + "0" + std::to_string(static_cast(mio::abm::LocationType::Home)) + std::to_string(home.get()); + auto zone_iter = loc_area_mapping.find(home_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({home_zone, {loc}}); + } + else { + loc_area_mapping[home_zone].push_back(loc); + } + } + else { + home = home_locations[home_id]; + } + // Add person to model and assign home location to it + auto pid = model.add_person(home, determine_age_group(age)); + auto& person = model.get_person(pid); + person.set_assigned_location(mio::abm::LocationType::Home, home); + + int shop_id = row[index["shop_id"]]; + int shop_zone = row[index["shop_zone"]]; + + mio::abm::LocationId shop; + + auto iter_shop = shop_locations.find(shop_id); + // Check whether shop location already exists in model + if (iter_shop == shop_locations.end()) { + // Create shop location and add it to mapping + shop = model.add_location(mio::abm::LocationType::BasicsShop); + // Shops with ids -1 are individual locations each + if (shop_id != -1) { + shop_locations.insert({shop_id, shop}); + } + std::string loc = + "0" + std::to_string(static_cast(mio::abm::LocationType::BasicsShop)) + std::to_string(shop.get()); + auto zone_iter = loc_area_mapping.find(shop_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({shop_zone, {loc}}); + } + else { + loc_area_mapping[shop_zone].push_back(loc); + } + } + else { + shop = shop_locations[shop_id]; + } + // Assign shop to person + person.set_assigned_location(mio::abm::LocationType::BasicsShop, shop); + + int event_id = row[index["event_id"]]; + int event_zone = row[index["event_zone"]]; + + mio::abm::LocationId event; + + auto iter_event = event_locations.find(event_id); + // Check whether event location already exists in model + if (iter_event == event_locations.end()) { + //Create event location and add it to mapping + event = model.add_location(mio::abm::LocationType::SocialEvent); + // Events with id -1 are individual locations each + if (event_id != -1) { + event_locations.insert({event_id, event}); + } + std::string loc = "0" + std::to_string(static_cast(mio::abm::LocationType::SocialEvent)) + + std::to_string(event.get()); + auto zone_iter = loc_area_mapping.find(event_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({event_zone, {loc}}); + } + else { + loc_area_mapping[event_zone].push_back(loc); + } + } + else { + event = event_locations[event_id]; + } + // Assign event location to person + person.set_assigned_location(mio::abm::LocationType::SocialEvent, event); + + // Check if person is school-aged + if (person.get_age() == mio::AgeGroup(1)) { + int school_id = row[index["school_id"]]; + int school_zone = row[index["school_zone"]]; + + mio::abm::LocationId school; + + auto iter_school = school_locations.find(school_id); + // Check whether school location is already in model + if (iter_school == school_locations.end()) { + // Add schools locations to model and insert it in mapping + school = model.add_location(mio::abm::LocationType::School); + // schools with id -1 are individual locations each + if (school_id != -1) { + school_locations.insert({school_id, school}); + // Add school to size map + school_sizes[school_id].insert({school, 1}); + } + std::string loc = "0" + std::to_string(static_cast(mio::abm::LocationType::School)) + + std::to_string(school.get()); + auto zone_iter = loc_area_mapping.find(school_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({school_zone, {loc}}); + } + else { + loc_area_mapping[school_zone].push_back(loc); + } + } + else { + school = school_locations[school_id]; + if (school_sizes[school_id][school] == max_school_size) { + // Check if a new school has to be open or if there still is a school that has capacity + bool found = false; + for (auto const& [key, val] : school_sizes[school_id]) { + if (val < max_school_size) { + found = true; + school_sizes[school_id][key] += 1; + school = key; + break; + } + } + if (!found) { + // Create new school + school = model.add_location(mio::abm::LocationType::School); + school_locations.insert({school_id, school}); + // Add school to size map + school_sizes[school_id].insert({school, 1}); + std::string loc = "0" + std::to_string(static_cast(mio::abm::LocationType::School)) + + std::to_string(school.get()); + auto zone_iter = loc_area_mapping.find(school_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({school_zone, {loc}}); + } + else { + loc_area_mapping[school_zone].push_back(loc); + } + } + } + else { + school_sizes[school_id][school] += 1; + } + } + // Assign school location to person + person.set_assigned_location(mio::abm::LocationType::School, school); + } + // Check if person is work-aged + if (person.get_age() == mio::AgeGroup(2) || person.get_age() == mio::AgeGroup(3)) { + int work_id = row[index["work_id"]]; + int work_zone = row[index["work_zone"]]; + + if (work_zone == -2) { + mio::log_error("Person with id {} has work age but no work zone", row[index["puid"]]); + } + + mio::abm::LocationId work; + + auto iter_work = work_locations.find(work_id); + // Check whether work location already exists in model + if (iter_work == work_locations.end()) { + // Add work location to model and insert it in mapping + work = model.add_location(mio::abm::LocationType::Work); + // Locations with id -1 are individual locations each + if (work_id != -1) { + work_locations.insert({work_id, work}); + // Add work to size map + work_sizes[work_id].insert({work, 1}); + } + std::string loc = + "0" + std::to_string(static_cast(mio::abm::LocationType::Work)) + std::to_string(work.get()); + auto zone_iter = loc_area_mapping.find(work_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({work_zone, {loc}}); + } + else { + loc_area_mapping[work_zone].push_back(loc); + } + } + else { + work = work_locations[work_id]; + if (work_sizes[work_id][work] == max_work_size) { + // Check if a new work has to be opened or if there still is a school that has capacity + bool found = false; + for (auto const& [key, val] : work_sizes[work_id]) { + if (val < max_work_size) { + found = true; + work_sizes[work_id][key] += 1; + work = key; + break; + } + } + if (!found) { + // Create new work + work = model.add_location(mio::abm::LocationType::Work); + work_locations.insert({work_id, work}); + // Add work to size map + work_sizes[work_id].insert({work, 1}); + std::string loc = "0" + std::to_string(static_cast(mio::abm::LocationType::Work)) + + std::to_string(work.get()); + auto zone_iter = loc_area_mapping.find(work_zone); + if (zone_iter == loc_area_mapping.end()) { + loc_area_mapping.insert({work_zone, {loc}}); + } + else { + loc_area_mapping[work_zone].push_back(loc); + } + } + } + else { + work_sizes[work_id][work] += 1; + } + } + // Assign work location to person + person.set_assigned_location(mio::abm::LocationType::Work, work); + } + // Assign Hospital and ICU + size_t hosp = mio::DiscreteDistribution::get_instance()(model.get_rng(), hospital_weights); + person.set_assigned_location(mio::abm::LocationType::Hospital, hospitals[hosp]); + if (hosp_to_icu.count(std::make_pair(mio::abm::LocationType::Hospital, hospitals[hosp])) > 0) { + person.set_assigned_location( + mio::abm::LocationType::ICU, + hosp_to_icu[std::make_pair(mio::abm::LocationType::Hospital, hospitals[hosp])]); + } + else { + size_t icu = mio::DiscreteDistribution::get_instance()(model.get_rng(), icu_weights); + person.set_assigned_location(mio::abm::LocationType::ICU, icus[icu]); + } + } + + write_mapping_to_file(outfile, loc_area_mapping); +} + +void write_size_per_location(std::string out_file, mio::abm::Model& model) +{ + std::map size_per_loc; + // Count number of assigned persons for each location + for (auto& a : model.get_persons()) { + for (auto& loc_id : a.get_assigned_locations()) { + auto& loc = model.get_location(loc_id); + if (loc_id != mio::abm::LocationId::invalid_id() && loc.get_type() != mio::abm::LocationType::Cemetery) { + std::string loc_string = + "0" + std::to_string(static_cast(loc.get_type())) + std::to_string(loc.get_id().get()); + auto string_iter = size_per_loc.find(loc_string); + if (string_iter == size_per_loc.end()) { + size_per_loc.insert({loc_string, 1}); + } + else { + size_per_loc[loc_string] += 1; + } + } + } + } + //write map to file + auto file = fopen(out_file.c_str(), "w"); + if (file == NULL) { + mio::log(mio::LogLevel::warn, "Could not open file {}", out_file); + } + else { + for (auto it = size_per_loc.begin(); it != size_per_loc.end(); it++) { + fprintf(file, "%s", (it->first).c_str()); + fprintf(file, " %d", int(it->second)); + fprintf(file, "\n"); + } + fclose(file); + } +} + PYBIND11_MODULE(_simulation_abm, m) { pymio::iterable_enum(m, "InfectionState") @@ -46,7 +1161,8 @@ PYBIND11_MODULE(_simulation_abm, m) .value("InfectedSevere", mio::abm::InfectionState::InfectedSevere) .value("InfectedCritical", mio::abm::InfectionState::InfectedCritical) .value("Recovered", mio::abm::InfectionState::Recovered) - .value("Dead", mio::abm::InfectionState::Dead); + .value("Dead", mio::abm::InfectionState::Dead) + .value("Count", mio::abm::InfectionState::Count); pymio::iterable_enum(m, "ProtectionType") .value("NoProtection", mio::abm::ProtectionType::NoProtection) @@ -65,14 +1181,14 @@ PYBIND11_MODULE(_simulation_abm, m) .value("ICU", mio::abm::LocationType::ICU) .value("Car", mio::abm::LocationType::Car) .value("PublicTransport", mio::abm::LocationType::PublicTransport) - .value("TransportWithoutContact", mio::abm::LocationType::TransportWithoutContact); + .value("TransportWithoutContact", mio::abm::LocationType::TransportWithoutContact) + .value("Cemetery", mio::abm::LocationType::Cemetery); pymio::iterable_enum(m, "TestType") .value("Generic", mio::abm::TestType::Generic) .value("Antigen", mio::abm::TestType::Antigen) .value("PCR", mio::abm::TestType::PCR); - pymio::bind_class(m, "TimeSpan") .def(py::init(), py::arg("seconds") = 0) .def_property_readonly("seconds", &mio::abm::TimeSpan::seconds) @@ -117,7 +1233,7 @@ PYBIND11_MODULE(_simulation_abm, m) .def(py::self += mio::abm::TimeSpan{}) .def(py::self - mio::abm::TimeSpan{}) .def(py::self -= mio::abm::TimeSpan{}); - + pymio::bind_class(m, "TestParameters") .def(py::init()) .def_readwrite("sensitivity", &mio::abm::TestParameters::sensitivity) @@ -128,6 +1244,7 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_CustomIndexArray, mio::abm::VirusVariant, mio::AgeGroup>( m, "_AgeParameterArray"); pymio::bind_CustomIndexArray(m, "_TestData"); + pymio::bind_CustomIndexArray(m, "_InfectionRateArray"); pymio::bind_Index(m, "ProtectionTypeIndex"); pymio::bind_ParameterSet(m, "ParametersBase"); pymio::bind_class(m, "Parameters") @@ -149,10 +1266,38 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_class(m, "Person") .def("set_assigned_location", py::overload_cast(&mio::abm::Person::set_assigned_location)) + .def("add_new_infection", + [](mio::abm::Person& self, mio::abm::Infection& infection, mio::abm::TimePoint t) { + self.add_new_infection(std::move(infection), t); + }) + .def("assigned_location", + [](mio::abm::Person& self, mio::abm::LocationType type) { + return self.get_assigned_location(type); + }) + .def("infection_state", + [](mio::abm::Person& self, mio::abm::TimePoint t) { + return self.get_infection_state(t); + }) + .def_property_readonly("infection", py::overload_cast<>(&mio::abm::Person::get_infection, py::const_)) .def_property_readonly("location", py::overload_cast<>(&mio::abm::Person::get_location, py::const_)) .def_property_readonly("age", &mio::abm::Person::get_age) + .def_property_readonly("id", &mio::abm::Person::get_id) .def_property_readonly("is_in_quarantine", &mio::abm::Person::is_in_quarantine); + pymio::bind_class(m, "HouseholdMember") + .def(py::init()) + .def("set_age_weight", &mio::abm::HouseholdMember::set_age_weight); + + pymio::bind_class(m, "Household") + .def(py::init<>()) + .def("add_members", &mio::abm::Household::add_members); + + m.def("add_household_group_to_model", &mio::abm::add_household_group_to_model); + + pymio::bind_class(m, "HouseholdGroup") + .def(py::init<>()) + .def("add_households", &mio::abm::HouseholdGroup::add_households); + pymio::bind_class(m, "TestingCriteria") .def(py::init&, const std::vector&>(), py::arg("age_groups"), py::arg("infection_states")); @@ -172,7 +1317,21 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_class(m, "TestingStrategy") .def(py::init&>()); + pymio::bind_class(m, "Infection") + .def(py::init([](mio::abm::Model& model, mio::abm::Person& person, mio::abm::VirusVariant variant, + mio::abm::TimePoint start_date, mio::abm::InfectionState start_state, bool detected, + bool shift_init, double shift_rate) { + auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); + return mio::abm::Infection(rng, variant, person.get_age(), model.parameters, start_date, start_state, + person.get_latest_protection(), detected, shift_init, shift_rate); + })) + .def("get_infection_start", &mio::abm::Infection::get_infection_start) + .def("get_time_in_state", [](mio::abm::Infection& self, mio::abm::InfectionState state) { + return self.get_time_in_state(state); + }); + pymio::bind_class(m, "Location") + .def("set_capacity", &mio::abm::Location::set_capacity) .def_property_readonly("type", &mio::abm::Location::get_type) .def_property_readonly("id", &mio::abm::Location::get_id) .def_property("infection_parameters", @@ -208,6 +1367,13 @@ PYBIND11_MODULE(_simulation_abm, m) .def("add_person", py::overload_cast(&mio::abm::Model::add_person), py::arg("location_id"), py::arg("age_group")) .def("assign_location", &mio::abm::Model::assign_location, py::arg("person_id"), py::arg("location_id")) + .def("get_location", py::overload_cast(&mio::abm::Model::get_location, py::const_), + py::return_value_policy::reference_internal) + .def("add_infection_rate_damping", &mio::abm::Model::add_infection_rate_damping, py::arg("t"), + py::arg("factor")) + .def("add_location_closure", &mio::abm::Model::add_location_closure, py::arg("t"), py::arg("loc_type"), + py::arg("percentage")) + .def("get_rng", &mio::abm::Model::get_rng, py::return_value_policy::reference_internal) .def_property_readonly("locations", py::overload_cast<>(&mio::abm::Model::get_locations, py::const_), py::keep_alive<1, 0>{}) //keep this model alive while contents are referenced in ranges .def_property_readonly("persons", py::overload_cast<>(&mio::abm::Model::get_persons, py::const_), @@ -230,11 +1396,231 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_class(m, "Simulation") .def(py::init()) + .def("advance", + &mio::abm::Simulation::advance>) .def("advance", static_cast(&mio::abm::Simulation::advance), py::arg("tmax")) .def_property_readonly("model", py::overload_cast<>(&mio::abm::Simulation::get_model)); + pymio::bind_class, + pymio::EnablePickling::Never>(m, "History") + .def(py::init<>()) + .def_property_readonly("log", [](mio::History& self) { + return self.get_log(); + }); + + m.def( + "set_viral_load_parameters", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double min_peak, + double max_peak, double min_incline, double max_incline, double min_decline, double max_decline) { + infection_params.get()[{variant, age}] = + mio::abm::ViralLoadDistributionsParameters{ + {min_peak, max_peak}, {min_incline, max_incline}, {min_decline, max_decline}}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_incubationPeriod", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedNoSymptomsToSymptoms", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedNoSymptomsToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSymptomsToSevere", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSymptomsToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSevereToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSevereToCritical", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedCriticalToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedCriticalToDead", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double my, + double sigma) { + infection_params.get()[{variant, age}] = {my, sigma}; + }, + py::return_value_policy::reference_internal); + + m.def("set_AgeGroupGoToSchool", [](mio::abm::Parameters& infection_params, mio::AgeGroup age) { + infection_params.get()[age] = true; + }); + + m.def("set_AgeGroupGoToWork", [](mio::abm::Parameters& infection_params, mio::AgeGroup age) { + infection_params.get()[age] = true; + }); + + m.def("set_AgeGroupGoToShop", [](mio::abm::Parameters& infection_params, mio::AgeGroup age) { + infection_params.get()[age] = true; + }); + + m.def( + "set_infectivity_parameters", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double min_alpha, + double max_alpha, double min_beta, double max_beta) { + infection_params.get()[{variant, age}] = + mio::abm::InfectivityDistributionsParameters{{min_alpha, max_alpha}, {min_beta, max_beta}}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_seeds", + [](mio::abm::Model& model, int seed) { + auto rng = mio::RandomNumberGenerator(); + rng.seed({static_cast(seed)}); + model.get_rng() = rng; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_log_level_warn", + []() { + mio::set_log_level(mio::LogLevel::warn); + }, + py::return_value_policy::reference_internal); + + m.def("initialize_model", &initialize_model, py::return_value_policy::reference_internal); + m.def("save_infection_paths", &write_infection_paths, py::return_value_policy::reference_internal); + m.def("save_comp_output", &write_compartments, py::return_value_policy::reference_internal); + m.def("set_wastewater_ids", &set_wastewater_ids, py::return_value_policy::reference_internal); + m.def("write_size_per_location", &write_size_per_location, py::return_value_policy::reference_internal); + +#ifdef MEMILIO_HAS_HDF5 + m.def( + "write_h5", + [](std::string filename, mio::History& history) { + auto res = write_h5(filename, history); + if (res == mio::success()) { + return 0; + } + else { + return 1; + } + }, + py::return_value_policy::reference_internal); + + m.def( + "write_h5_v2", + [](std::string filename, mio::History& history) { + auto res = write_h5_v2(filename, history); + if (res == mio::success()) { + return 0; + } + else { + return 1; + } + }, + py::return_value_policy::reference_internal); + + m.def( + "write_h5_v3", + [](std::string filename, mio::History& history) { + auto res = write_h5_v3(filename, history); + if (res == mio::success()) { + return 0; + } + else { + return 1; + } + }, + py::return_value_policy::reference_internal); + + m.def( + "write_h5_v4", + [](std::string filename, mio::History& history) { + auto res = write_h5_v4(filename, history); + if (res == mio::success()) { + return 0; + } + else { + return 1; + } + }, + py::return_value_policy::reference_internal); + + m.def( + "write_h5_v5", + [](std::string filename, mio::History& history) { + auto res = write_h5_v5(filename, history); + if (res == mio::success()) { + return 0; + } + else { + return 1; + } + }, + py::return_value_policy::reference_internal); + + m.def( + "calculate_infections_per_quantity", + [](std::string sim_result_folder, std::string save_folder, int num_sims) { + std::string save_file_loctype_infections = save_folder + "num_agents_infections_loctype.txt"; + std::string save_file_ww_area_infections = save_folder + "num_agents_infections_area.txt"; + calculate_infections_per_quantity(sim_result_folder, save_file_loctype_infections, "locType", "v5", + num_sims); + calculate_infections_per_quantity(sim_result_folder, save_file_ww_area_infections, "area", "v4", num_sims); + }, + py::return_value_policy::reference_internal); +#endif + m.attr("__version__") = "dev"; } diff --git a/pycode/memilio-simulation/memilio/simulation/bindings/simulation.cpp b/pycode/memilio-simulation/memilio/simulation/bindings/simulation.cpp index a64c8287fb..3c05694652 100755 --- a/pycode/memilio-simulation/memilio/simulation/bindings/simulation.cpp +++ b/pycode/memilio-simulation/memilio/simulation/bindings/simulation.cpp @@ -59,7 +59,7 @@ PYBIND11_MODULE(_simulation, m) pymio::bind_CustomIndexArray, mio::AgeGroup>(m, "AgeGroupArray"); pymio::bind_class>(m, "AgeGroup") .def(py::init()); - + pymio::bind_CustomIndexArray(m, "AgeGroupSimulationDayArray"); pymio::bind_class>(m, "SimulationDay") @@ -127,7 +127,7 @@ PYBIND11_MODULE(_simulation, m) return std::vector>(h.begin(), h.end()); }, py::arg("state_id"), py::arg("start_date") = mio::Date(std::numeric_limits::min(), 1, 1), - py::arg("end_date") = mio::Date(std::numeric_limits::max(), 1, 1)); + py::arg("end_date") = mio::Date(std::numeric_limits::max(), 1, 1)); m.def( "read_mobility_plain", diff --git a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py index f14bbbc5e1..3ea993f162 100644 --- a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py +++ b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py @@ -98,9 +98,7 @@ def test_simulation(self): model.assign_location(p1_id, loc_id) model.assign_location(p2_id, loc_id) - model.parameters.InfectedSymptomsToSevere[abm.VirusVariant.Wildtype, mio.AgeGroup( - 0)] = 0.0 - model.parameters.InfectedSymptomsToRecovered[abm.VirusVariant.Wildtype, mio.AgeGroup( + model.parameters.SeverePerInfectedSymptoms[abm.VirusVariant.Wildtype, mio.AgeGroup( 0)] = 0.0 # trips