LCOV - code coverage report
Current view: top level - models/abm - testing_strategy.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 100 100 100.0 %
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: 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             : 
      21             : #include "abm/testing_strategy.h"
      22             : #include "abm/location_id.h"
      23             : #include "memilio/utils/random_number_generator.h"
      24             : #include <utility>
      25             : 
      26             : namespace mio
      27             : {
      28             : namespace abm
      29             : {
      30             : 
      31          63 : TestingCriteria::TestingCriteria(const std::vector<AgeGroup>& ages, const std::vector<InfectionState>& infection_states)
      32             : {
      33          72 :     for (auto age : ages) {
      34           9 :         m_ages.set(static_cast<size_t>(age), true);
      35             :     }
      36         162 :     for (auto infection_state : infection_states) {
      37          99 :         m_infection_states.set(static_cast<size_t>(infection_state), true);
      38             :     }
      39          63 : }
      40             : 
      41          45 : bool TestingCriteria::operator==(const TestingCriteria& other) const
      42             : {
      43          45 :     return m_ages == other.m_ages && m_infection_states == other.m_infection_states;
      44             : }
      45             : 
      46           9 : void TestingCriteria::add_age_group(const AgeGroup age_group)
      47             : {
      48           9 :     m_ages.set(static_cast<size_t>(age_group), true);
      49           9 : }
      50             : 
      51          18 : void TestingCriteria::remove_age_group(const AgeGroup age_group)
      52             : {
      53          18 :     m_ages.set(static_cast<size_t>(age_group), false);
      54          18 : }
      55             : 
      56          54 : void TestingCriteria::add_infection_state(const InfectionState infection_state)
      57             : {
      58          54 :     m_infection_states.set(static_cast<size_t>(infection_state), true);
      59          54 : }
      60             : 
      61           9 : void TestingCriteria::remove_infection_state(const InfectionState infection_state)
      62             : {
      63           9 :     m_infection_states.set(static_cast<size_t>(infection_state), false);
      64           9 : }
      65             : 
      66         126 : bool TestingCriteria::evaluate(const Person& p, TimePoint t) const
      67             : {
      68             :     // An empty vector of ages or none bitset of #InfectionStates% means that no condition on the corresponding property is set.
      69         486 :     return (m_ages.none() || m_ages[static_cast<size_t>(p.get_age())]) &&
      70         477 :            (m_infection_states.none() || m_infection_states[static_cast<size_t>(p.get_infection_state(t))]);
      71             : }
      72             : 
      73          63 : TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimeSpan validity_period, TimePoint start_date,
      74          63 :                              TimePoint end_date, TestParameters test_parameters, double probability)
      75          63 :     : m_testing_criteria(testing_criteria)
      76          63 :     , m_validity_period(validity_period)
      77          63 :     , m_start_date(start_date)
      78          63 :     , m_end_date(end_date)
      79          63 :     , m_test_parameters(test_parameters)
      80          63 :     , m_probability(probability)
      81             : {
      82          63 : }
      83             : 
      84          27 : bool TestingScheme::operator==(const TestingScheme& other) const
      85             : {
      86          63 :     return this->m_testing_criteria == other.m_testing_criteria && this->m_validity_period == other.m_validity_period &&
      87          36 :            this->m_start_date == other.m_start_date && this->m_end_date == other.m_end_date &&
      88          18 :            this->m_test_parameters.sensitivity == other.m_test_parameters.sensitivity &&
      89          63 :            this->m_test_parameters.specificity == other.m_test_parameters.specificity &&
      90          45 :            this->m_probability == other.m_probability;
      91             :     //To be adjusted and also TestType should be static.
      92             : }
      93             : 
      94         117 : bool TestingScheme::is_active() const
      95             : {
      96         117 :     return m_is_active;
      97             : }
      98             : 
      99          72 : void TestingScheme::update_activity_status(TimePoint t)
     100             : {
     101          72 :     m_is_active = (m_start_date <= t && t <= m_end_date);
     102          72 : }
     103             : 
     104         108 : bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& person, TimePoint t) const
     105             : {
     106         108 :     auto test_result = person.get_test_result(m_test_parameters.type);
     107             :     // If the agent has a test result valid until now, use the result directly
     108         234 :     if ((test_result.time_of_testing > TimePoint(std::numeric_limits<int>::min())) &&
     109         126 :         (test_result.time_of_testing + m_validity_period >= t)) {
     110          18 :         return !test_result.result;
     111             :     }
     112             :     // Otherwise, the time_of_testing in the past (i.e. the agent has already performed it).
     113          90 :     if (m_testing_criteria.evaluate(person, t - m_test_parameters.required_time)) {
     114          63 :         double random = UniformDistribution<double>::get_instance()(rng);
     115          63 :         if (random < m_probability) {
     116          63 :             bool result = person.get_tested(rng, t - m_test_parameters.required_time, m_test_parameters);
     117          63 :             person.add_test_result(t, m_test_parameters.type, result);
     118          63 :             return !result;
     119             :         }
     120             :     }
     121          27 :     return true;
     122             : }
     123             : 
     124           9 : TestingStrategy::TestingStrategy(const std::vector<LocalStrategy>& location_to_schemes_map)
     125           9 :     : m_location_to_schemes_map(location_to_schemes_map.begin(), location_to_schemes_map.end())
     126             : {
     127           9 : }
     128             : 
     129          54 : void TestingStrategy::add_testing_scheme(const LocationType& loc_type, const LocationId& loc_id,
     130             :                                          const TestingScheme& scheme)
     131             : {
     132          54 :     auto iter_schemes =
     133          54 :         std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [&](const auto& p) {
     134          18 :             return p.type == loc_type && p.id == loc_id;
     135             :         });
     136          54 :     if (iter_schemes == m_location_to_schemes_map.end()) {
     137             :         //no schemes for this location yet, add a new list with one scheme
     138          36 :         m_location_to_schemes_map.push_back({loc_type, loc_id, std::vector<TestingScheme>(1, scheme)});
     139             :     }
     140             :     else {
     141             :         //add scheme to existing vector if the scheme doesn't exist yet
     142          18 :         auto& schemes = iter_schemes->schemes;
     143          18 :         if (std::find(schemes.begin(), schemes.end(), scheme) == schemes.end()) {
     144           9 :             schemes.push_back(scheme);
     145             :         }
     146             :     }
     147          54 : }
     148             : 
     149           9 : void TestingStrategy::remove_testing_scheme(const LocationType& loc_type, const LocationId& loc_id,
     150             :                                             const TestingScheme& scheme)
     151             : {
     152           9 :     auto iter_schemes =
     153           9 :         std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [&](const auto& p) {
     154           9 :             return p.type == loc_type && p.id == loc_id;
     155             :         });
     156           9 :     if (iter_schemes != m_location_to_schemes_map.end()) {
     157             :         //remove the scheme from the list
     158           9 :         auto& schemes_vector = iter_schemes->schemes;
     159           9 :         auto last            = std::remove(schemes_vector.begin(), schemes_vector.end(), scheme);
     160           9 :         schemes_vector.erase(last, schemes_vector.end());
     161             :         //delete the list of schemes for this location if no schemes left
     162           9 :         if (schemes_vector.empty()) {
     163           9 :             m_location_to_schemes_map.erase(iter_schemes);
     164             :         }
     165             :     }
     166           9 : }
     167             : 
     168         765 : void TestingStrategy::update_activity_status(TimePoint t)
     169             : {
     170         792 :     for (auto& [_type, _id, testing_schemes] : m_location_to_schemes_map) {
     171          54 :         for (auto& scheme : testing_schemes) {
     172          27 :             scheme.update_activity_status(t);
     173             :         }
     174             :     }
     175         765 : }
     176             : 
     177         378 : bool TestingStrategy::run_strategy(PersonalRandomNumberGenerator& rng, Person& person, const Location& location,
     178             :                                    TimePoint t)
     179             : {
     180             :     // A Person is always allowed to go home and this is never called if a person is not discharged from a hospital or ICU.
     181         378 :     if (location.get_type() == mio::abm::LocationType::Home) {
     182          54 :         return true;
     183             :     }
     184             : 
     185             :     // If the Person does not comply to Testing where there is a testing scheme at the target location, it is not allowed to enter.
     186         324 :     if (!person.is_compliant(rng, InterventionType::Testing)) {
     187          18 :         return false;
     188             :     }
     189             : 
     190             :     // Lookup schemes for this specific location as well as the location type
     191             :     // Lookup in std::vector instead of std::map should be much faster unless for large numbers of schemes
     192        1791 :     for (auto key : {std::make_pair(location.get_type(), location.get_id()),
     193        2097 :                      std::make_pair(location.get_type(), LocationId::invalid_id())}) {
     194         612 :         auto iter_schemes =
     195         612 :             std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [&](const auto& p) {
     196         198 :                 return p.type == key.first && p.id == key.second;
     197             :             });
     198         612 :         if (iter_schemes != m_location_to_schemes_map.end()) {
     199             :             // Apply all testing schemes that are found
     200          81 :             auto& schemes = iter_schemes->schemes;
     201             :             // Whether the Person is allowed to enter or not depends on the test result(s).
     202         162 :             if (!std::all_of(schemes.begin(), schemes.end(), [&rng, &person, t](TestingScheme& ts) {
     203         261 :                     return !ts.is_active() || ts.run_scheme(rng, person, t);
     204             :                 })) {
     205          45 :                 return false;
     206             :             }
     207             :         }
     208             :     }
     209         261 :     return true;
     210             : }
     211             : 
     212             : } // namespace abm
     213             : } // namespace mio

Generated by: LCOV version 1.14