|
| 1 | +#include "abm/simulation.h" |
| 2 | +#include "memilio/utils/stl_util.h" |
| 3 | +#include "benchmark/benchmark.h" |
| 4 | + |
| 5 | +mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list<uint32_t> seeds) |
| 6 | +{ |
| 7 | + auto rng = mio::RandomNumberGenerator(); |
| 8 | + rng.seed(seeds); |
| 9 | + auto world = mio::abm::World(5); |
| 10 | + world.get_rng() = rng; |
| 11 | + |
| 12 | + //create persons at home |
| 13 | + const auto mean_home_size = 5.0; |
| 14 | + const auto min_home_size = 1; |
| 15 | + auto& home_size_distribution = mio::PoissonDistribution<int>::get_instance(); |
| 16 | + auto home = world.add_location(mio::abm::LocationType::Home); |
| 17 | + auto planned_home_size = home_size_distribution(world.get_rng(), mean_home_size); |
| 18 | + auto home_size = 0; |
| 19 | + for (size_t i = 0; i < num_persons; ++i) { |
| 20 | + if (home_size >= std::max(min_home_size, planned_home_size)) { |
| 21 | + home = world.add_location(mio::abm::LocationType::Home); |
| 22 | + planned_home_size = home_size_distribution(world.get_rng(), mean_home_size); |
| 23 | + home_size = 0; |
| 24 | + } |
| 25 | + |
| 26 | + auto age = mio::AgeGroup(mio::UniformIntDistribution<size_t>::get_instance()( |
| 27 | + world.get_rng(), size_t(0), world.parameters.get_num_groups() - 1)); |
| 28 | + auto& person = world.add_person(home, age); |
| 29 | + person.set_assigned_location(home); |
| 30 | + home_size++; |
| 31 | + } |
| 32 | + |
| 33 | + //create other locations |
| 34 | + for (auto loc_type : |
| 35 | + {mio::abm::LocationType::School, mio::abm::LocationType::Work, mio::abm::LocationType::SocialEvent, |
| 36 | + mio::abm::LocationType::BasicsShop, mio::abm::LocationType::Hospital, mio::abm::LocationType::ICU}) { |
| 37 | + |
| 38 | + const auto num_locs = std::max(size_t(1), num_persons / 2'000); |
| 39 | + std::vector<mio::abm::LocationId> locs(num_locs); |
| 40 | + std::generate(locs.begin(), locs.end(), [&] { |
| 41 | + return world.add_location(loc_type); |
| 42 | + }); |
| 43 | + for (auto& person : world.get_persons()) { |
| 44 | + auto loc_idx = |
| 45 | + mio::UniformIntDistribution<size_t>::get_instance()(world.get_rng(), size_t(0), num_locs - 1); |
| 46 | + person.set_assigned_location(locs[loc_idx]); |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + //infections and masks |
| 51 | + for (auto& person : world.get_persons()) { |
| 52 | + auto prng = mio::abm::Person::RandomNumberGenerator(world.get_rng(), person); |
| 53 | + //some % of people are infected, large enough to have some infection activity without everyone dying |
| 54 | + auto pct_infected = 0.05; |
| 55 | + if (mio::UniformDistribution<double>::get_instance()(prng, 0.0, 1.0) < pct_infected) { |
| 56 | + auto state = mio::abm::InfectionState( |
| 57 | + mio::UniformIntDistribution<int>::get_instance()(prng, 1, int(mio::abm::InfectionState::Count) - 1)); |
| 58 | + auto infection = |
| 59 | + mio::abm::Infection(prng, mio::abm::VirusVariant::Wildtype, person.get_age(), |
| 60 | + world.parameters, mio::abm::TimePoint(0), state); |
| 61 | + person.add_new_infection(std::move(infection)); |
| 62 | + } |
| 63 | + |
| 64 | + //equal chance of (moderate) mask refusal and (moderate) mask eagerness |
| 65 | + auto pct_mask_values = std::array{0.05 /*-1*/, 0.2 /*-0.5*/, 0.5 /*0*/, 0.2 /*0.5*/, 0.05 /*1*/}; |
| 66 | + auto mask_value = -1 + 0.5 * mio::DiscreteDistribution<int>::get_instance()(prng, pct_mask_values); |
| 67 | + person.set_mask_preferences({size_t(mio::abm::LocationType::Count), mask_value}); |
| 68 | + } |
| 69 | + |
| 70 | + //masks at locations |
| 71 | + for (auto& loc : world.get_locations()) |
| 72 | + { |
| 73 | + //some % of locations require masks |
| 74 | + //skip homes so persons always have a place to go, simulation might break otherwise |
| 75 | + auto pct_require_mask = 0.2; |
| 76 | + auto requires_mask = loc.get_type() != mio::abm::LocationType::Home && |
| 77 | + mio::UniformDistribution<double>::get_instance()(world.get_rng()) < pct_require_mask; |
| 78 | + loc.set_npi_active(requires_mask); |
| 79 | + } |
| 80 | + |
| 81 | + //testing schemes |
| 82 | + auto sample = [&](auto v, size_t n) { //selects n elements from list v |
| 83 | + std::shuffle(v.begin(), v.end(), world.get_rng()); |
| 84 | + return std::vector<typename decltype(v)::value_type>(v.begin(), v.begin() + n); |
| 85 | + }; |
| 86 | + std::vector<mio::AgeGroup> ages; |
| 87 | + std::generate_n(std::back_inserter(ages), world.parameters.get_num_groups(), [a = 0]() mutable { |
| 88 | + return mio::AgeGroup(a++); |
| 89 | + }); |
| 90 | + auto random_criteria = [&]() { |
| 91 | + auto random_ages = sample(ages, 2); |
| 92 | + auto random_states = std::vector<mio::abm::InfectionState>(0); |
| 93 | + return mio::abm::TestingCriteria(random_ages, random_states); |
| 94 | + }; |
| 95 | + |
| 96 | + world.get_testing_strategy().add_testing_scheme( |
| 97 | + mio::abm::LocationType::School, |
| 98 | + mio::abm::TestingScheme(random_criteria(), mio::abm::days(3), mio::abm::TimePoint(0), |
| 99 | + mio::abm::TimePoint(0) + mio::abm::days(10), {}, 0.5)); |
| 100 | + world.get_testing_strategy().add_testing_scheme( |
| 101 | + mio::abm::LocationType::Work, |
| 102 | + mio::abm::TestingScheme(random_criteria(), mio::abm::days(3), mio::abm::TimePoint(0), |
| 103 | + mio::abm::TimePoint(0) + mio::abm::days(10), {}, 0.5)); |
| 104 | + world.get_testing_strategy().add_testing_scheme( |
| 105 | + mio::abm::LocationType::Home, |
| 106 | + mio::abm::TestingScheme(random_criteria(), mio::abm::days(3), mio::abm::TimePoint(0), |
| 107 | + mio::abm::TimePoint(0) + mio::abm::days(10), {}, 0.5)); |
| 108 | + world.get_testing_strategy().add_testing_scheme( |
| 109 | + mio::abm::LocationType::SocialEvent, |
| 110 | + mio::abm::TestingScheme(random_criteria(), mio::abm::days(3), mio::abm::TimePoint(0), |
| 111 | + mio::abm::TimePoint(0) + mio::abm::days(10), {}, 0.5)); |
| 112 | + |
| 113 | + return mio::abm::Simulation(mio::abm::TimePoint(0), std::move(world)); |
| 114 | +} |
| 115 | + |
| 116 | +/** |
| 117 | + * Benchmark for the ABM simulation. |
| 118 | + * @param num_persons Number of persons in the simulation. |
| 119 | + * @param seeds Seeds for the random number generator. |
| 120 | + */ |
| 121 | +void abm_benchmark(benchmark::State& state, size_t num_persons, std::initializer_list<uint32_t> seeds) |
| 122 | +{ |
| 123 | + mio::set_log_level(mio::LogLevel::warn); |
| 124 | + |
| 125 | + for (auto&& _ : state) { |
| 126 | + state.PauseTiming(); //exclude the setup from the benchmark |
| 127 | + auto sim = make_simulation(num_persons, seeds); |
| 128 | + state.ResumeTiming(); |
| 129 | + |
| 130 | + //simulated time should be long enough to have full infection runs and migration to every location |
| 131 | + auto final_time = sim.get_time() + mio::abm::days(10); |
| 132 | + sim.advance(final_time); |
| 133 | + |
| 134 | + //debug output can be enabled to check for unexpected results (e.g. infections dieing out) |
| 135 | + //normally should have no significant effect on runtime |
| 136 | + const bool monitor_infection_activity = false; |
| 137 | + if constexpr (monitor_infection_activity) { |
| 138 | + std::cout << "num_persons = " << num_persons << "\n"; |
| 139 | + std::cout << sim.get_result()[0].transpose() << "\n"; |
| 140 | + std::cout << sim.get_result().get_last_value().transpose() << "\n"; |
| 141 | + } |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +//Measure ABM simulation run time with different sizes and different seeds. |
| 146 | +//Fixed RNG seeds to make runs comparable. When there are code changes, the simulation will still |
| 147 | +//run differently due to different sequence of random numbers being drawn. But for large enough sizes |
| 148 | +//RNG should average out, so runs should be comparable even with code changes. |
| 149 | +//We run a few different benchmarks to hopefully catch abnormal cases. Then seeds may |
| 150 | +//have to be adjusted to get the benchmark back to normal. |
| 151 | +//For small sizes (e.g. 10k) extreme cases are too likely, i.e. infections die out |
| 152 | +//or overwhelm everything, so we don't benchmark these. Results should be mostly transferrable. |
| 153 | +BENCHMARK_CAPTURE(abm_benchmark, abm_benchmark_50k, 50000, {14159265u, 35897932u})->Unit(benchmark::kMillisecond); |
| 154 | +BENCHMARK_CAPTURE(abm_benchmark, abm_benchmark_100k, 100000, {38462643u, 38327950u})->Unit(benchmark::kMillisecond); |
| 155 | +BENCHMARK_CAPTURE(abm_benchmark, abm_benchmark_200k, 200000, {28841971u, 69399375u})->Unit(benchmark::kMillisecond); |
| 156 | + |
| 157 | +BENCHMARK_MAIN(); |
0 commit comments