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/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..9ca1b2f9ce 100644 --- a/cpp/models/abm/infection.cpp +++ b/cpp/models/abm/infection.cpp @@ -19,7 +19,11 @@ */ #include "abm/infection.h" +#include +#include +#include #include +#include namespace mio { @@ -37,24 +41,35 @@ Infection::Infection(PersonalRandomNumberGenerator& rng, VirusVariant virus, Age 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 +90,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 +118,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,6 +151,19 @@ 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) @@ -129,6 +181,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,21 +191,22 @@ 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)); + 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 - next_state = InfectionState::InfectedSymptoms; + 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)); + next_state = InfectionState::InfectedSymptoms; } 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)); + next_state = InfectionState::Recovered; } break; @@ -159,47 +214,52 @@ 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 - next_state = InfectionState::InfectedSevere; + // 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)); + next_state = InfectionState::InfectedSevere; } 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)); + next_state = InfectionState::Recovered; } break; } 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)); + 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)); + 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)); + next_state = InfectionState::Dead; } 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)); + next_state = InfectionState::Recovered; } break; default: @@ -214,9 +274,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 +285,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..247e98cf95 100644 --- a/cpp/models/abm/infection.h +++ b/cpp/models/abm/infection.h @@ -109,6 +109,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 +148,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; @@ -184,6 +204,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/mobility_rules.cpp b/cpp/models/abm/mobility_rules.cpp index 32858037f7..df3961d6c8 100644 --- a/cpp/models/abm/mobility_rules.cpp +++ b/cpp/models/abm/mobility_rules.cpp @@ -56,7 +56,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 +76,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; @@ -106,7 +108,8 @@ LocationType go_to_event(PersonalRandomNumberGenerator& rng, const Person& perso 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)) && + ((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, diff --git a/cpp/models/abm/model.h b/cpp/models/abm/model.h index ea9f49c90c..89b1fea30d 100644 --- a/cpp/models/abm/model.h +++ b/cpp/models/abm/model.h @@ -112,7 +112,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 +131,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 +142,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 +154,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); } /** diff --git a/cpp/models/abm/model_functions.cpp b/cpp/models/abm/model_functions.cpp index 9e8a0ae78e..d2e3fc6fc8 100644 --- a/cpp/models/abm/model_functions.cpp +++ b/cpp/models/abm/model_functions.cpp @@ -96,6 +96,7 @@ 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, diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 204e2eb77d..e5f7085c8f 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 * @@ -37,23 +37,22 @@ #include "memilio/epidemiology/damping.h" #include "memilio/epidemiology/contact_matrix.h" -#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 +60,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 "TimeInfectedCriticalToDead"; + } +}; + +/** + * @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 "SevereToCritical"; + return "TimeInfectedCriticalToRecovered"; } }; -struct SevereToRecovered { +/** +* @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 +305,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 +356,21 @@ 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 Aerosol transmission rates. */ @@ -274,7 +378,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 +593,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 +653,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. */ @@ -550,12 +714,15 @@ struct AgeGroupGotoWork { }; using ParametersBase = - ParameterSet; @@ -643,116 +810,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()[{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) { - 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 TimeInfectedNoSymptomsToRecovered 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 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 InfectedNoSymptomsToRecovered of age group {:.0f} smaller " + 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}", - (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 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 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 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 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 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 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 TimeInfectedCriticalToRecovered 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}] < 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..24c6e836c4 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) @@ -81,11 +84,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 +146,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 +164,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 +246,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 +272,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/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/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.rst b/pycode/examples/simulation/ABM Demonstrator/README.rst new file mode 100644 index 0000000000..fbff0e3993 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/README.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/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..c97f51cfa5 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/abm_demonstrator_munich.py @@ -0,0 +1,890 @@ +############################################################################# +# 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 + +# 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_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 age_group_to_string(age_group): + if (age_group == age_group_0_to_4): + return "0" + elif (age_group == age_group_5_to_15): + return "1" + elif (age_group == age_group_16_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_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 read_txt(path): + # read input file and save it in a pd.Dataframe + return pd.read_csv(path, sep='\t') + + +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 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 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 age_to_age_group(age): + if (age < 5): + return age_group_0_to_4 + elif (age < 15): + return age_group_5_to_15 + elif (age < 35): + return age_group_16_to_34 + elif (age < 60): + return age_group_35_to_59 + elif (age < 80): + return age_group_60_to_79 + else: + return age_group_80_plus + + +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 + + # 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): + 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 = {} + for traffic_cell_id in d.keys(): + if traffic_cell_id[:4] != '9162': + 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] + 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() + print(' ') + + +def run_abm_simulation(sim_num): + 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) + # create simulation with starting timepoint and number of age groups + sim = abm.Simulation(t0, num_age_groups) + start_init = time.time() + # initialize model + abm.initialize_model(sim.model, input_path + 'persons.csv', 50, os.path.join( + output_path, str(sim_num) + '_mapping.txt')) + + # read infection parameters + parameters = pd.read_csv(os.path.join( + input_path, 'parameter_table.csv'), index_col=0) + # 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_15) + abm.set_AgeGroupGoToWork(sim.model.parameters, age_group_16_to_34) + abm.set_AgeGroupGoToWork(sim.model.parameters, age_group_35_to_59) + # 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) + end_init = time.time() + print(f'Time for model initialization: {end_init - start_init} seconds') + + # map locations to TAN areas + start_map = time.time() + map_traffic_cell_to_wastewater_area(os.path.join( + output_path, str(sim_num) + '_mapping.txt'), os.path.join(input_path, '_AusgangDLR/München_Flächen_bearb.shp'), os.path.join( + output_path, str(sim_num) + '_mapping_tan.txt')) + end_map = time.time() + print( + f'Time for mapping locations to TAN areas: {end_map - start_map} seconds') + + # 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') + # 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') + # 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') + # write results to h5 file. 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 = time.time() + abm.write_h5(os.path.join( + output_path, str(sim_num) + '_output.h5'), history) + end_h5 = time.time() + print(f'Time to write output h5: {end_h5 - start_h5} seconds') + + print('done') + + +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() + for i in range(1, 2): + run_abm_simulation(i, **args.__dict__) 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..50b211a6b0 --- /dev/null +++ b/pycode/examples/simulation/ABM Demonstrator/input/parameter_table.csv @@ -0,0 +1,86 @@ +parameter,value,dev +Age0to4_IncubationPeriod,3,1.2 +Age0to4_InfectedNoSymptomsToSymptoms,2.2,0.5 +Age0to4_InfectedNoSymptomsToRecovered,9.2,2.0 +Age0to4_InfectedSymptomsToRecovered,7,2.0 +Age0to4_InfectedSymptomsToSevere,10.5,1.1 +Age0to4_SevereToRecovered,5,2.0 +Age0to4_SevereToCritical,5,2.0 +Age0to4_CriticalToRecovered,7,3.0 +Age0to4_CriticalToDead,6,2.0 +Age0to4_SymptomsPerInfectedNoSymptoms,0.79,0.0 +Age0to4_SeverePerInfectedSymptoms,0.08,0.0 +Age0to4_CriticalPerInfectedSevere,0.18,0.0 +Age0to4_DeathsPerInfectedCritical,0.1,0.0 +Age5to14_IncubationPeriod,3,1.2 +Age5to14_InfectedNoSymptomsToSymptoms,2.2,0.5 +Age5to14_InfectedNoSymptomsToRecovered,9.2,2.0 +Age5to14_InfectedSymptomsToRecovered,7,2.0 +Age5to14_InfectedSymptomsToSevere,10.5,1.1 +Age5to14_SevereToRecovered,5,2.0 +Age5to14_SevereToCritical,5,2.0 +Age5to14_CriticalToRecovered,7,3.0 +Age5to14_CriticalToDead,6,2.0 +Age5to14_SymptomsPerInfectedNoSymptoms,0.79,0.0 +Age5to14_SeverePerInfectedSymptoms,0.08,0.0 +Age5to14_CriticalPerInfectedSevere,0.18,0.0 +Age5to14_DeathsPerInfectedCritical,0.1,0.0 +Age15to34_IncubationPeriod,3,1.2 +Age15to34_InfectedNoSymptomsToSymptoms,2.2,0.5 +Age15to34_InfectedNoSymptomsToRecovered,9.2,2.0 +Age15to34_InfectedSymptomsToRecovered,7,2.0 +Age15to34_InfectedSymptomsToSevere,10.5,1.1 +Age15to34_SevereToRecovered,6,2.0 +Age15to34_SevereToCritical,5,2.0 +Age15to34_CriticalToRecovered,7,3.0 +Age15to34_CriticalToDead,6,2.0 +Age15to34_SymptomsPerInfectedNoSymptoms,0.79,0.0 +Age15to34_SeverePerInfectedSymptoms,0.08,0.0 +Age15to34_CriticalPerInfectedSevere,0.18,0.0 +Age15to34_DeathsPerInfectedCritical,0.1,0.0 +Age35to59_IncubationPeriod,3,1.2 +Age35to59_InfectedNoSymptomsToSymptoms,2.2,0.5 +Age35to59_InfectedNoSymptomsToRecovered,9.2,2.0 +Age35to59_InfectedSymptomsToRecovered,7,2.0 +Age35to59_InfectedSymptomsToSevere,6,1.1 +Age35to59_SevereToRecovered,8,2.0 +Age35to59_SevereToCritical,5,2.0 +Age35to59_CriticalToRecovered,17.5,3.0 +Age35to59_CriticalToDead,16.5,2.0 +Age35to59_SymptomsPerInfectedNoSymptoms,0.79,0.0 +Age35to59_SeverePerInfectedSymptoms,0.08,0.0 +Age35to59_CriticalPerInfectedSevere,0.18,0.0 +Age35to59_DeathsPerInfectedCritical,0.1,0.0 +Age60to79_IncubationPeriod,3,1.2 +Age60to79_InfectedNoSymptomsToSymptoms,2.2,0.5 +Age60to79_InfectedNoSymptomsToRecovered,9.2,2.0 +Age60to79_InfectedSymptomsToRecovered,7,2.0 +Age60to79_InfectedSymptomsToSevere,6,1.1 +Age60to79_SevereToRecovered,10,2.0 +Age60to79_SevereToCritical,5,2.0 +Age60to79_CriticalToRecovered,17.5,3.0 +Age60to79_CriticalToDead,16.5,2.0 +Age60to79_SymptomsPerInfectedNoSymptoms,0.79,0.0 +Age60to79_SeverePerInfectedSymptoms,0.08,0.0 +Age60to79_CriticalPerInfectedSevere,0.18,0.0 +Age60to79_DeathsPerInfectedCritical,0.1,0.0 +Age80plus_IncubationPeriod,3,1.2 +Age80plus_InfectedNoSymptomsToSymptoms,2.2,0.5 +Age80plus_InfectedNoSymptomsToRecovered,9.2,2.0 +Age80plus_InfectedSymptomsToRecovered,7,2.0 +Age80plus_InfectedSymptomsToSevere,6,1.1 +Age80plus_SevereToRecovered,15,3.0 +Age80plus_SevereToCritical,5,2.0 +Age80plus_CriticalToRecovered,12.5,3.0 +Age80plus_CriticalToDead,11,2.0 +Age80plus_SymptomsPerInfectedNoSymptoms,0.79,0.0 +Age80plus_SeverePerInfectedSymptoms,0.08,0.0 +Age80plus_CriticalPerInfectedSevere,0.18,0.0 +Age80plus_DeathsPerInfectedCritical,0.1,0.0 +incline,2 +decline,-0.17 +alpha,-7 +beta,1 +t_limit,1.3 +peak_max,8.1 +s_sc,12589254.1179417 \ No newline at end of file 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..f58f24f0d1 100644 --- a/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp @@ -17,25 +17,574 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -//Includes from pymio +#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 "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 "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 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()) { + 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()))); + } + 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() > 1000 ? -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(); +} +#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 initialize_model(mio::abm::Model& model, std::string person_file, size_t num_hospitals, std::string outfile) +{ + // 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; + + 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++; + } + + // Create hospitals and ICU + for (auto i = size_t(0); i < num_hospitals; ++i) { + auto hosp = model.add_location(mio::abm::LocationType::Hospital); + auto icu = model.add_location(mio::abm::LocationType::ICU); + hospitals.push_back(hosp); + icus.push_back(icu); + } + + 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}); + } + 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]; + } + // 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"]]; + + 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}); + } + 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]; + } + // Assign work location to person + person.set_assigned_location(mio::abm::LocationType::Work, work); + } + // Assign Hospital and ICU + std::vector::iterator randItHosp = hospitals.begin(); + std::advance(randItHosp, std::rand() % hospitals.size()); + person.set_assigned_location(mio::abm::LocationType::Hospital, *randItHosp); + std::vector::iterator randItIcu = icus.begin(); + std::advance(randItIcu, std::rand() % icus.size()); + person.set_assigned_location(mio::abm::LocationType::ICU, *randItIcu); + } + + // Add hospitals to Mapping + for (size_t i = size_t(0); i < hospitals.size(); ++i) { + auto it = loc_area_mapping.begin(); + std::advance(it, rand() % loc_area_mapping.size()); + loc_area_mapping[it->first].push_back("0" + std::to_string(static_cast(mio::abm::LocationType::Hospital)) + + std::to_string(hospitals[i].get())); + loc_area_mapping[it->first].push_back("0" + std::to_string(static_cast(mio::abm::LocationType::ICU)) + + std::to_string(icus[i].get())); + } + write_mapping_to_file(outfile, loc_area_mapping); +} + PYBIND11_MODULE(_simulation_abm, m) { pymio::iterable_enum(m, "InfectionState") @@ -46,7 +595,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 +615,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 +667,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) @@ -149,10 +699,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 +750,20 @@ 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) { + 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); + })) + .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 +799,9 @@ 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("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 +824,176 @@ 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 mean, + double std) { + auto incubation_period_params = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = {incubation_period_params.first, + incubation_period_params.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedNoSymptomsToSymptoms", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedNoSymptomsToSymptoms = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedNoSymptomsToSymptoms.first, TimeInfectedNoSymptomsToSymptoms.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedNoSymptomsToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedNoSymptomsToRecovered = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedNoSymptomsToRecovered.first, TimeInfectedNoSymptomsToRecovered.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSymptomsToSevere", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedSymptomsToSevere = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedSymptomsToSevere.first, TimeInfectedSymptomsToSevere.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSymptomsToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedSymptomsToRecovered = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedSymptomsToRecovered.first, TimeInfectedSymptomsToRecovered.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSevereToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedSevereToRecovered = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedSevereToRecovered.first, TimeInfectedSevereToRecovered.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedSevereToCritical", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedSevereToCritical = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedSevereToCritical.first, TimeInfectedSevereToCritical.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedCriticalToRecovered", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedCriticalToRecovered = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedCriticalToRecovered.first, TimeInfectedCriticalToRecovered.second}; + }, + py::return_value_policy::reference_internal); + + m.def( + "set_TimeInfectedCriticalToDead", + [](mio::abm::Parameters& infection_params, mio::abm::VirusVariant variant, mio::AgeGroup age, double mean, + double std) { + auto TimeInfectedCriticalToDead = get_my_and_sigma(mean, std); + infection_params.get()[{variant, age}] = { + TimeInfectedCriticalToDead.first, TimeInfectedCriticalToDead.second}; + }, + 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_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); + +#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); +#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