|
| 1 | +/* |
| 2 | +* Copyright (C) 2020-2025 MEmilio |
| 3 | +* |
| 4 | +* Authors: Rene Schmieding, Sascha Korf |
| 5 | +* |
| 6 | +* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de> |
| 7 | +* |
| 8 | +* Licensed under the Apache License, Version 2.0 (the "License"); |
| 9 | +* you may not use this file except in compliance with the License. |
| 10 | +* You may obtain a copy of the License at |
| 11 | +* |
| 12 | +* http://www.apache.org/licenses/LICENSE-2.0 |
| 13 | +* |
| 14 | +* Unless required by applicable law or agreed to in writing, software |
| 15 | +* distributed under the License is distributed on an "AS IS" BASIS, |
| 16 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 17 | +* See the License for the specific language governing permissions and |
| 18 | +* limitations under the License. |
| 19 | +*/ |
| 20 | +#include "abm/result_simulation.h" |
| 21 | +#include "abm/household.h" |
| 22 | +#include "abm/lockdown_rules.h" |
| 23 | +#include "abm/model.h" |
| 24 | +#include "abm/time.h" |
| 25 | + |
| 26 | +#include "memilio/compartments/parameter_studies.h" |
| 27 | +#include "memilio/data/analyze_result.h" |
| 28 | +#include "memilio/io/io.h" |
| 29 | +#include "memilio/io/result_io.h" |
| 30 | +#include "memilio/utils/base_dir.h" |
| 31 | +#include "memilio/utils/logging.h" |
| 32 | +#include "memilio/utils/miompi.h" |
| 33 | +#include "memilio/utils/random_number_generator.h" |
| 34 | +#include "memilio/utils/stl_util.h" |
| 35 | + |
| 36 | +#include <string> |
| 37 | + |
| 38 | +constexpr size_t num_age_groups = 4; |
| 39 | + |
| 40 | +/// An ABM setup taken from abm_minimal.cpp. |
| 41 | +mio::abm::Model make_model(const mio::RandomNumberGenerator& rng) |
| 42 | +{ |
| 43 | + |
| 44 | + const auto age_group_0_to_4 = mio::AgeGroup(0); |
| 45 | + const auto age_group_5_to_14 = mio::AgeGroup(1); |
| 46 | + const auto age_group_15_to_34 = mio::AgeGroup(2); |
| 47 | + const auto age_group_35_to_59 = mio::AgeGroup(3); |
| 48 | + // Create the model with 4 age groups. |
| 49 | + auto model = mio::abm::Model(num_age_groups); |
| 50 | + model.get_rng() = rng; |
| 51 | + |
| 52 | + // Set same infection parameter for all age groups. For example, the incubation period is log normally distributed with parameters 4 and 1. |
| 53 | + model.parameters.get<mio::abm::TimeExposedToNoSymptoms>() = mio::ParameterDistributionLogNormal(4., 1.); |
| 54 | + |
| 55 | + // Set the age groups that can go to school; here this is AgeGroup(1) (i.e. 5-14) |
| 56 | + model.parameters.get<mio::abm::AgeGroupGotoSchool>() = false; |
| 57 | + model.parameters.get<mio::abm::AgeGroupGotoSchool>()[age_group_5_to_14] = true; |
| 58 | + // Set the age groups that can go to work; here these are AgeGroup(2) and AgeGroup(3) (i.e. 15-34 and 35-59) |
| 59 | + model.parameters.get<mio::abm::AgeGroupGotoWork>().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); |
| 60 | + |
| 61 | + // Check if the parameters satisfy their constraints. |
| 62 | + model.parameters.check_constraints(); |
| 63 | + |
| 64 | + // There are 10 households for each household group. |
| 65 | + int n_households = 10; |
| 66 | + |
| 67 | + // For more than 1 family households we need families. These are parents and children and randoms (which are distributed like the data we have for these households). |
| 68 | + auto child = mio::abm::HouseholdMember(num_age_groups); // A child is 50/50% 0-4 or 5-14. |
| 69 | + child.set_age_weight(age_group_0_to_4, 1); |
| 70 | + child.set_age_weight(age_group_5_to_14, 1); |
| 71 | + |
| 72 | + auto parent = mio::abm::HouseholdMember(num_age_groups); // A parent is 50/50% 15-34 or 35-59. |
| 73 | + parent.set_age_weight(age_group_15_to_34, 1); |
| 74 | + parent.set_age_weight(age_group_35_to_59, 1); |
| 75 | + |
| 76 | + // Two-person household with one parent and one child. |
| 77 | + auto twoPersonHousehold_group = mio::abm::HouseholdGroup(); |
| 78 | + auto twoPersonHousehold_full = mio::abm::Household(); |
| 79 | + twoPersonHousehold_full.add_members(child, 1); |
| 80 | + twoPersonHousehold_full.add_members(parent, 1); |
| 81 | + twoPersonHousehold_group.add_households(twoPersonHousehold_full, n_households); |
| 82 | + add_household_group_to_model(model, twoPersonHousehold_group); |
| 83 | + |
| 84 | + // Three-person household with two parent and one child. |
| 85 | + auto threePersonHousehold_group = mio::abm::HouseholdGroup(); |
| 86 | + auto threePersonHousehold_full = mio::abm::Household(); |
| 87 | + threePersonHousehold_full.add_members(child, 1); |
| 88 | + threePersonHousehold_full.add_members(parent, 2); |
| 89 | + threePersonHousehold_group.add_households(threePersonHousehold_full, n_households); |
| 90 | + add_household_group_to_model(model, threePersonHousehold_group); |
| 91 | + |
| 92 | + // Add one social event with 5 maximum contacts. |
| 93 | + // Maximum contacts limit the number of people that a person can infect while being at this location. |
| 94 | + auto event = model.add_location(mio::abm::LocationType::SocialEvent); |
| 95 | + model.get_location(event).get_infection_parameters().set<mio::abm::MaximumContacts>(5); |
| 96 | + // Add hospital and ICU with 5 maximum contacs. |
| 97 | + auto hospital = model.add_location(mio::abm::LocationType::Hospital); |
| 98 | + model.get_location(hospital).get_infection_parameters().set<mio::abm::MaximumContacts>(5); |
| 99 | + auto icu = model.add_location(mio::abm::LocationType::ICU); |
| 100 | + model.get_location(icu).get_infection_parameters().set<mio::abm::MaximumContacts>(5); |
| 101 | + // Add one supermarket, maximum constacts are assumed to be 20. |
| 102 | + auto shop = model.add_location(mio::abm::LocationType::BasicsShop); |
| 103 | + model.get_location(shop).get_infection_parameters().set<mio::abm::MaximumContacts>(20); |
| 104 | + // At every school, the maximum contacts are 20. |
| 105 | + auto school = model.add_location(mio::abm::LocationType::School); |
| 106 | + model.get_location(school).get_infection_parameters().set<mio::abm::MaximumContacts>(20); |
| 107 | + // At every workplace, maximum contacts are 20. |
| 108 | + auto work = model.add_location(mio::abm::LocationType::Work); |
| 109 | + model.get_location(work).get_infection_parameters().set<mio::abm::MaximumContacts>(20); |
| 110 | + |
| 111 | + // Increase aerosol transmission for all locations |
| 112 | + model.parameters.get<mio::abm::AerosolTransmissionRates>() = 10.0; |
| 113 | + // Increase contact rate for all people between 15 and 34 (i.e. people meet more often in the same location) |
| 114 | + model.get_location(work) |
| 115 | + .get_infection_parameters() |
| 116 | + .get<mio::abm::ContactRates>()[{age_group_15_to_34, age_group_15_to_34}] = 10.0; |
| 117 | + |
| 118 | + // People can get tested at work (and do this with 0.5 probability) from time point 0 to day 10. |
| 119 | + auto validity_period = mio::abm::days(1); |
| 120 | + auto probability = 0.5; |
| 121 | + auto start_date = mio::abm::TimePoint(0); |
| 122 | + auto end_date = mio::abm::TimePoint(0) + mio::abm::days(10); |
| 123 | + auto test_type = mio::abm::TestType::Antigen; |
| 124 | + auto test_parameters = model.parameters.get<mio::abm::TestData>()[test_type]; |
| 125 | + auto testing_criteria_work = mio::abm::TestingCriteria(); |
| 126 | + auto testing_scheme_work = mio::abm::TestingScheme(testing_criteria_work, validity_period, start_date, end_date, |
| 127 | + test_parameters, probability); |
| 128 | + model.get_testing_strategy().add_scheme(mio::abm::LocationType::Work, testing_scheme_work); |
| 129 | + |
| 130 | + // Assign infection state to each person. |
| 131 | + // The infection states are chosen randomly with the following distribution |
| 132 | + std::vector<ScalarType> infection_distribution{0.5, 0.3, 0.05, 0.05, 0.05, 0.05, 0.0, 0.0}; |
| 133 | + for (auto& person : model.get_persons()) { |
| 134 | + mio::abm::InfectionState infection_state = mio::abm::InfectionState( |
| 135 | + mio::DiscreteDistribution<size_t>::get_instance()(mio::thread_local_rng(), infection_distribution)); |
| 136 | + auto person_rng = mio::abm::PersonalRandomNumberGenerator(person); |
| 137 | + if (infection_state != mio::abm::InfectionState::Susceptible) { |
| 138 | + person.add_new_infection(mio::abm::Infection(person_rng, mio::abm::VirusVariant::Wildtype, person.get_age(), |
| 139 | + model.parameters, start_date, infection_state)); |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + // Assign locations to the people |
| 144 | + for (auto& person : model.get_persons()) { |
| 145 | + const auto id = person.get_id(); |
| 146 | + //assign shop and event |
| 147 | + model.assign_location(id, event); |
| 148 | + model.assign_location(id, shop); |
| 149 | + //assign hospital and ICU |
| 150 | + model.assign_location(id, hospital); |
| 151 | + model.assign_location(id, icu); |
| 152 | + //assign work/school to people depending on their age |
| 153 | + if (person.get_age() == age_group_5_to_14) { |
| 154 | + model.assign_location(id, school); |
| 155 | + } |
| 156 | + if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { |
| 157 | + model.assign_location(id, work); |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + // During the lockdown, social events are closed for 90% of people. |
| 162 | + auto t_lockdown = mio::abm::TimePoint(0) + mio::abm::days(10); |
| 163 | + mio::abm::close_social_events(t_lockdown, 0.9, model.parameters); |
| 164 | + |
| 165 | + return model; |
| 166 | +} |
| 167 | + |
| 168 | +int main() |
| 169 | +{ |
| 170 | + mio::mpi::init(); |
| 171 | + |
| 172 | + mio::set_log_level(mio::LogLevel::warn); |
| 173 | + |
| 174 | + // Set start and end time for the simulation. |
| 175 | + auto t0 = mio::abm::TimePoint(0); |
| 176 | + auto tmax = t0 + mio::abm::days(5); |
| 177 | + // Set the number of simulations to run in the study |
| 178 | + const size_t num_runs = 3; |
| 179 | + |
| 180 | + // Create a parameter study. |
| 181 | + // Note that the study for the ABM currently does not make use of the arguments "parameters" or "dt", as we create |
| 182 | + // a new model for each simulation. Hence we set both arguments to 0. |
| 183 | + // This is mostly due to https://github.com/SciCompMod/memilio/issues/1400 |
| 184 | + mio::ParameterStudy study(0, t0, tmax, mio::abm::TimeSpan(0), num_runs); |
| 185 | + |
| 186 | + // Optional: set seeds to get reproducable results |
| 187 | + // study.get_rng().seed({12341234, 53456, 63451, 5232576, 84586, 52345}); |
| 188 | + |
| 189 | + const std::string result_dir = mio::path_join(mio::base_dir(), "example_results"); |
| 190 | + if (!mio::create_directory(result_dir)) { |
| 191 | + mio::log_error("Could not create result directory \"{}\".", result_dir); |
| 192 | + return 1; |
| 193 | + } |
| 194 | + |
| 195 | + auto ensemble_results = study.run( |
| 196 | + [](auto, auto t0_, auto, size_t) { |
| 197 | + return mio::abm::ResultSimulation(make_model(mio::thread_local_rng()), t0_); |
| 198 | + }, |
| 199 | + [result_dir](auto&& sim, auto&& run_idx) { |
| 200 | + auto interpolated_result = mio::interpolate_simulation_result(sim.get_result()); |
| 201 | + std::string outpath = mio::path_join(result_dir, "abm_minimal_run_" + std::to_string(run_idx) + ".txt"); |
| 202 | + std::ofstream outfile_run(outpath); |
| 203 | + sim.get_result().print_table(outfile_run, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); |
| 204 | + std::cout << "Results written to " << outpath << std::endl; |
| 205 | + auto params = std::vector<mio::abm::Model>{}; |
| 206 | + return std::vector{interpolated_result}; |
| 207 | + }); |
| 208 | + |
| 209 | + if (ensemble_results.size() > 0) { |
| 210 | + auto ensemble_results_p05 = ensemble_percentile(ensemble_results, 0.05); |
| 211 | + auto ensemble_results_p25 = ensemble_percentile(ensemble_results, 0.25); |
| 212 | + auto ensemble_results_p50 = ensemble_percentile(ensemble_results, 0.50); |
| 213 | + auto ensemble_results_p75 = ensemble_percentile(ensemble_results, 0.75); |
| 214 | + auto ensemble_results_p95 = ensemble_percentile(ensemble_results, 0.95); |
| 215 | + |
| 216 | + mio::unused(save_result(ensemble_results_p05, {0}, num_age_groups, |
| 217 | + mio::path_join(result_dir, "Results_" + std::string("p05") + ".h5"))); |
| 218 | + mio::unused(save_result(ensemble_results_p25, {0}, num_age_groups, |
| 219 | + mio::path_join(result_dir, "Results_" + std::string("p25") + ".h5"))); |
| 220 | + mio::unused(save_result(ensemble_results_p50, {0}, num_age_groups, |
| 221 | + mio::path_join(result_dir, "Results_" + std::string("p50") + ".h5"))); |
| 222 | + mio::unused(save_result(ensemble_results_p75, {0}, num_age_groups, |
| 223 | + mio::path_join(result_dir, "Results_" + std::string("p75") + ".h5"))); |
| 224 | + mio::unused(save_result(ensemble_results_p95, {0}, num_age_groups, |
| 225 | + mio::path_join(result_dir, "Results_" + std::string("p95") + ".h5"))); |
| 226 | + } |
| 227 | + |
| 228 | + mio::mpi::finalize(); |
| 229 | + |
| 230 | + return 0; |
| 231 | +} |
0 commit comments