LCOV - code coverage report
Current view: top level - models/abm - model.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 186 196 94.9 %
Date: 2025-02-17 13:46:44 Functions: 26 30 86.7 %

          Line data    Source code
       1             : /* 
       2             : * Copyright (C) 2020-2025 MEmilio
       3             : *
       4             : * Authors: Daniel Abele, Majid Abedi, Elisabeth Kluth, Carlotta Gerstein, Martin J. Kuehn, David Kerkmann, Khoa Nguyen
       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/model.h"
      21             : #include "abm/location_id.h"
      22             : #include "abm/location_type.h"
      23             : #include "abm/intervention_type.h"
      24             : #include "abm/person.h"
      25             : #include "abm/location.h"
      26             : #include "abm/mobility_rules.h"
      27             : #include "abm/person_id.h"
      28             : #include "memilio/epidemiology/age_group.h"
      29             : #include "memilio/utils/logging.h"
      30             : #include "memilio/utils/mioomp.h"
      31             : #include "memilio/utils/stl_util.h"
      32             : #include <cassert>
      33             : #include <cstdint>
      34             : 
      35             : namespace mio
      36             : {
      37             : namespace abm
      38             : {
      39             : 
      40         822 : LocationId Model::add_location(LocationType type, uint32_t num_cells)
      41             : {
      42         822 :     LocationId id{static_cast<uint32_t>(m_locations.size())};
      43         822 :     m_locations.emplace_back(type, id, parameters.get_num_groups(), m_id, num_cells);
      44         822 :     m_has_locations[size_t(type)] = true;
      45             : 
      46             :     // mark caches for rebuild
      47         822 :     m_is_local_population_cache_valid = false;
      48         822 :     m_are_exposure_caches_valid       = false;
      49         822 :     m_exposure_caches_need_rebuild    = true;
      50             : 
      51        1644 :     return id;
      52             : }
      53             : 
      54        1045 : PersonId Model::add_person(const LocationId id, AgeGroup age)
      55             : {
      56        1045 :     PersonId person_id = (static_cast<int64_t>(m_id)) << 32 | static_cast<uint32_t>(m_persons.size());
      57        2090 :     return add_person(Person(m_rng, get_location(id).get_type(), id, m_id, age, person_id));
      58             : }
      59             : 
      60        1327 : PersonId Model::add_person(Person&& person)
      61             : {
      62        1327 :     assert(person.get_location() != LocationId::invalid_id() && "Added Person's location must be valid.");
      63        1327 :     assert(person.get_location() < LocationId((uint32_t)m_locations.size()) &&
      64             :            "Added Person's location is not in Model.");
      65        1327 :     assert(person.get_id() != PersonId::invalid_ID() && "Added Person's unique id must be valid.");
      66        1327 :     assert(person.get_age() < (AgeGroup)parameters.get_num_groups() && "Added Person's AgeGroup is too large.");
      67        1327 :     person.set_assigned_location(LocationType::Cemetery, m_cemetery_id, m_id);
      68        1327 :     m_persons.emplace_back(person);
      69        1327 :     m_activeness_statuses.push_back(true);
      70        1327 :     auto& new_person = m_persons.back();
      71        1327 :     if (m_is_local_population_cache_valid) {
      72           3 :         ++m_local_population_cache[new_person.get_location().get()];
      73             :     }
      74        1327 :     return new_person.get_id();
      75             : }
      76             : 
      77         774 : void Model::evolve(TimePoint t, TimeSpan dt)
      78             : {
      79         774 :     begin_step(t, dt);
      80         774 :     log_info("ABM Model interaction.");
      81         774 :     interaction(t, dt);
      82         774 :     log_info("ABM Model mobility.");
      83         774 :     perform_mobility(t, dt);
      84         774 : }
      85             : 
      86        1063 : void Model::interaction(TimePoint t, TimeSpan dt)
      87             : {
      88        1063 :     const uint32_t num_persons = static_cast<uint32_t>(m_persons.size());
      89             :     PRAGMA_OMP(parallel for)
      90        3929 :     for (uint32_t person_index = 0; person_index < num_persons; ++person_index) {
      91        2866 :         if (m_activeness_statuses[person_index]) {
      92        2853 :             assert(m_persons[person_index].get_location_model_id() == m_id &&
      93             :                    "Person is not in this model but still active.");
      94        2853 :             interact(m_persons[person_index], t, dt);
      95             :         }
      96             :     }
      97        1063 : }
      98             : 
      99         774 : void Model::perform_mobility(TimePoint t, TimeSpan dt)
     100             : {
     101         774 :     const uint32_t num_persons = static_cast<uint32_t>(m_persons.size());
     102             :     PRAGMA_OMP(parallel for)
     103        3564 :     for (uint32_t person_index = 0; person_index < num_persons; ++person_index) {
     104        2790 :         if (m_activeness_statuses[person_index]) {
     105        2790 :             Person& person    = m_persons[person_index];
     106        2790 :             auto personal_rng = PersonalRandomNumberGenerator(person);
     107             : 
     108        2790 :             auto try_mobility_rule = [&](auto rule) -> bool {
     109             :                 // run mobility rule and check if change of location can actually happen
     110       82017 :                 auto target_type = rule(personal_rng, person, t, dt, parameters);
     111       26676 :                 if (person.get_assigned_location_model_id(target_type) == m_id) {
     112       35568 :                     const Location& target_location   = get_location(find_location(target_type, person));
     113       26676 :                     const LocationId current_location = person.get_location();
     114             : 
     115             :                     // the Person cannot move if they do not wear mask as required at targeted location
     116        8928 :                     if (target_location.is_mask_required() &&
     117          72 :                         !person.is_compliant(personal_rng, InterventionType::Mask)) {
     118           9 :                         return false;
     119             :                     }
     120             :                     // the Person cannot move if the capacity of targeted Location is reached
     121        9045 :                     if (target_location.get_id() == current_location ||
     122         324 :                         get_number_persons(target_location.get_id()) >= target_location.get_capacity().persons) {
     123        8730 :                         return false;
     124             :                     }
     125             :                     // the Person cannot move if the performed TestingStrategy is positive
     126         612 :                     if (!m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) {
     127          18 :                         return false;
     128             :                     }
     129             :                     // update worn mask to target location's requirements
     130         135 :                     if (target_location.is_mask_required()) {
     131             :                         // if the current MaskProtection level is lower than required, the Person changes mask
     132          45 :                         if (parameters.get<MaskProtection>()[person.get_mask().get_type()] <
     133          18 :                             parameters.get<MaskProtection>()[target_location.get_required_mask()]) {
     134          36 :                             person.set_mask(target_location.get_required_mask(), t);
     135             :                         }
     136             :                     }
     137             :                     else {
     138         504 :                         person.set_mask(MaskType::None, t);
     139             :                     }
     140             :                     // all requirements are met, move to target location
     141         405 :                     change_location(person, target_location.get_id());
     142         135 :                     return true;
     143             :                 }
     144           0 :                 return false;
     145        2790 :             };
     146             : 
     147             :             // run mobility rules one after the other if the corresponding location type exists
     148             :             // shortcutting of bool operators ensures the rules stop after the first rule is applied
     149        2790 :             if (m_use_mobility_rules) {
     150       10980 :                 (has_locations({LocationType::Cemetery}) && try_mobility_rule(&get_buried)) ||
     151        5454 :                     (has_locations({LocationType::Home}) && try_mobility_rule(&return_home_when_recovered)) ||
     152        5436 :                     (has_locations({LocationType::Hospital}) && try_mobility_rule(&go_to_hospital)) ||
     153        5418 :                     (has_locations({LocationType::ICU}) && try_mobility_rule(&go_to_icu)) ||
     154        5418 :                     (has_locations({LocationType::School, LocationType::Home}) && try_mobility_rule(&go_to_school)) ||
     155        5391 :                     (has_locations({LocationType::Work, LocationType::Home}) && try_mobility_rule(&go_to_work)) ||
     156        5373 :                     (has_locations({LocationType::BasicsShop, LocationType::Home}) && try_mobility_rule(&go_to_shop)) ||
     157        5364 :                     (has_locations({LocationType::SocialEvent, LocationType::Home}) &&
     158        8964 :                      try_mobility_rule(&go_to_event)) ||
     159        5355 :                     (has_locations({LocationType::Home}) && try_mobility_rule(&go_to_quarantine));
     160             :             }
     161             :             else {
     162             :                 // no daily routine mobility, just infection related
     163         180 :                 (has_locations({LocationType::Cemetery}) && try_mobility_rule(&get_buried)) ||
     164          90 :                     (has_locations({LocationType::Home}) && try_mobility_rule(&return_home_when_recovered)) ||
     165          90 :                     (has_locations({LocationType::Hospital}) && try_mobility_rule(&go_to_hospital)) ||
     166         225 :                     (has_locations({LocationType::ICU}) && try_mobility_rule(&go_to_icu)) ||
     167          90 :                     (has_locations({LocationType::Home}) && try_mobility_rule(&go_to_quarantine));
     168             :             }
     169             :         }
     170             :     }
     171             : 
     172             :     // check if a person makes a trip
     173         774 :     bool weekend     = t.is_weekend();
     174         774 :     size_t num_trips = m_trip_list.num_trips(weekend);
     175             : 
     176        2205 :     for (; m_trip_list.get_current_index() < num_trips &&
     177        1521 :            m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds();
     178         189 :          m_trip_list.increase_index()) {
     179         189 :         auto& trip        = m_trip_list.get_next_trip(weekend);
     180         189 :         auto& person      = get_person(trip.person_id);
     181         189 :         auto personal_rng = PersonalRandomNumberGenerator(person);
     182             :         // skip the trip if the person is in quarantine or is dead
     183         189 :         if (person.is_in_quarantine(t, parameters) || person.get_infection_state(t) == InfectionState::Dead) {
     184           0 :             continue;
     185             :         }
     186         189 :         auto& target_location = get_location(trip.destination);
     187             :         // skip the trip if the Person wears mask as required at targeted location
     188         189 :         if (target_location.is_mask_required() && !person.is_compliant(personal_rng, InterventionType::Mask)) {
     189           9 :             continue;
     190             :         }
     191             :         // skip the trip if the performed TestingStrategy is positive
     192         180 :         if (!m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) {
     193          18 :             continue;
     194             :         }
     195             :         // all requirements are met, move to target location
     196         162 :         change_location(person, target_location.get_id(), trip.trip_mode);
     197             :         // update worn mask to target location's requirements
     198         162 :         if (target_location.is_mask_required()) {
     199             :             // if the current MaskProtection level is lower than required, the Person changes mask
     200          36 :             if (parameters.get<MaskProtection>()[person.get_mask().get_type()] <
     201          27 :                 parameters.get<MaskProtection>()[target_location.get_required_mask()]) {
     202           9 :                 person.set_mask(target_location.get_required_mask(), t);
     203             :             }
     204             :         }
     205             :         else {
     206         153 :             person.set_mask(MaskType::None, t);
     207             :         }
     208             :     }
     209         774 :     if (((t).days() < std::floor((t + dt).days()))) {
     210          63 :         m_trip_list.reset_index();
     211             :     }
     212         774 : }
     213             : 
     214         100 : void Model::build_compute_local_population_cache() const
     215             : {
     216             :     PRAGMA_OMP(single)
     217             :     {
     218         100 :         const size_t num_locations = m_locations.size();
     219         100 :         const size_t num_persons   = m_persons.size();
     220         100 :         m_local_population_cache.resize(num_locations);
     221             :         PRAGMA_OMP(taskloop)
     222         532 :         for (size_t i = 0; i < num_locations; i++) {
     223         432 :             m_local_population_cache[i] = 0;
     224             :         } // implicit taskloop barrier
     225             :         PRAGMA_OMP(taskloop)
     226         407 :         for (size_t i = 0; i < num_persons; i++) {
     227         307 :             if (m_activeness_statuses[i]) {
     228         301 :                 assert(m_persons[i].get_location_model_id() == m_id && "Person is not in this model but still active.");
     229         301 :                 ++m_local_population_cache[m_persons[i].get_location().get()];
     230             :             }
     231             :         } // implicit taskloop barrier
     232             :     } // implicit single barrier
     233         100 : }
     234             : 
     235          97 : void Model::build_exposure_caches()
     236             : {
     237             :     PRAGMA_OMP(single)
     238             :     {
     239          97 :         const size_t num_locations = m_locations.size();
     240          97 :         m_air_exposure_rates_cache.resize(num_locations);
     241          97 :         m_contact_exposure_rates_cache.resize(num_locations);
     242             :         PRAGMA_OMP(taskloop)
     243         519 :         for (size_t i = 0; i < num_locations; i++) {
     244         422 :             m_air_exposure_rates_cache[i].resize({CellIndex(m_locations[i].get_cells().size()), VirusVariant::Count});
     245        1266 :             m_contact_exposure_rates_cache[i].resize({CellIndex(m_locations[i].get_cells().size()), VirusVariant::Count,
     246         844 :                                                       AgeGroup(parameters.get_num_groups())});
     247             :         } // implicit taskloop barrier
     248          97 :         m_are_exposure_caches_valid    = false;
     249          97 :         m_exposure_caches_need_rebuild = false;
     250             :     } // implicit single barrier
     251          97 : }
     252             : 
     253        1063 : void Model::compute_exposure_caches(TimePoint t, TimeSpan dt)
     254             : {
     255             :     PRAGMA_OMP(single)
     256             :     {
     257             :         // if cache shape was changed (e.g. by add_location), rebuild it
     258        1063 :         if (m_exposure_caches_need_rebuild) {
     259          97 :             build_exposure_caches();
     260             :         }
     261             :         // use these const values to help omp recognize that the for loops are bounded
     262        1063 :         const auto num_locations = m_locations.size();
     263        1063 :         const auto num_persons   = m_persons.size();
     264             : 
     265             :         // 1) reset all cached values
     266             :         // Note: we cannot easily reuse values, as they are time dependant (get_infection_state)
     267             :         PRAGMA_OMP(taskloop)
     268        5214 :         for (size_t i = 0; i < num_locations; ++i) {
     269        4151 :             const auto index         = i;
     270        4151 :             auto& local_air_exposure = m_air_exposure_rates_cache[index];
     271        4151 :             std::for_each(local_air_exposure.begin(), local_air_exposure.end(), [](auto& r) {
     272        4151 :                 r = 0.0;
     273        4151 :             });
     274        4151 :             auto& local_contact_exposure = m_contact_exposure_rates_cache[index];
     275        4151 :             std::for_each(local_contact_exposure.begin(), local_contact_exposure.end(), [](auto& r) {
     276       23126 :                 r = 0.0;
     277       23126 :             });
     278             :         } // implicit taskloop barrier
     279             :         // here is an implicit (and needed) barrier from parallel for
     280             : 
     281             :         // 2) add all contributions from each person
     282             :         PRAGMA_OMP(taskloop)
     283        3929 :         for (size_t i = 0; i < num_persons; ++i) {
     284        2866 :             const Person& person = m_persons[i];
     285        2866 :             const auto location  = person.get_location().get();
     286        2866 :             if (m_activeness_statuses[i]) {
     287        2853 :                 assert(m_persons[i].get_location_model_id() == m_id && "Person is not in this model but still active.");
     288        5706 :                 mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location],
     289        2853 :                                                     m_contact_exposure_rates_cache[location], person,
     290        2853 :                                                     get_location(person.get_location()), t, dt);
     291             :             }
     292             :         } // implicit taskloop barrier
     293             :     } // implicit single barrier
     294        1063 : }
     295             : 
     296        1063 : void Model::begin_step(TimePoint t, TimeSpan dt)
     297             : {
     298        1063 :     m_testing_strategy.update_activity_status(t);
     299             : 
     300        1063 :     if (!m_is_local_population_cache_valid) {
     301         100 :         build_compute_local_population_cache();
     302         100 :         m_is_local_population_cache_valid = true;
     303             :     }
     304        1063 :     compute_exposure_caches(t, dt);
     305        1063 :     m_are_exposure_caches_valid = true;
     306        1063 : }
     307             : 
     308         720 : auto Model::get_locations() const -> Range<std::pair<ConstLocationIterator, ConstLocationIterator>>
     309             : {
     310         720 :     return std::make_pair(m_locations.cbegin(), m_locations.cend());
     311             : }
     312          63 : auto Model::get_locations() -> Range<std::pair<LocationIterator, LocationIterator>>
     313             : {
     314          63 :     return std::make_pair(m_locations.begin(), m_locations.end());
     315             : }
     316             : 
     317         486 : auto Model::get_persons() const -> Range<std::pair<ConstPersonIterator, ConstPersonIterator>>
     318             : {
     319         486 :     return std::make_pair(m_persons.cbegin(), m_persons.cend());
     320             : }
     321             : 
     322         379 : auto Model::get_persons() -> Range<std::pair<PersonIterator, PersonIterator>>
     323             : {
     324         379 :     return std::make_pair(m_persons.begin(), m_persons.end());
     325             : }
     326             : 
     327           0 : auto Model::get_activeness_statuses() const -> Range<std::pair<ConstActivenessIterator, ConstActivenessIterator>>
     328             : {
     329           0 :     return std::make_pair(m_activeness_statuses.cbegin(), m_activeness_statuses.cend());
     330             : }
     331             : 
     332           5 : auto Model::get_activeness_statuses() -> Range<std::pair<ActivenessIterator, ActivenessIterator>>
     333             : {
     334          10 :     return std::make_pair(m_activeness_statuses.begin(), m_activeness_statuses.end());
     335             : }
     336             : 
     337          81 : LocationId Model::find_location(LocationType type, const PersonId person) const
     338             : {
     339         162 :     return find_location(type, get_person(person));
     340             : }
     341             : 
     342           9 : size_t Model::get_subpopulation_combined(TimePoint t, InfectionState s) const
     343             : {
     344           9 :     return std::accumulate(m_locations.begin(), m_locations.end(), (size_t)0,
     345          90 :                            [t, s, this](size_t running_sum, const Location& loc) {
     346          90 :                                return running_sum + get_subpopulation(loc.get_id(), t, s);
     347           9 :                            });
     348             : }
     349             : 
     350          18 : size_t Model::get_subpopulation_combined_per_location_type(TimePoint t, InfectionState s, LocationType type) const
     351             : {
     352          36 :     return std::accumulate(
     353         216 :         m_locations.begin(), m_locations.end(), (size_t)0, [t, s, type, this](size_t running_sum, const Location& loc) {
     354         378 :             return loc.get_type() == type ? running_sum + get_subpopulation(loc.get_id(), t, s) : running_sum;
     355          18 :         });
     356             : }
     357             : 
     358          47 : TripList& Model::get_trip_list()
     359             : {
     360          47 :     return m_trip_list;
     361             : }
     362             : 
     363           0 : const TripList& Model::get_trip_list() const
     364             : {
     365           0 :     return m_trip_list;
     366             : }
     367             : 
     368           9 : void Model::use_mobility_rules(bool param)
     369             : {
     370           9 :     m_use_mobility_rules = param;
     371           9 : }
     372             : 
     373           0 : bool Model::use_mobility_rules() const
     374             : {
     375           0 :     return m_use_mobility_rules;
     376             : }
     377             : 
     378          81 : TestingStrategy& Model::get_testing_strategy()
     379             : {
     380          81 :     return m_testing_strategy;
     381             : }
     382             : 
     383           0 : const TestingStrategy& Model::get_testing_strategy() const
     384             : {
     385           0 :     return m_testing_strategy;
     386             : }
     387             : 
     388             : } // namespace abm
     389             : } // namespace mio

Generated by: LCOV version 1.14