Skip to content

Commit 494c015

Browse files
groeneycopybara-github
authored andcommitted
Adding DistanceToInfectionProbability into the tranmission model.
PiperOrigin-RevId: 322425960
1 parent f7fc6cb commit 494c015

13 files changed

+267
-56
lines changed

agent_based_epidemic_sim/applications/contact_tracing/graph_location.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,13 @@ class GraphLocation : public Location {
5252
auto second_infectivity = infectivity.find(edge.second);
5353
if (second_infectivity == infectivity.end()) continue;
5454

55-
// Generate a random proximity measure.
56-
float proximity = absl::Uniform(gen_, 0.0, 100.0);
57-
5855
// Note that we do not report times or durations. A visit either occurs
5956
// on a given day or not.
6057
infection_broker->Send(
6158
{{
6259
.agent_uuid = edge.first,
6360
.exposure =
6461
{
65-
.proximity = proximity,
6662
.infectivity = second_infectivity->second,
6763
},
6864
.exposure_type = InfectionOutcomeProto::CONTACT,
@@ -72,7 +68,6 @@ class GraphLocation : public Location {
7268
.agent_uuid = edge.second,
7369
.exposure =
7470
{
75-
.proximity = proximity,
7671
.infectivity = first_infectivity->second,
7772
},
7873
.exposure_type = InfectionOutcomeProto::CONTACT,

agent_based_epidemic_sim/core/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ cc_library(
145145
deps = [
146146
":integral_types",
147147
":visit",
148+
"//agent_based_epidemic_sim/util:ostream_overload",
148149
"@com_google_absl//absl/meta:type_traits",
149150
"@com_google_absl//absl/time",
150151
"@com_google_absl//absl/types:variant",
@@ -310,6 +311,12 @@ cc_test(
310311
],
311312
)
312313

314+
cc_library(
315+
name = "constants",
316+
hdrs = ["constants.h"],
317+
deps = [":integral_types"],
318+
)
319+
313320
cc_library(
314321
name = "seir_agent",
315322
srcs = [
@@ -321,6 +328,8 @@ cc_library(
321328
deps = [
322329
":agent",
323330
":broker",
331+
":constants",
332+
":enum_indexed_array",
324333
":event",
325334
":integral_types",
326335
":public_policy",
@@ -329,6 +338,7 @@ cc_library(
329338
":visit",
330339
":visit_generator",
331340
"//agent_based_epidemic_sim/port:logging",
341+
"@com_google_absl//absl/container:flat_hash_map",
332342
"@com_google_absl//absl/container:flat_hash_set",
333343
"@com_google_absl//absl/time",
334344
"@com_google_absl//absl/types:span",
@@ -340,6 +350,7 @@ cc_test(
340350
srcs = ["seir_agent_test.cc"],
341351
deps = [
342352
":broker",
353+
":constants",
343354
":integral_types",
344355
":public_policy",
345356
":seir_agent",

agent_based_epidemic_sim/core/aggregated_transmission_model.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ namespace {
2222
constexpr float kSusceptibility = 1;
2323
constexpr double kEpsilon = 1e-8;
2424

25+
// Used as a constant when computing a distance to infection probability.
26+
// Details in (broken link).
27+
constexpr float kDistanceInfectionProbabilityS = 1.5;
28+
29+
// Used as a constant when computing a distance to infection probability.
30+
// Details in (broken link).
31+
constexpr float kDistanceInfectionProbabilityB = 6.6;
32+
33+
constexpr std::array<float, kNumberMicroExposureBuckets> kIndexToMeters = {
34+
0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5};
35+
36+
float DistanceToInfectionProbability(float distance) {
37+
// Infectiousness is defined as a sigmoid curve that starts at 100% at 0
38+
// distance, is 50% at 4 meters, and is 0% at 10 meters. These values are
39+
// estimated heuristically.
40+
return 1 - 1 / (1 + std::exp(-kDistanceInfectionProbabilityS * distance +
41+
kDistanceInfectionProbabilityB));
42+
}
43+
44+
// Returns distance in meters as a function of MicroExposure bucket index.
45+
float ExtractDistanceFromIndex(size_t index) { return kIndexToMeters[index]; }
46+
2547
float ProbabilityExposureInfects(const Exposure& exposure,
2648
const float transmissibility) {
2749
return std::log(1 -
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#ifndef AGENT_BASED_EPIDEMIC_SIM_CORE_CONSTANTS_H_
2+
#define AGENT_BASED_EPIDEMIC_SIM_CORE_CONSTANTS_H_
3+
4+
#include <array>
5+
6+
#include "base/integral_types.h"
7+
8+
namespace abesim {
9+
inline const uint8 kMaxDaysAfterInfection = 14;
10+
11+
// This table maps days since infection to an infectivity factor. It is computed
12+
// using a Gamma CDF distribution and the parameters are defined in
13+
// (broken link).
14+
inline const std::array<float, kMaxDaysAfterInfection + 1> kInfectivityArray = {
15+
0.9706490058950823, 1.7351078930710577, 1.2019044766404108,
16+
0.6227475415319733, 0.2804915619270876, 0.1163222813140352,
17+
0.04568182435881363, 0.017259874839000156, 0.006335824965724157,
18+
0.002274361283270409, 0.0008019921659041529, 0.00027871327592632333,
19+
0.0000956941785834608, 0.0000325214311858168, 0};
20+
21+
} // namespace abesim
22+
23+
#endif // AGENT_BASED_EPIDEMIC_SIM_CORE_CONSTANTS_H_

agent_based_epidemic_sim/core/event.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
#include "absl/types/variant.h"
2424
#include "agent_based_epidemic_sim/core/integral_types.h"
2525
#include "agent_based_epidemic_sim/core/visit.h"
26+
#include "agent_based_epidemic_sim/util/ostream_overload.h"
2627

2728
namespace abesim {
2829

30+
inline constexpr uint8 kNumberMicroExposureBuckets = 10;
31+
2932
// An event representing a HealthState transition.
3033
struct HealthTransition {
3134
absl::Time time;
@@ -49,17 +52,23 @@ struct HealthTransition {
4952
static_assert(absl::is_trivially_copy_constructible<HealthTransition>::value,
5053
"HealthTransition must be trivially copyable.");
5154

52-
// Represents an exposure of a SUSCEPTIBLE to an INFECTIOUS entity (contact,
53-
// fomite, location etc.) with a given infectivity.
55+
OVERLOAD_ARRAY_OSTREAM_OPS
56+
57+
// Represents multiple "micro" exposures between a SUSCEPTIBLE and an INFECTIOUS
58+
// entity at different durations and distances (contact, fomite, location etc.)
59+
// with a constant infectivity and symptom_factor representing the infectivity
60+
// and symptom_factor of the infected individual.
5461
struct Exposure {
5562
absl::Time start_time;
5663
absl::Duration duration;
57-
float proximity;
64+
std::array<uint8, kNumberMicroExposureBuckets> micro_exposure_counts = {};
5865
float infectivity;
66+
float symptom_factor;
5967

6068
friend bool operator==(const Exposure& a, const Exposure& b) {
6169
return (a.start_time == b.start_time && a.duration == b.duration &&
62-
a.infectivity == b.infectivity);
70+
a.infectivity == b.infectivity &&
71+
a.micro_exposure_counts == b.micro_exposure_counts);
6372
}
6473

6574
friend bool operator!=(const Exposure& a, const Exposure& b) {
@@ -69,7 +78,8 @@ struct Exposure {
6978
friend std::ostream& operator<<(std::ostream& strm,
7079
const Exposure& exposure) {
7180
return strm << "{" << exposure.start_time << ", " << exposure.duration
72-
<< ", " << exposure.infectivity << "}";
81+
<< ", " << exposure.infectivity << ", "
82+
<< exposure.micro_exposure_counts << "}";
7383
}
7484
};
7585

agent_based_epidemic_sim/core/location_discrete_event_simulator.cc

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ namespace {
2525

2626
// TODO: Move into an event message about visiting infectious agents.
2727
constexpr float kInfectivity = 1;
28-
constexpr float kProximityMin = 0;
29-
constexpr float kProximityMax = 10;
30-
31-
absl::BitGen bitgen;
3228

3329
// Corresponds to the record of a visiting agent in a location.
3430
struct VisitNode {
@@ -80,27 +76,48 @@ absl::Duration Overlap(const Visit& a, const Visit& b) {
8076
std::max(a.start_time, b.start_time);
8177
}
8278

79+
const std::array<uint8, kNumberMicroExposureBuckets> GenerateMicroExposures(
80+
absl::Duration overlap) {
81+
std::array<uint8, kNumberMicroExposureBuckets> micro_exposure_counts = {};
82+
83+
// TODO: Use a distribution of duration@distance once it is
84+
// figured out.
85+
// Generate counts for each bucket and never over assign
86+
// duration.
87+
const uint8 total_counts_to_assign = absl::ToInt64Minutes(overlap);
88+
89+
if (total_counts_to_assign == 0) return micro_exposure_counts;
90+
91+
const uint8 buckets_to_fill =
92+
std::min(kNumberMicroExposureBuckets, total_counts_to_assign);
93+
const uint8 counts_per_bucket = total_counts_to_assign / buckets_to_fill;
94+
95+
for (auto i = 0; i < buckets_to_fill; i++) {
96+
micro_exposure_counts[i] = counts_per_bucket;
97+
}
98+
99+
return micro_exposure_counts;
100+
}
101+
83102
void RecordContact(VisitNode* a, VisitNode* b) {
84-
absl::Duration overlap = Overlap(*a->visit, *b->visit);
85-
float proximity = absl::Uniform(bitgen, kProximityMin, kProximityMax);
103+
const absl::Duration overlap = Overlap(*a->visit, *b->visit);
104+
const std::array<uint8, kNumberMicroExposureBuckets> micro_exposure_counts =
105+
GenerateMicroExposures(overlap);
106+
86107
a->contacts.push_back(
87108
{.other_uuid = b->visit->agent_uuid,
88109
.other_state = b->visit->health_state,
89-
.exposure = {
90-
.duration = overlap,
91-
.proximity = proximity,
92-
.infectivity = b->visit->health_state == HealthState::INFECTIOUS
93-
? kInfectivity
94-
: 0.0f}});
110+
.exposure = {.duration = overlap,
111+
.micro_exposure_counts = micro_exposure_counts,
112+
.infectivity = b->visit->infectivity,
113+
.symptom_factor = b->visit->symptom_factor}});
95114
b->contacts.push_back(
96115
{.other_uuid = a->visit->agent_uuid,
97116
.other_state = a->visit->health_state,
98-
.exposure = {
99-
.duration = overlap,
100-
.proximity = proximity,
101-
.infectivity = a->visit->health_state == HealthState::INFECTIOUS
102-
? kInfectivity
103-
: 0.0f}});
117+
.exposure = {.duration = overlap,
118+
.micro_exposure_counts = micro_exposure_counts,
119+
.infectivity = a->visit->infectivity,
120+
.symptom_factor = a->visit->symptom_factor}});
104121
}
105122
} // namespace
106123

agent_based_epidemic_sim/core/location_discrete_event_simulator_test.cc

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,36 @@ std::vector<InfectionOutcome> InfectionOutcomesFromContacts(
5050
return infection_outcomes;
5151
}
5252

53+
// TODO: Add test coverage of health states ASYMPTOMATIC, MILD,
54+
// SEVERE.
55+
// TODO: Test that micro_exposure_counts never over-assigns
56+
// durations.
5357
TEST(LocationDiscreteEventSimulatorTest, ContactTracing) {
5458
const int64 kUuid = 42LL;
5559
std::vector<Visit> visits{Visit{.location_uuid = 42LL,
5660
.agent_uuid = 0LL,
5761
.start_time = absl::FromUnixSeconds(0LL),
58-
.end_time = absl::FromUnixSeconds(100LL),
59-
.health_state = HealthState::INFECTIOUS},
62+
.end_time = absl::FromUnixSeconds(1000LL),
63+
.health_state = HealthState::INFECTIOUS,
64+
.infectivity = 1.0f},
6065
Visit{.location_uuid = 42LL,
6166
.agent_uuid = 1LL,
62-
.start_time = absl::FromUnixSeconds(10LL),
63-
.end_time = absl::FromUnixSeconds(50LL),
64-
.health_state = HealthState::SUSCEPTIBLE},
67+
.start_time = absl::FromUnixSeconds(100LL),
68+
.end_time = absl::FromUnixSeconds(500LL),
69+
.health_state = HealthState::SUSCEPTIBLE,
70+
.infectivity = 0.0f},
6571
Visit{.location_uuid = 42LL,
6672
.agent_uuid = 2LL,
67-
.start_time = absl::FromUnixSeconds(100LL),
68-
.end_time = absl::FromUnixSeconds(200LL),
69-
.health_state = HealthState::EXPOSED},
73+
.start_time = absl::FromUnixSeconds(1000LL),
74+
.end_time = absl::FromUnixSeconds(2000LL),
75+
.health_state = HealthState::EXPOSED,
76+
.infectivity = 0.0f},
7077
Visit{.location_uuid = 42LL,
7178
.agent_uuid = 3LL,
72-
.start_time = absl::FromUnixSeconds(40LL),
73-
.end_time = absl::FromUnixSeconds(60LL),
74-
.health_state = HealthState::RECOVERED}};
79+
.start_time = absl::FromUnixSeconds(400LL),
80+
.end_time = absl::FromUnixSeconds(600LL),
81+
.health_state = HealthState::RECOVERED,
82+
.infectivity = 0.0f}};
7583

7684
std::vector<Contact> contacts;
7785
MockInfectionBroker infection_broker;
@@ -80,12 +88,18 @@ TEST(LocationDiscreteEventSimulatorTest, ContactTracing) {
8088
{
8189
.other_uuid = 1,
8290
.other_state = HealthState::SUSCEPTIBLE,
83-
.exposure = {.duration = absl::Seconds(40), .infectivity = 0.0f},
91+
.exposure = {.duration = absl::Seconds(400),
92+
.micro_exposure_counts = {1, 1, 1, 1, 1, 1, 0, 0, 0,
93+
0},
94+
.infectivity = 0.0f},
8495
},
8596
{
8697
.other_uuid = 3,
8798
.other_state = HealthState::RECOVERED,
88-
.exposure = {.duration = absl::Seconds(20), .infectivity = 0.0f},
99+
.exposure = {.duration = absl::Seconds(200),
100+
.micro_exposure_counts = {1, 1, 1, 0, 0, 0, 0, 0, 0,
101+
0},
102+
.infectivity = 0.0f},
89103
}};
90104
EXPECT_CALL(infection_broker,
91105
Send(UnorderedElementsAreArray(InfectionOutcomesFromContacts(
@@ -97,12 +111,18 @@ TEST(LocationDiscreteEventSimulatorTest, ContactTracing) {
97111
{
98112
.other_uuid = 0,
99113
.other_state = HealthState::INFECTIOUS,
100-
.exposure = {.duration = absl::Seconds(40), .infectivity = 1.0f},
114+
.exposure = {.duration = absl::Seconds(400),
115+
.micro_exposure_counts = {1, 1, 1, 1, 1, 1, 0, 0, 0,
116+
0},
117+
.infectivity = 1.0f},
101118
},
102119
{
103120
.other_uuid = 3,
104121
.other_state = HealthState::RECOVERED,
105-
.exposure = {.duration = absl::Seconds(10), .infectivity = 0.0f},
122+
.exposure = {.duration = absl::Seconds(100),
123+
.micro_exposure_counts = {1, 0, 0, 0, 0, 0, 0, 0, 0,
124+
0},
125+
.infectivity = 0.0f},
106126
}};
107127
EXPECT_CALL(infection_broker,
108128
Send(UnorderedElementsAreArray(InfectionOutcomesFromContacts(
@@ -121,12 +141,20 @@ TEST(LocationDiscreteEventSimulatorTest, ContactTracing) {
121141
{
122142
.other_uuid = 0,
123143
.other_state = HealthState::INFECTIOUS,
124-
.exposure = {.duration = absl::Seconds(20), .infectivity = 1.0f},
144+
.exposure =
145+
{
146+
.duration = absl::Seconds(200),
147+
.micro_exposure_counts = {1, 1, 1, 0, 0, 0, 0, 0, 0, 0},
148+
.infectivity = 1.0f,
149+
},
125150
},
126151
{
127152
.other_uuid = 1,
128153
.other_state = HealthState::SUSCEPTIBLE,
129-
.exposure = {.duration = absl::Seconds(10), .infectivity = 0.0f},
154+
.exposure = {.duration = absl::Seconds(100),
155+
.micro_exposure_counts = {1, 0, 0, 0, 0, 0, 0, 0, 0,
156+
0},
157+
.infectivity = 0.0f},
130158
}};
131159
EXPECT_CALL(infection_broker,
132160
Send(UnorderedElementsAreArray(InfectionOutcomesFromContacts(

agent_based_epidemic_sim/core/pandemic.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ message ExposureProto {
6060
google.protobuf.Timestamp start_time = 1;
6161
google.protobuf.Duration duration = 2;
6262
float infectivity = 3;
63-
float proximity = 4;
63+
repeated fixed32 micro_exposure_counts = 4;
6464
}
6565

6666
// InfectionOutcomeProto represents a health state change brought about by a

0 commit comments

Comments
 (0)