Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

369 allowing tests to be performed at individual locations not only location types #752

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e932e12
Add template for TestingCriteria to be apply for LocationType/Location
khoanguyen-dev Aug 24, 2023
d083904
Fix error with python binding
khoanguyen-dev Aug 26, 2023
9f4da51
Change types of ages, infection states in TestingCriteria to bitset
khoanguyen-dev Sep 7, 2023
823ab08
Only one TestingCriteria per TestingScheme
khoanguyen-dev Sep 11, 2023
65028d0
Fix error in pycode for TestingScheme
khoanguyen-dev Sep 11, 2023
04fb3ed
Fix error in pycode for TestingScheme
khoanguyen-dev Sep 11, 2023
7ac2758
Fix error in pycode for TestingCriteria
khoanguyen-dev Sep 11, 2023
f33e4e8
Merge branch 'main' into 369-allowing-tests-to-be-performed-at-indivi…
khoanguyen-dev Sep 11, 2023
050e9e4
Revert TestingCriteria to accept vectors of AgeGroup and InfectionState
khoanguyen-dev Sep 12, 2023
83ef677
Only one TestingCriteria per TestingScheme
khoanguyen-dev Sep 13, 2023
515a368
Add map of Location-TestingScheme to TestingStrategy
khoanguyen-dev Sep 21, 2023
120232e
Fix errors in pycode
khoanguyen-dev Sep 22, 2023
898dee9
Use unordered_map in TestingStrategy and avoid copy in run_strategy
khoanguyen-dev Sep 27, 2023
f7c7c84
Merge branch 'main' into 369-allowing-tests-to-be-performed-at-indivi…
khoanguyen-dev Oct 9, 2023
eeb52ea
Merge branch 'main' into 369-allowing-tests-to-be-performed-at-indivi…
khoanguyen-dev Oct 18, 2023
6df42b3
Optimise testing_strategy
khoanguyen-dev Oct 18, 2023
668fb6b
Add test for initialising TestStrategy
khoanguyen-dev Oct 19, 2023
81172d7
Merge branch 'main' into 369-allowing-tests-to-be-performed-at-indivi…
khoanguyen-dev Oct 23, 2023
fd5b577
Fix errors in using bitset in TestingStrategy
khoanguyen-dev Oct 23, 2023
50f626f
Remove analyze_result 2.h
khoanguyen-dev Oct 23, 2023
692e1b5
Merge branch 'main' into 369-allowing-tests-to-be-performed-at-indivi…
khoanguyen-dev Nov 9, 2023
2d935f8
Small fixes according to Sascha's comments
khoanguyen-dev Nov 9, 2023
d3eb492
Remove a comment in initialisation of TestingCriteria
khoanguyen-dev Nov 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions cpp/examples/abm_history_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,15 @@ int main()
world.get_individualized_location(work).get_infection_parameters().set<mio::abm::MaximumContacts>(10);

// People can get tested at work (and do this with 0.5 probability) from time point 0 to day 30.
auto testing_min_time = mio::abm::days(1);
auto probability = 0.5;
auto start_date = mio::abm::TimePoint(0);
auto end_date = mio::abm::TimePoint(0) + mio::abm::days(30);
auto test_type = mio::abm::AntigenTest();
auto test_at_work = std::vector<mio::abm::LocationType>{mio::abm::LocationType::Work};
auto testing_criteria_work =
std::vector<mio::abm::TestingCriteria>{mio::abm::TestingCriteria({}, test_at_work, {})};
auto testing_min_time = mio::abm::days(1);
auto probability = 0.5;
auto start_date = mio::abm::TimePoint(0);
auto end_date = mio::abm::TimePoint(0) + mio::abm::days(30);
auto test_type = mio::abm::AntigenTest();
auto testing_criteria_work = mio::abm::TestingCriteria();
auto testing_scheme_work =
mio::abm::TestingScheme(testing_criteria_work, testing_min_time, start_date, end_date, test_type, probability);
world.get_testing_strategy().add_testing_scheme(testing_scheme_work);
world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work);

// Assign infection state to each person.
// The infection states are chosen randomly.
Expand Down
18 changes: 8 additions & 10 deletions cpp/examples/abm_minimal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,15 @@ int main()
world.get_individualized_location(work).get_infection_parameters().set<mio::abm::MaximumContacts>(10);

// People can get tested at work (and do this with 0.5 probability) from time point 0 to day 30.
auto testing_min_time = mio::abm::days(1);
auto probability = 0.5;
auto start_date = mio::abm::TimePoint(0);
auto end_date = mio::abm::TimePoint(0) + mio::abm::days(30);
auto test_type = mio::abm::AntigenTest();
auto test_at_work = std::vector<mio::abm::LocationType>{mio::abm::LocationType::Work};
auto testing_criteria_work =
std::vector<mio::abm::TestingCriteria>{mio::abm::TestingCriteria({}, test_at_work, {})};
auto testing_min_time = mio::abm::days(1);
auto probability = 0.5;
auto start_date = mio::abm::TimePoint(0);
auto end_date = mio::abm::TimePoint(0) + mio::abm::days(30);
auto test_type = mio::abm::AntigenTest();
auto testing_criteria_work = mio::abm::TestingCriteria();
auto testing_scheme_work =
mio::abm::TestingScheme(testing_criteria_work, testing_min_time, start_date, end_date, test_type, probability);
world.get_testing_strategy().add_testing_scheme(testing_scheme_work);
world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work);

// Assign infection state to each person.
// The infection states are chosen randomly.
Expand Down Expand Up @@ -173,4 +171,4 @@ int main()
sim.advance(tmax);

write_results_to_file(sim);
}
}
17 changes: 17 additions & 0 deletions cpp/models/abm/location_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <cstdint>
#include <limits>
#include <functional>

namespace mio
{
Expand Down Expand Up @@ -67,6 +68,14 @@ struct LocationId {
{
return !(index == rhs.index && type == rhs.type);
}

bool operator<(const LocationId& rhs) const
{
if (type == rhs.type) {
return index < rhs.index;
}
return (type < rhs.type);
}
};

struct GeographicalLocation {
Expand All @@ -90,4 +99,12 @@ struct GeographicalLocation {
} // namespace abm
} // namespace mio

template <>
struct std::hash<mio::abm::LocationId> {
std::size_t operator()(const mio::abm::LocationId& loc_id) const
{
return (std::hash<uint32_t>()(loc_id.index)) ^ (std::hash<uint32_t>()(static_cast<uint32_t>(loc_id.type)));
}
};

#endif
162 changes: 53 additions & 109 deletions cpp/models/abm/testing_strategy.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2020-2024 MEmilio
*
* Authors: Elisabeth Kluth, David Kerkmann, Sascha Korf, Martin J. Kuehn
* Authors: Elisabeth Kluth, David Kerkmann, Sascha Korf, Martin J. Kuehn, Khoa Nguyen
*
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
*
Expand All @@ -26,102 +26,50 @@ namespace mio
namespace abm
{

TestingCriteria::TestingCriteria(const std::vector<AgeGroup>& ages, const std::vector<LocationType>& location_types,
const std::vector<InfectionState>& infection_states)
: m_ages(ages)
, m_location_types(location_types)
, m_infection_states(infection_states)
TestingCriteria::TestingCriteria(const std::vector<AgeGroup>& ages, const std::vector<InfectionState>& infection_states)
{
for (auto age : ages) {
m_ages.insert(static_cast<size_t>(age));
}
for (auto infection_state : infection_states) {
m_infection_states.set(static_cast<size_t>(infection_state), true);
}
}

bool TestingCriteria::operator==(TestingCriteria other) const
bool TestingCriteria::operator==(const TestingCriteria& other) const
{
auto to_compare_ages = this->m_ages;
auto to_compare_infection_states = this->m_infection_states;
auto to_compare_location_types = this->m_location_types;

std::sort(to_compare_ages.begin(), to_compare_ages.end());
std::sort(other.m_ages.begin(), other.m_ages.end());
std::sort(to_compare_infection_states.begin(), to_compare_infection_states.end());
std::sort(other.m_infection_states.begin(), other.m_infection_states.end());
std::sort(to_compare_location_types.begin(), to_compare_location_types.end());
std::sort(other.m_location_types.begin(), other.m_location_types.end());

return to_compare_ages == other.m_ages && to_compare_location_types == other.m_location_types &&
to_compare_infection_states == other.m_infection_states;
return m_ages == other.m_ages && m_infection_states == other.m_infection_states;
}

void TestingCriteria::add_age_group(const AgeGroup age_group)
{
if (std::find(m_ages.begin(), m_ages.end(), age_group) == m_ages.end()) {
m_ages.push_back(age_group);
}
m_ages.insert(static_cast<size_t>(age_group));
}

void TestingCriteria::remove_age_group(const AgeGroup age_group)
{
auto last = std::remove(m_ages.begin(), m_ages.end(), age_group);
m_ages.erase(last, m_ages.end());
}

void TestingCriteria::add_location_type(const LocationType location_type)
{
if (std::find(m_location_types.begin(), m_location_types.end(), location_type) == m_location_types.end()) {
m_location_types.push_back(location_type);
}
}
void TestingCriteria::remove_location_type(const LocationType location_type)
{
auto last = std::remove(m_location_types.begin(), m_location_types.end(), location_type);
m_location_types.erase(last, m_location_types.end());
m_ages.erase(static_cast<size_t>(age_group));
}

void TestingCriteria::add_infection_state(const InfectionState infection_state)
{
if (std::find(m_infection_states.begin(), m_infection_states.end(), infection_state) == m_infection_states.end()) {
m_infection_states.push_back(infection_state);
}
m_infection_states.set(static_cast<size_t>(infection_state), true);
}

void TestingCriteria::remove_infection_state(const InfectionState infection_state)
{
auto last = std::remove(m_infection_states.begin(), m_infection_states.end(), infection_state);
m_infection_states.erase(last, m_infection_states.end());
}

bool TestingCriteria::evaluate(const Person& p, const Location& l, TimePoint t) const
{
return has_requested_age(p) && is_requested_location_type(l) && has_requested_infection_state(p, t);
}

bool TestingCriteria::has_requested_age(const Person& p) const
{
if (m_ages.empty()) {
return true; // no condition on the age
}
return std::find(m_ages.begin(), m_ages.end(), p.get_age()) != m_ages.end();
}

bool TestingCriteria::is_requested_location_type(const Location& l) const
{
if (m_location_types.empty()) {
return true; // no condition on the location
}
return std::find(m_location_types.begin(), m_location_types.end(), l.get_type()) != m_location_types.end();
m_infection_states.set(static_cast<size_t>(infection_state), false);
}

bool TestingCriteria::has_requested_infection_state(const Person& p, TimePoint t) const
bool TestingCriteria::evaluate(const Person& p, TimePoint t) const
{
if (m_infection_states.empty()) {
return true; // no condition on infection state
}
return std::find(m_infection_states.begin(), m_infection_states.end(), p.get_infection_state(t)) !=
m_infection_states.end();
// An empty vector of ages or none bitset of #InfectionStates% means that no condition on the corresponding property is set.
return (m_ages.empty() || m_ages.count(static_cast<size_t>(p.get_age()))) &&
xsaschako marked this conversation as resolved.
Show resolved Hide resolved
(m_infection_states.none() || m_infection_states[static_cast<size_t>(p.get_infection_state(t))]);
}

TestingScheme::TestingScheme(const std::vector<TestingCriteria>& testing_criteria,
TimeSpan minimal_time_since_last_test, TimePoint start_date, TimePoint end_date,
const GenericTest& test_type, double probability)
TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimeSpan minimal_time_since_last_test,
TimePoint start_date, TimePoint end_date, const GenericTest& test_type, double probability)
: m_testing_criteria(testing_criteria)
, m_minimal_time_since_last_test(minimal_time_since_last_test)
, m_start_date(start_date)
Expand All @@ -142,84 +90,80 @@ bool TestingScheme::operator==(const TestingScheme& other) const
//To be adjusted and also TestType should be static.
}

void TestingScheme::add_testing_criteria(const TestingCriteria criteria)
{
if (std::find(m_testing_criteria.begin(), m_testing_criteria.end(), criteria) == m_testing_criteria.end()) {
m_testing_criteria.push_back(criteria);
}
}

void TestingScheme::remove_testing_criteria(const TestingCriteria criteria)
{
auto last = std::remove(m_testing_criteria.begin(), m_testing_criteria.end(), criteria);
m_testing_criteria.erase(last, m_testing_criteria.end());
}

bool TestingScheme::is_active() const
{
return m_is_active;
}

void TestingScheme::update_activity_status(TimePoint t)
{
m_is_active = (m_start_date <= t && t <= m_end_date);
}

bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& person, const Location& location,
TimePoint t) const
bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& person, TimePoint t) const
{
if (person.get_time_since_negative_test() > m_minimal_time_since_last_test) {
double random = UniformDistribution<double>::get_instance()(rng);
if (random < m_probability) {
if (std::any_of(m_testing_criteria.begin(), m_testing_criteria.end(),
[&person, &location, t](TestingCriteria tr) {
return tr.evaluate(person, location, t);
})) {
if (m_testing_criteria.evaluate(person, t)) {
return !person.get_tested(rng, t, m_test_type.get_default());
}
}
}
return true;
}

TestingStrategy::TestingStrategy(const std::vector<TestingScheme>& testing_schemes)
: m_testing_schemes(testing_schemes)
TestingStrategy::TestingStrategy(
const std::unordered_map<LocationId, std::vector<TestingScheme>>& location_to_schemes_map)
: m_location_to_schemes_map(location_to_schemes_map)
{
}

void TestingStrategy::add_testing_scheme(const TestingScheme& scheme)
void TestingStrategy::add_testing_scheme(const LocationId& loc_id, const TestingScheme& scheme)
{
if (std::find(m_testing_schemes.begin(), m_testing_schemes.end(), scheme) == m_testing_schemes.end()) {
m_testing_schemes.push_back(scheme);
auto& schemes_vector = m_location_to_schemes_map[loc_id];
xsaschako marked this conversation as resolved.
Show resolved Hide resolved
if (std::find(schemes_vector.begin(), schemes_vector.end(), scheme) == schemes_vector.end()) {
schemes_vector.emplace_back(scheme);
}
}

void TestingStrategy::remove_testing_scheme(const TestingScheme& scheme)
void TestingStrategy::remove_testing_scheme(const LocationId& loc_id, const TestingScheme& scheme)
{
auto last = std::remove(m_testing_schemes.begin(), m_testing_schemes.end(), scheme);
m_testing_schemes.erase(last, m_testing_schemes.end());
auto& schemes_vector = m_location_to_schemes_map[loc_id];
auto last = std::remove(schemes_vector.begin(), schemes_vector.end(), scheme);
schemes_vector.erase(last, schemes_vector.end());
}

void TestingStrategy::update_activity_status(TimePoint t)
{
for (auto& ts : m_testing_schemes) {
ts.update_activity_status(t);
for (auto& [_, testing_schemes] : m_location_to_schemes_map) {
for (auto& scheme : testing_schemes) {
scheme.update_activity_status(t);
}
}
}

bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& person, const Location& location,
TimePoint t) const
TimePoint t)
{
// Person who is in quarantine but not yet home should go home. Otherwise they can't because they test positive.
if (location.get_type() == mio::abm::LocationType::Home && person.is_in_quarantine()) {
return true;
}
return std::all_of(m_testing_schemes.begin(), m_testing_schemes.end(),
[&person, &location, &rng, t](const TestingScheme& ts) {
if (ts.is_active()) {
return ts.run_scheme(rng, person, location, t);
}
return true;
});

// Combine two vectors of schemes at corresponding location and location stype
std::vector<TestingScheme>* schemes_vector[] = {
&m_location_to_schemes_map[LocationId{location.get_index(), location.get_type()}],
&m_location_to_schemes_map[LocationId{INVALID_LOCATION_INDEX, location.get_type()}]};

for (auto vec_ptr : schemes_vector) {
if (!std::all_of(vec_ptr->begin(), vec_ptr->end(), [&rng, &person, t](TestingScheme& ts) {
return !ts.is_active() || ts.run_scheme(rng, person, t);
})) {
return false;
}
}
return true;
}

} // namespace abm
Expand Down
Loading