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
|