Skip to content

Commit cff9ccb

Browse files
1274 normalizing of exposure rate (#1322)
- Add a cache for the number of persons per cell and age group. - Use this cache to normalize the exposure rate according to the number of persons inside a cell. Co-authored-by: reneSchm <49305466+reneSchm@users.noreply.github.com>
1 parent aa541c0 commit cff9ccb

File tree

7 files changed

+205
-21
lines changed

7 files changed

+205
-21
lines changed

cpp/models/abm/model.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ PersonId Model::add_person(Person&& person)
7171
auto& new_person = m_persons.back();
7272
if (m_is_local_population_cache_valid) {
7373
++m_local_population_cache[new_person.get_location().get()];
74+
for (CellIndex cell : new_person.get_cells()) {
75+
auto& local_population_by_age = m_local_population_by_age_cache[new_person.get_location().get()];
76+
++local_population_by_age[{cell, new_person.get_age()}];
77+
}
7478
}
7579
return new_person.get_id();
7680
}
@@ -221,18 +225,33 @@ void Model::build_compute_local_population_cache() const
221225
{
222226
PRAGMA_OMP(single)
223227
{
228+
log_debug("Building local population cache for ABM.");
224229
const size_t num_locations = m_locations.size();
225230
const size_t num_persons = m_persons.size();
226231
m_local_population_cache.resize(num_locations);
232+
m_local_population_by_age_cache.resize(num_locations);
233+
227234
PRAGMA_OMP(taskloop)
228235
for (size_t i = 0; i < num_locations; i++) {
229236
m_local_population_cache[i] = 0;
237+
m_local_population_by_age_cache[i].resize(
238+
{CellIndex(m_locations[i].get_cells().size()), AgeGroup(parameters.get_num_groups())});
239+
for (CellIndex cell = 0; cell < CellIndex(m_locations[i].get_cells().size()); ++cell) {
240+
for (AgeGroup group = AgeGroup(0); group < AgeGroup(parameters.get_num_groups()); ++group) {
241+
auto& local_population_by_age = m_local_population_by_age_cache[i];
242+
local_population_by_age[{cell, group}] = 0;
243+
}
244+
}
230245
} // implicit taskloop barrier
231246
PRAGMA_OMP(taskloop)
232247
for (size_t i = 0; i < num_persons; i++) {
233248
if (m_activeness_statuses[i]) {
234249
assert(m_persons[i].get_location_model_id() == m_id && "Person is not in this model but still active.");
235250
++m_local_population_cache[m_persons[i].get_location().get()];
251+
for (CellIndex cell : m_persons[i].get_cells()) {
252+
auto& local_population_by_age = m_local_population_by_age_cache[m_persons[i].get_location().get()];
253+
++local_population_by_age[{cell, m_persons[i].get_age()}];
254+
}
236255
}
237256
} // implicit taskloop barrier
238257
} // implicit single barrier
@@ -242,6 +261,7 @@ void Model::build_exposure_caches()
242261
{
243262
PRAGMA_OMP(single)
244263
{
264+
mio::log_debug("Building exposure caches for ABM.");
245265
const size_t num_locations = m_locations.size();
246266
m_air_exposure_rates_cache.resize(num_locations);
247267
m_contact_exposure_rates_cache.resize(num_locations);
@@ -297,6 +317,20 @@ void Model::compute_exposure_caches(TimePoint t, TimeSpan dt)
297317
get_location(person.get_location()), parameters, t, dt);
298318
}
299319
} // implicit taskloop barrier
320+
321+
// 3) normalize contributions for each location
322+
323+
// make sure that the caches are computed
324+
if (!m_is_local_population_cache_valid) {
325+
build_compute_local_population_cache();
326+
m_is_local_population_cache_valid = true;
327+
}
328+
PRAGMA_OMP(taskloop)
329+
for (size_t location = 0; location < num_locations; ++location) {
330+
mio::abm::normalize_exposure_contribution(m_contact_exposure_rates_cache[location],
331+
m_local_population_by_age_cache[location]);
332+
} // implicit taskloop barrier
333+
300334
} // implicit single barrier
301335
}
302336

cpp/models/abm/model.h

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class Model
9494
Model(const Model& other, int id = 0)
9595
: parameters(other.parameters)
9696
, m_local_population_cache()
97+
, m_local_population_by_age_cache()
9798
, m_air_exposure_rates_cache()
9899
, m_contact_exposure_rates_cache()
99100
, m_is_local_population_cache_valid(false)
@@ -374,16 +375,34 @@ class Model
374375
/**
375376
* @brief Get the total number of Person%s at the Location.
376377
* @param[in] location A LocationId from the Model.
377-
* @return Number of Person%s in the location.
378+
* @return Number of Person%s at the location.
378379
*/
379380
size_t get_number_persons(LocationId location) const
380381
{
381382
if (!m_is_local_population_cache_valid) {
382383
build_compute_local_population_cache();
384+
m_is_local_population_cache_valid = true;
383385
}
384386
return m_local_population_cache[location.get()];
385387
}
386388

389+
/**
390+
* @brief Get the number of Person%s of a specific AgeGroup in a specific Cell at the Location.
391+
* @param[in] location A LocationId from the Model.
392+
* @param[in] cell_idx Index of the Cell.
393+
* @param[in] AgeGroup An AgeGroup from the Model.
394+
* @return Number of Person%s of the AgeGroup in the Cell at the Location.
395+
*/
396+
size_t get_number_persons_age(LocationId location, CellIndex cell_idx, AgeGroup age) const
397+
{
398+
if (!m_is_local_population_cache_valid) {
399+
build_compute_local_population_cache();
400+
m_is_local_population_cache_valid = true;
401+
}
402+
auto& m_local_population_by_age = m_local_population_by_age_cache[location.get()];
403+
return m_local_population_by_age[{cell_idx, age}];
404+
}
405+
387406
// Change the Location of a Person. this requires that Location is part of this Model.
388407
/**
389408
* @brief Let a Person change to another Location.
@@ -503,6 +522,7 @@ class Model
503522

504523
/**
505524
* @brief Store all air/contact exposures for the current simulation step.
525+
* This will also compute the local population cache if it is not valid, as it is required for the computation of the exposure rates.
506526
* @param[in] t Current TimePoint of the simulation.
507527
* @param[in] dt The duration of the simulation step.
508528
*/
@@ -520,13 +540,22 @@ class Model
520540
const std::vector<uint32_t>& cells = {0})
521541
{
522542
LocationId origin = get_location(person).get_id();
543+
auto old_cells = person.get_cells();
523544
const bool has_changed_location = mio::abm::change_location(person, get_location(destination), mode, cells);
524545
// if the person has changed location, invalidate exposure caches but keep population caches valid
525546
if (has_changed_location) {
526547
m_are_exposure_caches_valid = false;
527548
if (m_is_local_population_cache_valid) {
528549
--m_local_population_cache[origin.get()];
529550
++m_local_population_cache[destination.get()];
551+
for (CellIndex cell : old_cells) {
552+
auto& local_population_by_age = m_local_population_by_age_cache[origin.get()];
553+
--local_population_by_age[{cell, person.get_age()}];
554+
}
555+
for (CellIndex cell : cells) {
556+
auto& local_population_by_age = m_local_population_by_age_cache[destination.get()];
557+
++local_population_by_age[{cell, person.get_age()}];
558+
}
530559
}
531560
}
532561
}
@@ -575,9 +604,10 @@ class Model
575604
m_are_exposure_caches_valid = true;
576605
}
577606
auto personal_rng = PersonalRandomNumberGenerator(person);
578-
mio::abm::interact(personal_rng, person, get_location(person.get_location()),
579-
m_air_exposure_rates_cache[person.get_location().get()],
580-
m_contact_exposure_rates_cache[person.get_location().get()], t, dt, parameters);
607+
auto location = person.get_location();
608+
mio::abm::interact(personal_rng, person, get_location(location),
609+
m_local_population_by_age_cache[location.get()], m_air_exposure_rates_cache[location.get()],
610+
m_contact_exposure_rates_cache[location.get()], t, dt, parameters);
581611
}
582612

583613
/**
@@ -608,13 +638,15 @@ class Model
608638

609639
mutable Eigen::Matrix<std::atomic_int_fast32_t, Eigen::Dynamic, 1>
610640
m_local_population_cache; ///< Current number of Persons in a given location.
641+
mutable Eigen::Matrix<PopulationByAge, Eigen::Dynamic, 1>
642+
m_local_population_by_age_cache; ///<Current number of Persons per AgeGroup in a given location.
611643
Eigen::Matrix<AirExposureRates, Eigen::Dynamic, 1>
612644
m_air_exposure_rates_cache; ///< Cache for local exposure through droplets in #transmissions/day.
613645
Eigen::Matrix<ContactExposureRates, Eigen::Dynamic, 1>
614646
m_contact_exposure_rates_cache; ///< Cache for local exposure through contacts in #transmissions/day.
615-
bool m_is_local_population_cache_valid = false;
616-
bool m_are_exposure_caches_valid = false;
617-
bool m_exposure_caches_need_rebuild = true;
647+
mutable bool m_is_local_population_cache_valid = false;
648+
bool m_are_exposure_caches_valid = false;
649+
bool m_exposure_caches_need_rebuild = true;
618650

619651
int m_id; ///< Model id. Is only used for abm graph model or hybrid model.
620652
std::vector<Person> m_persons; ///< Vector of every Person.

cpp/models/abm/model_functions.cpp

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,22 @@ namespace abm
3434

3535
ScalarType daily_transmissions_by_contacts(const ContactExposureRates& rates, const CellIndex cell_index,
3636
const VirusVariant virus, const AgeGroup age_receiver,
37-
const LocalInfectionParameters& params)
37+
size_t age_receiver_group_size, const LocalInfectionParameters& params)
3838
{
3939
assert(age_receiver < rates.size<AgeGroup>());
4040
ScalarType prob = 0;
4141
for (AgeGroup age_transmitter(0); age_transmitter < rates.size<AgeGroup>(); ++age_transmitter) {
42-
prob +=
43-
rates[{cell_index, virus, age_transmitter}] * params.get<ContactRates>()[{age_receiver, age_transmitter}];
42+
if (age_receiver == age_transmitter &&
43+
age_receiver_group_size > 1) // adjust for the person not meeting themself
44+
{
45+
prob += rates[{cell_index, virus, age_transmitter}] *
46+
params.get<ContactRates>()[{age_receiver, age_transmitter}] * age_receiver_group_size /
47+
(age_receiver_group_size - 1);
48+
}
49+
else {
50+
prob += rates[{cell_index, virus, age_transmitter}] *
51+
params.get<ContactRates>()[{age_receiver, age_transmitter}];
52+
}
4453
}
4554
return prob;
4655
}
@@ -52,8 +61,9 @@ ScalarType daily_transmissions_by_air(const AirExposureRates& rates, const CellI
5261
}
5362

5463
void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const Location& location,
55-
const AirExposureRates& local_air_exposure, const ContactExposureRates& local_contact_exposure,
56-
const TimePoint t, const TimeSpan dt, const Parameters& global_parameters)
64+
const PopulationByAge& local_population_by_age, const AirExposureRates& local_air_exposure,
65+
const ContactExposureRates& local_contact_exposure, const TimePoint t, const TimeSpan dt,
66+
const Parameters& global_parameters)
5767
{
5868
// make sure all dimensions are set correctly and all indices are valid
5969
assert(location.get_cells().size() == local_air_exposure.size<CellIndex>().get());
@@ -72,13 +82,14 @@ void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const
7282
auto age_receiver = person.get_age();
7383
ScalarType mask_protection = person.get_mask_protective_factor(global_parameters);
7484
assert(person.get_cells().size() && "Person is in multiple cells. Interact logic is incorrect at the moment.");
75-
for (auto cell_index : person.get_cells()) {
85+
for (CellIndex cell_index : person.get_cells()) {
7686
std::pair<VirusVariant, ScalarType> local_indiv_trans_prob[static_cast<uint32_t>(VirusVariant::Count)];
7787
for (uint32_t v = 0; v != static_cast<uint32_t>(VirusVariant::Count); ++v) {
78-
VirusVariant virus = static_cast<VirusVariant>(v);
88+
VirusVariant virus = static_cast<VirusVariant>(v);
89+
size_t local_population_by_age_receiver = local_population_by_age[{cell_index, age_receiver}];
7990
ScalarType local_indiv_trans_prob_v =
8091
(daily_transmissions_by_contacts(local_contact_exposure, cell_index, virus, age_receiver,
81-
local_parameters) +
92+
local_population_by_age_receiver, local_parameters) +
8293
daily_transmissions_by_air(local_air_exposure, cell_index, virus, global_parameters)) *
8394
(1 - mask_protection) * (1 - person.get_protection_factor(t, virus, global_parameters));
8495

@@ -129,6 +140,23 @@ void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExpo
129140
}
130141
}
131142

143+
void normalize_exposure_contribution(ContactExposureRates& local_contact_exposure,
144+
const PopulationByAge& local_population_by_age)
145+
{
146+
// make sure all dimensions are set correctly and all indices are valid
147+
assert(local_population_by_age.size<AgeGroup>() == local_contact_exposure.size<AgeGroup>());
148+
assert(local_population_by_age.size<CellIndex>() == local_contact_exposure.size<CellIndex>());
149+
assert(local_contact_exposure.size<VirusVariant>() == VirusVariant::Count);
150+
151+
for (auto index : make_index_range(local_contact_exposure.size())) {
152+
auto age_index = reduce_index<Index<CellIndex, AgeGroup>>(index);
153+
if (local_population_by_age[age_index] > 0) {
154+
// this instruction is not and does not need to be atomic
155+
local_contact_exposure[index] = local_contact_exposure[index] / local_population_by_age[age_index];
156+
}
157+
}
158+
}
159+
132160
bool change_location(Person& person, const Location& destination, const TransportMode mode,
133161
const std::vector<uint32_t>& cells)
134162
{

cpp/models/abm/model_functions.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ namespace abm
3737
* @param[in] cell_index Cell index of the Cell.
3838
* @param[in] virus VirusVariant of interest.
3939
* @param[in] age_receiver AgeGroup of the receiving Person.
40+
* @param[in] age_receiver_group_size Number of persons in the AgeGroup of the receiving Person.
4041
* @param[in] params The local infection parameters.
4142
* @return Average amount of Infection%s with the virus from the AgeGroup of the transmitter per day.
4243
*/
4344
ScalarType daily_transmissions_by_contacts(const ContactExposureRates& rates, const CellIndex cell_index,
4445
const VirusVariant virus, const AgeGroup age_receiver,
45-
const LocalInfectionParameters& params);
46+
size_t age_receiver_group_size, const LocalInfectionParameters& params);
4647

4748
/**
4849
* @brief Compute the number of daily transmissions for aerosol transmission of a virus in a cell.
@@ -69,20 +70,32 @@ void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExpo
6970
const Person& person, const Location& location, const Parameters& params,
7071
const TimePoint t, const TimeSpan dt);
7172

73+
using PopulationByAge = CustomIndexArray<std::atomic_int_fast32_t, CellIndex, AgeGroup>;
74+
75+
/**
76+
* @brief Normalize contact exposure rate to average exposure per contact per time (from total exposure per time).
77+
* @param[in, out] local_contact_exposure Exposure rates through contacts for the local population.
78+
* @param[in] local_population_by_age Local population by AgeGroup%s.
79+
*/
80+
void normalize_exposure_contribution(ContactExposureRates& local_contact_exposure,
81+
const PopulationByAge& local_population_by_age);
82+
7283
/**
7384
* @brief Let a Person interact with the population at its current Location, possibly getting infected.
7485
* @param[in, out] rng PersonalRandomNumberGenerator for this Person.
7586
* @param[in, out] person The person to interact with the local population.
7687
* @param[in] location The person's current location.
88+
* @param[in] local_population_by_age Local population by AgeGroup%s at the given location.
7789
* @param[in] local_air_exposure Precomputed exposure rates by aerosols for the local population.
7890
* @param[in] local_contact_exposure Precomputed exposure by rates contacts for the local population.
7991
* @param[in] t Current Simulation time.
8092
* @param[in] dt Length of the current Simulation time step.
8193
* @param[in] global_parameters Parameters of the Model.
8294
*/
8395
void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const Location& location,
84-
const AirExposureRates& local_air_exposure, const ContactExposureRates& local_contact_exposure,
85-
const TimePoint t, const TimeSpan dt, const Parameters& global_parameters);
96+
const PopulationByAge& local_population_by_age, const AirExposureRates& local_air_exposure,
97+
const ContactExposureRates& local_contact_exposure, const TimePoint t, const TimeSpan dt,
98+
const Parameters& global_parameters);
8699
/**
87100
* @brief Change a persons location to another location.
88101
* If the person already is at the destination, neither mode nor cells are set.

cpp/tests/abm_helpers.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,19 @@ void interact_testing(mio::abm::PersonalRandomNumberGenerator& personal_rng, mio
6565
for (const mio::abm::Person& p : local_population) {
6666
add_exposure_contribution(local_air_exposure, local_contact_exposure, p, location, global_parameters, t, dt);
6767
}
68+
// count local population by age group and cell
69+
mio::abm::PopulationByAge local_population_by_age;
70+
local_population_by_age.resize(
71+
{mio::abm::CellIndex(location.get_cells().size()), mio::AgeGroup(global_parameters.get_num_groups())});
72+
std::for_each(local_population_by_age.begin(), local_population_by_age.end(), [](auto& r) {
73+
r = 0; // initialize with 0
74+
});
75+
for (const mio::abm::Person& p : local_population) {
76+
for (mio::abm::CellIndex cell_index : p.get_cells()) {
77+
local_population_by_age[{cell_index, p.get_age()}]++;
78+
}
79+
}
6880
// run interaction
69-
mio::abm::interact(personal_rng, person, location, local_air_exposure, local_contact_exposure, t, dt,
70-
global_parameters);
81+
mio::abm::interact(personal_rng, person, location, local_population_by_age, local_air_exposure,
82+
local_contact_exposure, t, dt, global_parameters);
7183
}

0 commit comments

Comments
 (0)