LCOV - code coverage report
Current view: top level - models/abm - model.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 114 117 97.4 %
Date: 2024-11-18 12:45:26 Functions: 21 21 100.0 %

          Line data    Source code
       1             : /* 
       2             : * Copyright (C) 2020-2024 MEmilio
       3             : *
       4             : * Authors: Daniel Abele, Majid Abedi, Elisabeth Kluth, David Kerkmann, Sascha Korf, Martin J. Kuehn, 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             : #ifndef MIO_ABM_MODEL_H
      21             : #define MIO_ABM_MODEL_H
      22             : 
      23             : #include "abm/model_functions.h"
      24             : #include "abm/location_type.h"
      25             : #include "abm/mobility_data.h"
      26             : #include "abm/parameters.h"
      27             : #include "abm/location.h"
      28             : #include "abm/person.h"
      29             : #include "abm/person_id.h"
      30             : #include "abm/time.h"
      31             : #include "abm/trip_list.h"
      32             : #include "abm/random_events.h"
      33             : #include "abm/testing_strategy.h"
      34             : #include "memilio/epidemiology/age_group.h"
      35             : #include "memilio/utils/random_number_generator.h"
      36             : #include "memilio/utils/stl_util.h"
      37             : 
      38             : #include <bitset>
      39             : #include <vector>
      40             : 
      41             : namespace mio
      42             : {
      43             : namespace abm
      44             : {
      45             : 
      46             : /**
      47             :  * @brief The Model of the Simulation.
      48             :  * It consists of Location%s and Person%s (Agents).
      49             :  */
      50             : class Model
      51             : {
      52             : public:
      53             :     using LocationIterator      = std::vector<Location>::iterator;
      54             :     using ConstLocationIterator = std::vector<Location>::const_iterator;
      55             :     using PersonIterator        = std::vector<Person>::iterator;
      56             :     using ConstPersonIterator   = std::vector<Person>::const_iterator;
      57             : 
      58             :     /**
      59             :      * @brief Create a Model.
      60             :      * @param[in] num_agegroups The number of AgeGroup%s in the simulated Model. Must be less than MAX_NUM_AGE_GROUPS.
      61             :      */
      62         199 :     Model(size_t num_agegroups)
      63         199 :         : parameters(num_agegroups)
      64         199 :         , m_trip_list()
      65         199 :         , m_use_mobility_rules(true)
      66         398 :         , m_cemetery_id(add_location(LocationType::Cemetery))
      67             :     {
      68         199 :         assert(num_agegroups < MAX_NUM_AGE_GROUPS && "MAX_NUM_AGE_GROUPS exceeded.");
      69         199 :     }
      70             : 
      71             :     /**
      72             :      * @brief Create a Model.
      73             :      * @param[in] params Initial simulation parameters.
      74             :      */
      75           9 :     Model(const Parameters& params)
      76           9 :         : parameters(params.get_num_groups())
      77           9 :         , m_trip_list()
      78           9 :         , m_use_mobility_rules(true)
      79          18 :         , m_cemetery_id(add_location(LocationType::Cemetery))
      80             :     {
      81           9 :         parameters = params;
      82           9 :     }
      83             : 
      84         140 :     Model(const Model& other)
      85         140 :         : parameters(other.parameters)
      86         140 :         , m_local_population_cache()
      87         140 :         , m_air_exposure_rates_cache()
      88         140 :         , m_contact_exposure_rates_cache()
      89         140 :         , m_is_local_population_cache_valid(false)
      90         140 :         , m_are_exposure_caches_valid(false)
      91         140 :         , m_exposure_caches_need_rebuild(true)
      92         140 :         , m_persons(other.m_persons)
      93         140 :         , m_locations(other.m_locations)
      94         140 :         , m_has_locations(other.m_has_locations)
      95         140 :         , m_testing_strategy(other.m_testing_strategy)
      96         140 :         , m_trip_list(other.m_trip_list)
      97         140 :         , m_use_mobility_rules(other.m_use_mobility_rules)
      98         140 :         , m_mobility_rules(other.m_mobility_rules)
      99         140 :         , m_cemetery_id(other.m_cemetery_id)
     100         140 :         , m_rng(other.m_rng)
     101             :     {
     102         140 :     }
     103             :     Model& operator=(const Model&) = default;
     104          72 :     Model(Model&&)                 = default;
     105             :     Model& operator=(Model&&)      = default;
     106             : 
     107             :     /**
     108             :      * serialize this.
     109             :      * @see mio::serialize
     110             :      */
     111             :     template <class IOContext>
     112           9 :     void serialize(IOContext& io) const
     113             :     {
     114          27 :         auto obj = io.create_object("Model");
     115           9 :         obj.add_element("parameters", parameters);
     116             :         // skip caches, they are rebuild by the deserialized model
     117           9 :         obj.add_list("persons", get_persons().begin(), get_persons().end());
     118           9 :         obj.add_list("locations", get_locations().begin(), get_locations().end());
     119           9 :         obj.add_element("location_types", m_has_locations.to_ulong());
     120           9 :         obj.add_element("testing_strategy", m_testing_strategy);
     121           9 :         obj.add_element("trip_list", m_trip_list);
     122           9 :         obj.add_element("use_mobility_rules", m_use_mobility_rules);
     123           9 :         obj.add_element("cemetery_id", m_cemetery_id);
     124           9 :         obj.add_element("rng", m_rng);
     125          18 :     }
     126             : 
     127             :     /**
     128             :      * deserialize an object of this class.
     129             :      * @see mio::deserialize
     130             :      */
     131             :     template <class IOContext>
     132           9 :     static IOResult<Model> deserialize(IOContext& io)
     133             :     {
     134          27 :         auto obj                = io.expect_object("Model");
     135          27 :         auto params             = obj.expect_element("parameters", Tag<Parameters>{});
     136          27 :         auto persons            = obj.expect_list("persons", Tag<Person>{});
     137          27 :         auto locations          = obj.expect_list("locations", Tag<Location>{});
     138          27 :         auto location_types     = obj.expect_element("location_types", Tag<unsigned long>{});
     139          27 :         auto trip_list          = obj.expect_element("trip_list", Tag<TripList>{});
     140          27 :         auto use_mobility_rules = obj.expect_element("use_mobility_rules", Tag<bool>{});
     141          27 :         auto cemetery_id        = obj.expect_element("cemetery_id", Tag<LocationId>{});
     142          27 :         auto rng                = obj.expect_element("rng", Tag<RandomNumberGenerator>{});
     143             :         return apply(
     144             :             io,
     145           9 :             [](auto&& params_, auto&& persons_, auto&& locations_, auto&& location_types_, auto&& trip_list_,
     146             :                auto&& use_mobility_rules_, auto&& cemetery_id_, auto&& rng_) {
     147           9 :                 Model model{params_};
     148           9 :                 model.m_persons.assign(persons_.cbegin(), persons_.cend());
     149           9 :                 model.m_locations.assign(locations_.cbegin(), locations_.cend());
     150           9 :                 model.m_has_locations      = location_types_;
     151           9 :                 model.m_trip_list          = trip_list_;
     152           9 :                 model.m_use_mobility_rules = use_mobility_rules_;
     153           9 :                 model.m_cemetery_id        = cemetery_id_;
     154           9 :                 model.m_rng                = rng_;
     155          18 :                 return model;
     156           9 :             },
     157          18 :             params, persons, locations, location_types, trip_list, use_mobility_rules, cemetery_id, rng);
     158           9 :     }
     159             : 
     160             :     /** 
     161             :      * @brief Prepare the Model for the next Simulation step.
     162             :      * @param[in] t Current time.
     163             :      * @param[in] dt Length of the time step.
     164             :      */
     165             :     void begin_step(TimePoint t, TimeSpan dt);
     166             : 
     167             :     /** 
     168             :      * @brief Evolve the Model one time step.
     169             :      * @param[in] t Current time.
     170             :      * @param[in] dt Length of the time step.
     171             :      */
     172             :     void evolve(TimePoint t, TimeSpan dt);
     173             : 
     174             :     /** 
     175             :      * @brief Add a Location to the Model.
     176             :      * @param[in] type Type of Location to add.
     177             :      * @param[in] num_cells [Default: 1] Number of Cell%s that the Location is divided into.
     178             :      * @return ID of the newly created Location.
     179             :      */
     180             :     LocationId add_location(LocationType type, uint32_t num_cells = 1);
     181             : 
     182             :     /** 
     183             :      * @brief Add a Person to the Model.
     184             :      * @param[in] id The LocationID of the initial Location of the Person.
     185             :      * @param[in] age AgeGroup of the person.
     186             :      * @return ID of the newly created Person.
     187             :      */
     188             :     PersonId add_person(const LocationId id, AgeGroup age);
     189             : 
     190             :     /**
     191             :      * @brief Adds a copy of a given Person to the Model.
     192             :      * @param[in] person The Person to copy from. 
     193             :      * @return ID of the newly created Person.
     194             :      */
     195             :     PersonId add_person(Person&& person);
     196             : 
     197             :     /**
     198             :      * @brief Get a range of all Location%s in the Model.
     199             :      * @return A range of all Location%s.
     200             :      * @{
     201             :      */
     202             :     Range<std::pair<ConstLocationIterator, ConstLocationIterator>> get_locations() const;
     203             :     Range<std::pair<LocationIterator, LocationIterator>> get_locations();
     204             :     /** @} */
     205             : 
     206             :     /**
     207             :      * @brief Get a range of all Person%s in the Model.
     208             :      * @return A range of all Person%s.
     209             :      * @{
     210             :      */
     211             :     Range<std::pair<ConstPersonIterator, ConstPersonIterator>> get_persons() const;
     212             :     Range<std::pair<PersonIterator, PersonIterator>> get_persons();
     213             :     /** @} */
     214             : 
     215             :     /**
     216             :      * @brief Find an assigned Location of a Person.
     217             :      * @param[in] type The #LocationType that specifies the assigned Location.
     218             :      * @param[in] person PersonId of the Person.
     219             :      * @return ID of the Location of LocationType type assigend to person.
     220             :      */
     221             :     LocationId find_location(LocationType type, const PersonId person) const;
     222             : 
     223             :     /**
     224             :      * @brief Assign a Location to a Person.
     225             :      * A Person can have at most one assigned Location of a certain LocationType.
     226             :      * Assigning another Location of an already assigned LocationType will replace the prior assignment.  
     227             :      * @param[in] person The PersonId of the person this location will be assigned to.
     228             :      * @param[in] location The LocationId of the Location.
     229             :      */
     230        1098 :     void assign_location(PersonId person, LocationId location)
     231             :     {
     232        1098 :         get_person(person).set_assigned_location(get_location(location).get_type(), location);
     233        1098 :     }
     234             : 
     235             :     /**
     236             :      * @brief Get the number of Persons in one #InfectionState at all Location%s.
     237             :      * @param[in] t Specified #TimePoint.
     238             :      * @param[in] s Specified #InfectionState.
     239             :      */
     240             :     size_t get_subpopulation_combined(TimePoint t, InfectionState s) const;
     241             : 
     242             :     /**
     243             :      * @brief Get the number of Persons in one #InfectionState at all Location%s of a type.
     244             :      * @param[in] t Specified #TimePoint.
     245             :      * @param[in] s Specified #InfectionState.
     246             :      * @param[in] type Specified #LocationType.
     247             :      */
     248             :     size_t get_subpopulation_combined_per_location_type(TimePoint t, InfectionState s, LocationType type) const;
     249             : 
     250             :     /**
     251             :      * @brief Get the mobility data.
     252             :      * @return Reference to the list of Trip%s that the Person%s make.
     253             :      */
     254             :     TripList& get_trip_list();
     255             : 
     256             :     const TripList& get_trip_list() const;
     257             : 
     258             :     /**
     259             :      * @brief Decide if mobility rules (like go to school/work) are used or not;
     260             :      * The mobility rules regarding hospitalization/ICU/quarantine are always used.
     261             :      * @param[in] param If true uses the mobility rules for changing location to school/work etc., else only the rules
     262             :      * regarding hospitalization/ICU/quarantine.
     263             :      */
     264             :     void use_mobility_rules(bool param);
     265             :     bool use_mobility_rules() const;
     266             : 
     267             :     /**
     268             :     * @brief Check if at least one Location with a specified LocationType exists.
     269             :     * @return True if there is at least one Location of LocationType `type`. False otherwise.
     270             :     */
     271       28899 :     bool has_location(LocationType type) const
     272             :     {
     273       28899 :         return m_has_locations[size_t(type)];
     274             :     }
     275             : 
     276             :     /**
     277             :     * @brief Check if at least one Location of every specified LocationType exists.
     278             :     * @tparam C A type of container of LocationType.
     279             :     * @param location_types A container of LocationType%s.
     280             :     * @return True if there is at least one Location of every LocationType in `location_types`. False otherwise.
     281             :     */
     282             :     template <class C = std::initializer_list<LocationType>>
     283       24156 :     bool has_locations(const C& location_types) const
     284             :     {
     285       81954 :         return std::all_of(location_types.begin(), location_types.end(), [&](auto loc) {
     286       57798 :             return has_location(loc);
     287       24156 :         });
     288             :     }
     289             : 
     290             :     /**
     291             :      * @brief Get the TestingStrategy.
     292             :      * @return Reference to the list of TestingScheme%s that are checked for testing.
     293             :      */
     294             :     TestingStrategy& get_testing_strategy();
     295             : 
     296             :     const TestingStrategy& get_testing_strategy() const;
     297             : 
     298             :     /** 
     299             :      * @brief The simulation parameters of the Model.
     300             :      */
     301             :     Parameters parameters;
     302             : 
     303             :     /**
     304             :     * Get the RandomNumberGenerator used by this Model for random events.
     305             :     * Persons use their own generators with the same key as the global one. 
     306             :     * @return The random number generator.
     307             :     */
     308         936 :     RandomNumberGenerator& get_rng()
     309             :     {
     310         936 :         return m_rng;
     311             :     }
     312             : 
     313             :     /**
     314             :      * @brief Add a TestingScheme to the set of schemes that are checked for testing at all Locations that have
     315             :      * the LocationType.
     316             :      * @param[in] loc_type LocationId key for TestingScheme to be added.
     317             :      * @param[in] scheme TestingScheme to be added.
     318             :      */
     319             :     void add_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme);
     320             : 
     321             :     /**
     322             :      * @brief Remove a TestingScheme from the set of schemes that are checked for testing at all Locations that have
     323             :      * the LocationType.
     324             :      * @param[in] loc_type LocationId key for TestingScheme to be added.
     325             :      * @param[in] scheme TestingScheme to be added.
     326             :      */
     327             :     void remove_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme);
     328             : 
     329             :     /**
     330             :      * @brief Get a reference to a Person from this Model.
     331             :      * @param[in] id A person's PersonId.
     332             :      * @return A reference to the Person.
     333             :      * @{
     334             :      */
     335       18828 :     Person& get_person(PersonId id)
     336             :     {
     337       18828 :         assert(id.get() < m_persons.size() && "Given PersonId is not in this Model.");
     338       18828 :         return m_persons[id.get()];
     339             :     }
     340             : 
     341        8910 :     const Person& get_person(PersonId id) const
     342             :     {
     343        8910 :         assert(id.get() < m_persons.size() && "Given PersonId is not in this Model.");
     344        8910 :         return m_persons[id.get()];
     345             :     }
     346             :     /** @} */
     347             : 
     348             :     /**
     349             :      * @brief Get the number of Person%s of a particular #InfectionState for all Cell%s.
     350             :      * @param[in] location A LocationId from the Model.
     351             :      * @param[in] t TimePoint of querry.
     352             :      * @param[in] state #InfectionState of interest.
     353             :      * @return Amount of Person%s of the #InfectionState in all Cell%s of the Location.
     354             :      */
     355       27315 :     size_t get_subpopulation(LocationId location, TimePoint t, InfectionState state) const
     356             :     {
     357       27315 :         return std::count_if(m_persons.begin(), m_persons.end(), [&](auto&& p) {
     358      186516 :             return p.get_location() == location && p.get_infection_state(t) == state;
     359       27315 :         });
     360             :     }
     361             : 
     362             :     /**
     363             :      * @brief Get the total number of Person%s at the Location.
     364             :      * @param[in] location A LocationId from the Model.
     365             :      * @return Number of Person%s in the location.
     366             :      */
     367         270 :     size_t get_number_persons(LocationId location) const
     368             :     {
     369         270 :         if (!m_is_local_population_cache_valid) {
     370           0 :             build_compute_local_population_cache();
     371             :         }
     372         270 :         return m_local_population_cache[location.get()];
     373             :     }
     374             : 
     375             :     // Change the Location of a Person. this requires that Location is part of this Model.
     376             :     /**
     377             :      * @brief Let a Person change to another Location.
     378             :      * @param[in] person PersonId of a Person from this Model.
     379             :      * @param[in] destination LocationId of the Location in this Model, which the Person should change to.
     380             :      * @param[in] mode The transport mode the person uses to change the Location.
     381             :      * @param[in] cells The cells within the destination the person should be in.
     382             :      */
     383         315 :     inline void change_location(PersonId person, LocationId destination, TransportMode mode = TransportMode::Unknown,
     384             :                                 const std::vector<uint32_t>& cells = {0})
     385             :     {
     386         315 :         LocationId origin = get_location(person).get_id();
     387             :         const bool has_changed_location =
     388         315 :             mio::abm::change_location(get_person(person), get_location(destination), mode, cells);
     389             :         // if the person has changed location, invalidate exposure caches but keep population caches valid
     390         315 :         if (has_changed_location) {
     391         315 :             m_are_exposure_caches_valid = false;
     392         315 :             if (m_is_local_population_cache_valid) {
     393         315 :                 --m_local_population_cache[origin.get()];
     394         315 :                 ++m_local_population_cache[destination.get()];
     395             :             }
     396             :         }
     397         315 :     }
     398             : 
     399             :     /**
     400             :      * @brief Let a person interact with the population at its current location.
     401             :      * @param[in] person PersonId of a person from this Model.
     402             :      * @param[in] t Time step of the simulation.
     403             :      * @param[in] dt Step size of the simulation.
     404             :      */
     405        2772 :     inline void interact(PersonId person, TimePoint t, TimeSpan dt)
     406             :     {
     407        2772 :         if (!m_are_exposure_caches_valid) {
     408             :             // checking caches is only needed for external calls
     409             :             // during simulation (i.e. in evolve()), the caches are computed in begin_step
     410           0 :             compute_exposure_caches(t, dt);
     411           0 :             m_are_exposure_caches_valid = true;
     412             :         }
     413        2772 :         auto personal_rng = PersonalRandomNumberGenerator(m_rng, get_person(person));
     414        8316 :         mio::abm::interact(personal_rng, get_person(person), get_location(person),
     415        5544 :                            m_air_exposure_rates_cache[get_location(person).get_id().get()],
     416        8316 :                            m_contact_exposure_rates_cache[get_location(person).get_id().get()], t, dt, parameters);
     417        2772 :     }
     418             : 
     419             :     /**
     420             :      * @brief Get a reference to a location in this Model.
     421             :      * @param[in] id LocationId of the Location.
     422             :      * @return Reference to the Location.
     423             :      * @{
     424             :      */
     425             :     const Location& get_location(LocationId id) const
     426             :     {
     427             :         assert(id != LocationId::invalid_id() && "Given LocationId must be valid.");
     428             :         assert(id < LocationId((uint32_t)m_locations.size()) && "Given LocationId is not in this Model.");
     429             :         return m_locations[id.get()];
     430             :     }
     431             : 
     432       23391 :     Location& get_location(LocationId id)
     433             :     {
     434       23391 :         assert(id != LocationId::invalid_id() && "Given LocationId must be valid.");
     435       23391 :         assert(id < LocationId((uint32_t)m_locations.size()) && "Given LocationId is not in this Model.");
     436       23391 :         return m_locations[id.get()];
     437             :     }
     438             :     /** @} */
     439             : 
     440             :     /**
     441             :      * @brief Get a reference to the location of a person.
     442             :      * @param[in] id PersonId of a person.
     443             :      * @return Reference to the Location.
     444             :      * @{
     445             :      */
     446       11439 :     inline Location& get_location(PersonId id)
     447             :     {
     448       11439 :         return get_location(get_person(id).get_location());
     449             :     }
     450             : 
     451             :     inline const Location& get_location(PersonId id) const
     452             :     {
     453             :         return get_location(get_person(id).get_location());
     454             :     }
     455             :     /** @} */
     456             : 
     457             : private:
     458             :     /**
     459             :      * @brief Person%s interact at their Location and may become infected.
     460             :      * @param[in] t The current TimePoint.
     461             :      * @param[in] dt The length of the time step of the Simulation.
     462             :      */
     463             :     void interaction(TimePoint t, TimeSpan dt);
     464             :     /**
     465             :      * @brief Person%s change location in the Model according to rules.
     466             :      * @param[in] t The current TimePoint.
     467             :      * @param[in] dt The length of the time step of the Simulation.
     468             :      */
     469             :     void perform_mobility(TimePoint t, TimeSpan dt);
     470             : 
     471             :     /// @brief Shape the cache and store how many Person%s are at any Location. Use from single thread!
     472             :     void build_compute_local_population_cache() const;
     473             : 
     474             :     /// @brief Shape the air and contact exposure cache according to the current Location%s.
     475             :     void build_exposure_caches();
     476             : 
     477             :     /**
     478             :      * @brief Store all air/contact exposures for the current simulation step.
     479             :      * @param[in] t Current TimePoint of the simulation.
     480             :      * @param[in] dt The duration of the simulation step.
     481             :      */
     482             :     void compute_exposure_caches(TimePoint t, TimeSpan dt);
     483             : 
     484             :     mutable Eigen::Matrix<std::atomic_int_fast32_t, Eigen::Dynamic, 1>
     485             :         m_local_population_cache; ///< Current number of Persons in a given location.
     486             :     Eigen::Matrix<AirExposureRates, Eigen::Dynamic, 1>
     487             :         m_air_exposure_rates_cache; ///< Cache for local exposure through droplets in #transmissions/day.
     488             :     Eigen::Matrix<ContactExposureRates, Eigen::Dynamic, 1>
     489             :         m_contact_exposure_rates_cache; ///< Cache for local exposure through contacts in #transmissions/day.
     490             :     bool m_is_local_population_cache_valid = false;
     491             :     bool m_are_exposure_caches_valid       = false;
     492             :     bool m_exposure_caches_need_rebuild    = true;
     493             : 
     494             :     std::vector<Person> m_persons; ///< Vector of every Person.
     495             :     std::vector<Location> m_locations; ///< Vector of every Location.
     496             :     std::bitset<size_t(LocationType::Count)>
     497             :         m_has_locations; ///< Flags for each LocationType, set if a Location of that type exists.
     498             :     TestingStrategy m_testing_strategy; ///< List of TestingScheme%s that are checked for testing.
     499             :     TripList m_trip_list; ///< List of all Trip%s the Person%s do.
     500             :     bool m_use_mobility_rules; ///< Whether mobility rules are considered.
     501             :     std::vector<std::pair<LocationType (*)(PersonalRandomNumberGenerator&, const Person&, TimePoint, TimeSpan,
     502             :                                            const Parameters&),
     503             :                           std::vector<LocationType>>>
     504             :         m_mobility_rules; ///< Rules that govern the mobility between Location%s.
     505             :     LocationId m_cemetery_id; // Central cemetery for all dead persons.
     506             :     RandomNumberGenerator m_rng; ///< Global random number generator
     507             : };
     508             : 
     509             : } // namespace abm
     510             : } // namespace mio
     511             : 
     512             : #endif

Generated by: LCOV version 1.14