From b40aedc9f11d18408ef90f9632afed8d47ef3ffe Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:32:26 +0100 Subject: [PATCH 01/37] Add struct for TestingResult in Person --- cpp/models/abm/infection.h | 2 +- cpp/models/abm/parameters.h | 23 ++++++++++++++++++++--- cpp/models/abm/person.cpp | 4 ++++ cpp/models/abm/person.h | 12 ++++++++++++ cpp/models/abm/trip_list.h | 2 ++ cpp/models/abm/world.cpp | 5 +++++ cpp/models/abm/world.h | 7 +++++++ 7 files changed, 51 insertions(+), 4 deletions(-) diff --git a/cpp/models/abm/infection.h b/cpp/models/abm/infection.h index f882d22af2..00f0ca5ed2 100644 --- a/cpp/models/abm/infection.h +++ b/cpp/models/abm/infection.h @@ -20,11 +20,11 @@ #ifndef EPI_ABM_INFECTION_H #define EPI_ABM_INFECTION_H +#include "abm/person.h" #include "abm/time.h" #include "abm/infection_state.h" #include "abm/virus_variant.h" #include "abm/parameters.h" -#include "abm/person.h" #include diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index ad1d37b49a..c6452fcb40 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -314,13 +314,15 @@ struct HighViralLoadProtectionFactor { struct TestParameters { UncertainValue sensitivity; UncertainValue specificity; + TimeSpan required_time; + TimeSpan validity_time; }; struct GenericTest { using Type = TestParameters; static Type get_default() { - return Type{0.9, 0.99}; + return Type{0.9, 0.99, hours(24), hours(24)}; } static std::string name() { @@ -328,6 +330,21 @@ struct GenericTest { } }; +/** + * @brief Reliability of an AntibodyTest. + */ +struct AntibodyTest : public GenericTest { + using Type = TestParameters; + static Type get_default() + { + return Type{0.8, 0.88, minutes(30), hours(24)}; + } + static std::string name() + { + return "AntigenTest"; + } +}; + /** * @brief Reliability of an AntigenTest. */ @@ -335,7 +352,7 @@ struct AntigenTest : public GenericTest { using Type = TestParameters; static Type get_default() { - return Type{0.8, 0.88}; + return Type{0.8, 0.88, minutes(30), hours(24)}; } static std::string name() { @@ -350,7 +367,7 @@ struct PCRTest : public GenericTest { using Type = TestParameters; static Type get_default() { - return Type{0.9, 0.99}; + return Type{0.9, 0.99, days(1), days(3)}; } static std::string name() { diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 51ecc3a5e8..45e47242fb 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -299,5 +299,9 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const t.days() - latest_protection.second.days()); } +bool Person::has_valid_test(TimePoint t) const { + return t > m_latest_test_result.time && m_latest_test_result.result; +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 8ca7faa323..deb87d0a13 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -455,6 +455,11 @@ class Person */ std::pair get_latest_protection() const; + /** + * @brief Get the latest #Infection or #Vaccination and its initial TimePoint of the Person. + */ + bool has_valid_test(TimePoint t) const; + /** * serialize this. * @see mio::serialize @@ -488,6 +493,12 @@ class Person } private: + struct TestingResult { + TimePoint time; + GenericTest type; + bool result; + }; + observer_ptr m_location; ///< Current Location of the Person. std::vector m_assigned_locations; /**! Vector with the indices of the assigned Locations so that the Person always visits the same Home or School etc. */ @@ -507,6 +518,7 @@ class Person uint32_t m_person_id; ///< Id of the Person. std::vector m_cells; ///< Vector with all Cell%s the Person visits at its current Location. Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator + TestingResult m_latest_test_result; }; } // namespace abm diff --git a/cpp/models/abm/trip_list.h b/cpp/models/abm/trip_list.h index d0afc7cc7b..e4a54ea9b6 100644 --- a/cpp/models/abm/trip_list.h +++ b/cpp/models/abm/trip_list.h @@ -34,6 +34,8 @@ namespace mio namespace abm { +class Infection; + /** * @brief A trip describes a migration from one Location to another Location. */ diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 95cf10d732..884453ab3d 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -239,5 +239,10 @@ const TestingStrategy& World::get_testing_strategy() const return m_testing_strategy; } +void World::update_trip_list(TimePoint t, TimeSpan dt) +{ + +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index e0d3a4c828..15be9e05aa 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -301,6 +301,13 @@ class World */ void remove_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme); + /** + * @brief Update the trip list in accodance with the tests. + * @param[in] t Current time. + * @param[in] dt Length of the time step. + */ + void update_trip_list(TimePoint t, TimeSpan dt); + private: /** * @brief Person%s interact at their Location and may become infected. From 20ddc7e7639ac0d13dc9558dfb74db975a6a18c2 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Wed, 15 Nov 2023 15:31:08 +0100 Subject: [PATCH 02/37] Add functions for TestResult in Person --- cpp/models/abm/parameters.h | 2 +- cpp/models/abm/person.cpp | 25 +++++++++++++++++++++++-- cpp/models/abm/person.h | 13 +++++++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index c6452fcb40..7c2c264a05 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -315,7 +315,7 @@ struct TestParameters { UncertainValue sensitivity; UncertainValue specificity; TimeSpan required_time; - TimeSpan validity_time; + TimeSpan validity_period; }; struct GenericTest { diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 45e47242fb..c6c8e3e271 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -299,8 +299,29 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const t.days() - latest_protection.second.days()); } -bool Person::has_valid_test(TimePoint t) const { - return t > m_latest_test_result.time && m_latest_test_result.result; +void Person::add_test_result(const TestResult& result) +{ + // Remove outdated test results or replace the old result of the same type + m_test_results.erase(std::remove_if(m_test_results.begin(), m_test_results.end(), + [result](const TestResult& old_result) { + return old_result.type.name == result.type.name || + (old_result.time_of_testing + old_result.validity_period) < + result.time_of_testing; + }), + m_test_results.end()); + + m_test_results.push_back(result); +} + +bool Person::has_valid_test_result(GenericTest type, TimePoint t) const +{ + for (const auto& result : m_test_results) { + if (result.type.name == type.name && result.result == true && + (result.time_of_testing + result.validity_period) > t) { + return true; + } + } + return false; } } // namespace abm diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index deb87d0a13..9dc63d7541 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -492,13 +492,18 @@ class Person loc, age, id); } -private: - struct TestingResult { - TimePoint time; + struct TestResult { + TimePoint time_of_testing; GenericTest type; bool result; + TimeSpan validity_period; }; + void add_test_result(const TestResult& result); + + bool has_valid_test_result(GenericTest type, TimePoint t) const; + +private: observer_ptr m_location; ///< Current Location of the Person. std::vector m_assigned_locations; /**! Vector with the indices of the assigned Locations so that the Person always visits the same Home or School etc. */ @@ -518,7 +523,7 @@ class Person uint32_t m_person_id; ///< Id of the Person. std::vector m_cells; ///< Vector with all Cell%s the Person visits at its current Location. Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator - TestingResult m_latest_test_result; + std::vector m_test_results; }; } // namespace abm From 2cf4afcc9a0ed0723718d0049d88a8b004df0180 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:44:58 +0100 Subject: [PATCH 03/37] Add functions for checking a Person require a test in advance --- cpp/models/abm/parameters.h | 4 ++-- cpp/models/abm/person.cpp | 11 ++++++++--- cpp/models/abm/person.h | 9 ++++----- cpp/models/abm/testing_strategy.cpp | 30 ++++++++++++++++++++++++++++- cpp/models/abm/testing_strategy.h | 22 +++++++++++++++++++++ cpp/models/abm/trip_list.cpp | 14 ++++++++++++++ cpp/models/abm/trip_list.h | 8 ++++++++ cpp/models/abm/world.cpp | 21 ++++++++++++++++++-- 8 files changed, 106 insertions(+), 13 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 7c2c264a05..d701dc9f2c 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -331,7 +331,7 @@ struct GenericTest { }; /** - * @brief Reliability of an AntibodyTest. + * @brief Parameters of an AntibodyTest. */ struct AntibodyTest : public GenericTest { using Type = TestParameters; @@ -346,7 +346,7 @@ struct AntibodyTest : public GenericTest { }; /** - * @brief Reliability of an AntigenTest. + * @brief Parameters of an AntigenTest. */ struct AntigenTest : public GenericTest { using Type = TestParameters; diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index c6c8e3e271..bbb1da9adc 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -299,13 +299,18 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const t.days() - latest_protection.second.days()); } -void Person::add_test_result(const TestResult& result) +void Person::add_test_result(TimePoint t, GenericTest type, bool result) { + TestResult result; + result.time_of_testing = t; + result.type = type; + result.result = result; // Remove outdated test results or replace the old result of the same type m_test_results.erase(std::remove_if(m_test_results.begin(), m_test_results.end(), [result](const TestResult& old_result) { return old_result.type.name == result.type.name || - (old_result.time_of_testing + old_result.validity_period) < + (old_result.time_of_testing + + old_result.type.get_default().validity_period) < result.time_of_testing; }), m_test_results.end()); @@ -317,7 +322,7 @@ bool Person::has_valid_test_result(GenericTest type, TimePoint t) const { for (const auto& result : m_test_results) { if (result.type.name == type.name && result.result == true && - (result.time_of_testing + result.validity_period) > t) { + (result.time_of_testing + result.type.get_default().validity_period) > t) { return true; } } diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 9dc63d7541..56b99580a4 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -493,13 +493,12 @@ class Person } struct TestResult { - TimePoint time_of_testing; - GenericTest type; - bool result; - TimeSpan validity_period; + TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. + GenericTest type; ///< The type of the test. + bool result; ///< The result of the test. }; - void add_test_result(const TestResult& result); + void add_test_result(TimePoint t, GenericTest type, bool result); bool has_valid_test_result(GenericTest type, TimePoint t) const; diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 1f82bdadc7..4f429ffc41 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -63,7 +63,7 @@ void TestingCriteria::remove_infection_state(const InfectionState infection_stat bool TestingCriteria::evaluate(const Person& p, TimePoint t) const { - // An empty vector of ages or none bitset of #InfectionStates% means that no condition on the corresponding property is set. + // 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(p.get_age()))) && (m_infection_states.none() || m_infection_states[static_cast(p.get_infection_state(t))]); } @@ -113,6 +113,12 @@ bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& perso return true; } +bool TestingScheme::is_applicable(Person& person, TimePoint trip_time, TimePoint curr_time) const +{ + return m_testing_criteria.evaluate(person, curr_time) && (!person.has_valid_test_result(m_test_type, trip_time)) && + (trip_time - curr_time >= m_test_type.get_default().required_time); +} + TestingStrategy::TestingStrategy( const std::unordered_map>& location_to_schemes_map) : m_location_to_schemes_map(location_to_schemes_map) @@ -166,5 +172,27 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p return true; } +std::vector TestingStrategy::get_applicable_schemes(const Person& person, + const Location& location, TimePoint trip_time, + TimePoint curr_time) const +{ + std::vector applicable_schemes; + + // Combine two vectors of schemes at corresponding location and location stype + std::vector* 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) { + for (const auto& scheme : *vec_ptr) { + if (scheme.is_active() && scheme.is_applicable(person, trip_time, curr_time)) { + applicable_schemes.push_back(&scheme); + } + } + } + + return applicable_schemes; +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 0591e3c931..577cdbbcba 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -142,6 +142,17 @@ class TestingScheme */ bool run_scheme(Person::RandomNumberGenerator& rng, Person& person, TimePoint t) const; + bool is_applicable(Person& person, TimePoint t) const; + + /** + * @brief Get the type of the TestingScheme. + * @return The type of the TestingScheme. + */ + GenericTest get_type() + { + return m_test_type; + } + private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. TimeSpan m_minimal_time_since_last_test; ///< Shortest period of time between two tests. @@ -220,6 +231,17 @@ class TestingStrategy */ bool run_strategy(Person::RandomNumberGenerator& rng, Person& person, const Location& location, TimePoint t); + /** + * @brief Get and returns a list of testing schemes that are applicable to a given person at a specific location and time. + * @param person The Person for whom the testing schemes are to be evaluated. + * @param location The Location where the testing schemes are to be applied. + * @param trip_time The TimePoint at which the trip takes place. + * @param curr_time The current TimePoint. + * @return A vector of pointers to the TestingScheme instances that are applicable. + */ + std::vector get_applicable_schemes(const Person& person, const Location& location, + TimePoint trip_time, TimePoint curr_time) const; + private: std::unordered_map> m_location_to_schemes_map; ///< Set of schemes that are checked for testing. diff --git a/cpp/models/abm/trip_list.cpp b/cpp/models/abm/trip_list.cpp index 88ef819de4..e84c4d387d 100644 --- a/cpp/models/abm/trip_list.cpp +++ b/cpp/models/abm/trip_list.cpp @@ -67,5 +67,19 @@ void TripList::add_trip(Trip trip, bool weekend) } } +std::vector TripList::get_trips_after(TimePoint time, bool weekend) const +{ + std::vector futureTrips; + const auto& trips = weekend ? m_trips_weekend : m_trips_weekday; + + for (const auto& trip : trips) { + if (trip.time > time) { + futureTrips.push_back(&trip); + } + } + + return futureTrips; +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/trip_list.h b/cpp/models/abm/trip_list.h index e4a54ea9b6..4a91549749 100644 --- a/cpp/models/abm/trip_list.h +++ b/cpp/models/abm/trip_list.h @@ -190,6 +190,14 @@ class TripList return m_current_index; } + /** + * @brief Get trips after a given time. + * @param time The time after which to find the trips. + * @param weekend Whether to search in weekend trips or weekday trips. + * @return A vector of pointers to the trips after the given time. + */ + std::vector get_trips_after(TimePoint time, bool weekend) const; + private: std::vector m_trips_weekday; ///< The list of Trip%s a Person makes on a weekday. std::vector m_trips_weekend; ///< The list of Trip%s a Person makes on a weekend day. diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 884453ab3d..841a1ef68b 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -77,10 +77,29 @@ void World::interaction(TimePoint t, TimeSpan dt) void World::migration(TimePoint t, TimeSpan dt) { PRAGMA_OMP(parallel for) + bool weekend = t.is_weekend(); for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + // Check and process future trips for the person + auto future_trips = m_trip_list.get_trips_after(t, weekend); + for (const auto& trip : future_trips) { + auto& target_location = find_location(trip->migration_destination.type, *person); + + // Get applicable testing schemes for the person and future trip location + auto applicable_schemes = m_testing_strategy.get_applicable_schemes(*person, target_location, trip->time, t); + + // Process each applicable scheme + for (const auto* scheme : applicable_schemes) { + if (!scheme->run_scheme(personal_rng, *person, t)) { + // Handle the case where the test fails (e.g., person cannot make the trip) + } + // Optionally, save the test result in the person's record + person.add_test_result(t, scheme->get_type(), true) + } + } + auto try_migration_rule = [&](auto rule) -> bool { //run migration rule and check if migration can actually happen auto target_type = rule(personal_rng, *person, t, dt, parameters); @@ -123,7 +142,6 @@ void World::migration(TimePoint t, TimeSpan dt) } // check if a person makes a trip - bool weekend = t.is_weekend(); size_t num_trips = m_trip_list.num_trips(weekend); if (num_trips != 0) { @@ -241,7 +259,6 @@ const TestingStrategy& World::get_testing_strategy() const void World::update_trip_list(TimePoint t, TimeSpan dt) { - } } // namespace abm From c83b092b5ab9c3eaa822aa4d7b86cfeee16642ad Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:31:00 +0100 Subject: [PATCH 04/37] Fix compilation errors --- cpp/models/abm/person.cpp | 16 ++++++++-------- cpp/models/abm/testing_strategy.cpp | 4 ++-- cpp/models/abm/testing_strategy.h | 20 ++++++++++++-------- cpp/models/abm/world.cpp | 4 ++-- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index bbb1da9adc..7f11aa102a 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -301,21 +301,21 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const void Person::add_test_result(TimePoint t, GenericTest type, bool result) { - TestResult result; - result.time_of_testing = t; - result.type = type; - result.result = result; + TestResult test_result; + test_result.time_of_testing = t; + test_result.type = type; + test_result.result = result; // Remove outdated test results or replace the old result of the same type m_test_results.erase(std::remove_if(m_test_results.begin(), m_test_results.end(), - [result](const TestResult& old_result) { - return old_result.type.name == result.type.name || + [test_result](const TestResult& old_result) { + return old_result.type.name == test_result.type.name || (old_result.time_of_testing + old_result.type.get_default().validity_period) < - result.time_of_testing; + test_result.time_of_testing; }), m_test_results.end()); - m_test_results.push_back(result); + m_test_results.push_back(test_result); } bool Person::has_valid_test_result(GenericTest type, TimePoint t) const diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 4f429ffc41..7be9fa3dad 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -113,7 +113,7 @@ bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& perso return true; } -bool TestingScheme::is_applicable(Person& person, TimePoint trip_time, TimePoint curr_time) const +bool TestingScheme::is_applicable(const Person& person, TimePoint trip_time, TimePoint curr_time) const { return m_testing_criteria.evaluate(person, curr_time) && (!person.has_valid_test_result(m_test_type, trip_time)) && (trip_time - curr_time >= m_test_type.get_default().required_time); @@ -174,7 +174,7 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p std::vector TestingStrategy::get_applicable_schemes(const Person& person, const Location& location, TimePoint trip_time, - TimePoint curr_time) const + TimePoint curr_time) { std::vector applicable_schemes; diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 577cdbbcba..6a0352549b 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -122,7 +122,7 @@ class TestingScheme bool operator==(const TestingScheme& other) const; /** - * @brief Get the activity status of the scheme. + * @brief Gets the activity status of the scheme. * @return Whether the TestingScheme is currently active. */ bool is_active() const; @@ -142,16 +142,20 @@ class TestingScheme */ bool run_scheme(Person::RandomNumberGenerator& rng, Person& person, TimePoint t) const; - bool is_applicable(Person& person, TimePoint t) const; + /** + * @brief Checks if the TestScheme is applicable for the given Person, trip TimePoint and current TimePoint + * @param[in] person Person to check. + * @param[in] trip_time TimePoint when the trip is schedules. + * @param[in] curr_time TimePoint of the current time. + * @return If the TestScheme is applicable for the given Person, trip TimePoint and current TimePoint + */ + bool is_applicable(const Person& person, TimePoint trip_time, TimePoint curr_time) const; /** - * @brief Get the type of the TestingScheme. + * @brief Gets the type of the TestingScheme. * @return The type of the TestingScheme. */ - GenericTest get_type() - { - return m_test_type; - } + GenericTest get_type(); private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. @@ -240,7 +244,7 @@ class TestingStrategy * @return A vector of pointers to the TestingScheme instances that are applicable. */ std::vector get_applicable_schemes(const Person& person, const Location& location, - TimePoint trip_time, TimePoint curr_time) const; + TimePoint trip_time, TimePoint curr_time); private: std::unordered_map> diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 841a1ef68b..0036e9e548 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -96,7 +96,7 @@ void World::migration(TimePoint t, TimeSpan dt) // Handle the case where the test fails (e.g., person cannot make the trip) } // Optionally, save the test result in the person's record - person.add_test_result(t, scheme->get_type(), true) + //person->add_test_result(t, scheme.get_type(), true) } } @@ -257,7 +257,7 @@ const TestingStrategy& World::get_testing_strategy() const return m_testing_strategy; } -void World::update_trip_list(TimePoint t, TimeSpan dt) +void World::update_trip_list(TimePoint /*t*/, TimeSpan /*dt*/) { } From 90530ce7a49f78cfd3946cffcf2180d21b2fd1bf Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:26:06 +0100 Subject: [PATCH 05/37] Add structures for agents to look ahead for tests --- cpp/models/abm/parameters.h | 20 ++++++++ cpp/models/abm/testing_strategy.cpp | 7 ++- cpp/models/abm/testing_strategy.h | 10 +++- cpp/models/abm/trip_list.cpp | 5 +- cpp/models/abm/trip_list.h | 7 +-- cpp/models/abm/world.cpp | 75 ++++++++++++++++++++++------- 6 files changed, 99 insertions(+), 25 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index d701dc9f2c..f3bb2a98e0 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -540,6 +540,21 @@ struct AgeGroupGotoWork { } }; +/** + * @brief The TimeSpan agents look forward to performed tests. + */ +struct LookAheadTime { + using Type = TimeSpan; + static Type get_default(AgeGroup /*size*/) + { + return TimeSpan(days(1)); + } + static std::string name() + { + return "LookAheadTime"; + } +}; + using ParametersBase = ParameterSetget().seconds() < 0.0) { + log_error("Constraint check: Parameter LookAheadTime smaller {:d}", 0); + return true; + } + return false; } diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 7be9fa3dad..16175535cc 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -100,6 +100,11 @@ void TestingScheme::update_activity_status(TimePoint t) m_is_active = (m_start_date <= t && t <= m_end_date); } +bool TestingScheme::is_active_at_time(TimePoint t) const +{ + return m_start_date <= t && t <= m_end_date; +} + 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) { @@ -185,7 +190,7 @@ std::vector TestingStrategy::get_applicable_schemes(const for (auto vec_ptr : schemes_vector) { for (const auto& scheme : *vec_ptr) { - if (scheme.is_active() && scheme.is_applicable(person, trip_time, curr_time)) { + if (scheme.is_active_at_time(trip_time) && scheme.is_applicable(person, trip_time, curr_time)) { applicable_schemes.push_back(&scheme); } } diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 6a0352549b..331bf4e6ae 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -127,6 +127,12 @@ class TestingScheme */ bool is_active() const; + /** + * @brief Gets the activity status of the scheme at a given TimePoint. + * @return Whether the TestingScheme is active at a given TimePoint. + */ + bool is_active_at_time(TimePoint t) const; + /** * @brief Checks if the scheme is active at a given time and updates activity status. * @param[in] t TimePoint to be updated at. @@ -155,7 +161,9 @@ class TestingScheme * @brief Gets the type of the TestingScheme. * @return The type of the TestingScheme. */ - GenericTest get_type(); + GenericTest get_type() const { + return m_test_type; + } private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. diff --git a/cpp/models/abm/trip_list.cpp b/cpp/models/abm/trip_list.cpp index e84c4d387d..94dc1837c8 100644 --- a/cpp/models/abm/trip_list.cpp +++ b/cpp/models/abm/trip_list.cpp @@ -67,13 +67,14 @@ void TripList::add_trip(Trip trip, bool weekend) } } -std::vector TripList::get_trips_after(TimePoint time, bool weekend) const +std::vector TripList::get_trips_between(TimePoint from_time, TimePoint to_time, bool weekend) const { std::vector futureTrips; const auto& trips = weekend ? m_trips_weekend : m_trips_weekday; for (const auto& trip : trips) { - if (trip.time > time) { + if (trip.time.seconds() > from_time.time_since_midnight().seconds() && + trip.time.seconds() < to_time.time_since_midnight().seconds()) { futureTrips.push_back(&trip); } } diff --git a/cpp/models/abm/trip_list.h b/cpp/models/abm/trip_list.h index 4a91549749..24c4832f45 100644 --- a/cpp/models/abm/trip_list.h +++ b/cpp/models/abm/trip_list.h @@ -191,12 +191,13 @@ class TripList } /** - * @brief Get trips after a given time. - * @param time The time after which to find the trips. + * @brief Get trips between two TimePoints. + * @param from_time The time from which to find the trips. + * @param to_time The time to which to find the trips. * @param weekend Whether to search in weekend trips or weekday trips. * @return A vector of pointers to the trips after the given time. */ - std::vector get_trips_after(TimePoint time, bool weekend) const; + std::vector get_trips_between(TimePoint from_time, TimePoint to_time, bool weekend) const; private: std::vector m_trips_weekday; ///< The list of Trip%s a Person makes on a weekday. diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 0036e9e548..39261184b7 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -78,28 +78,11 @@ void World::migration(TimePoint t, TimeSpan dt) { PRAGMA_OMP(parallel for) bool weekend = t.is_weekend(); + for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - // Check and process future trips for the person - auto future_trips = m_trip_list.get_trips_after(t, weekend); - for (const auto& trip : future_trips) { - auto& target_location = find_location(trip->migration_destination.type, *person); - - // Get applicable testing schemes for the person and future trip location - auto applicable_schemes = m_testing_strategy.get_applicable_schemes(*person, target_location, trip->time, t); - - // Process each applicable scheme - for (const auto* scheme : applicable_schemes) { - if (!scheme->run_scheme(personal_rng, *person, t)) { - // Handle the case where the test fails (e.g., person cannot make the trip) - } - // Optionally, save the test result in the person's record - //person->add_test_result(t, scheme.get_type(), true) - } - } - auto try_migration_rule = [&](auto rule) -> bool { //run migration rule and check if migration can actually happen auto target_type = rule(personal_rng, *person, t, dt, parameters); @@ -163,6 +146,62 @@ void World::migration(TimePoint t, TimeSpan dt) if (((t).days() < std::floor((t + dt).days()))) { m_trip_list.reset_index(); } + + // Agents plan for tests + for (auto i = size_t(0); i < m_persons.size(); ++i) { + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + + // Check and process future trips for the person + auto future_trips = m_trip_list.get_trips_between(t, t + parameters.get(), weekend); + for (const auto& trip : future_trips) { + auto& target_location = find_location(trip->migration_destination.type, *person); + + // Get applicable testing schemes for the person and future trip location + auto applicable_schemes = + m_testing_strategy.get_applicable_schemes(*person, target_location, trip->time, t); + + // Process each applicable scheme + for (const auto* scheme : applicable_schemes) { + auto result = scheme->run_scheme(personal_rng, *person, t); + if (!result) { + // Handle the case where the test fails (e.g., person cannot make the trip) + } + // Optionally, save the test result in the person's record + person->add_test_result(t + scheme->get_type().get_default().required_time, scheme->get_type(), result); + } + } + auto testing_migration_rule = [&](auto rule) -> bool { + //run migration rule and check if migration can actually happen + auto target_type = + rule(personal_rng, *person, t + parameters.get(), dt, parameters); + auto& target_location = find_location(target_type, *person); + auto& current_location = person->get_location(); + // Get applicable testing schemes for the person and future trip location + auto applicable_schemes = m_testing_strategy.get_applicable_schemes( + *person, target_location, t + parameters.get(), t); + + if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + if (target_location != current_location && + target_location.get_number_persons() < target_location.get_capacity().persons) { + + return true; + } + } + return false; + }; + //run migration rules one after the other if the corresponding location type exists + //shortcutting of bool operators ensures the rules stop after the first rule is applied + if (m_use_migration_rules) { + (has_locations({LocationType::School, LocationType::Home}) && testing_migration_rule(&go_to_school)) || + (has_locations({LocationType::Work, LocationType::Home}) && testing_migration_rule(&go_to_work)) || + (has_locations({LocationType::BasicsShop, LocationType::Home}) && + testing_migration_rule(&go_to_shop)) || + (has_locations({LocationType::SocialEvent, LocationType::Home}) && + testing_migration_rule(&go_to_event)) || + (has_locations({LocationType::Home}) && testing_migration_rule(&go_to_quarantine)); + } + } } void World::begin_step(TimePoint t, TimeSpan dt) From 2513d4e87837a7a6cdce246f1df5f44955e64c4c Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:18:29 +0100 Subject: [PATCH 06/37] Resolve compilation error --- cpp/models/abm/parameters.h | 4 ++-- cpp/models/abm/world.cpp | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 4ca51d7257..6f9741c3bc 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -541,7 +541,7 @@ struct AgeGroupGotoWork { }; /** - * @brief The TimeSpan agents look forward to performed tests. + * @brief The TimeSpan agents look forward to take tests. */ struct LookAheadTime { using Type = TimeSpan; @@ -562,7 +562,7 @@ using ParametersBase = InfectivityDistributions, DetectInfection, MaskProtection, AerosolTransmissionRates, LockdownDate, SocialEventRate, BasicShoppingRate, WorkRatio, SchoolRatio, GotoWorkTimeMinimum, GotoWorkTimeMaximum, GotoSchoolTimeMinimum, GotoSchoolTimeMaximum, AgeGroupGotoSchool, AgeGroupGotoWork, - InfectionProtectionFactor, SeverityProtectionFactor, HighViralLoadProtectionFactor>; + InfectionProtectionFactor, SeverityProtectionFactor, HighViralLoadProtectionFactor, LookAheadTime>; /** * @brief Maximum number of Person%s an infectious Person can infect at the respective Location. diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index e076735e61..cd7268af72 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -150,25 +150,15 @@ void World::migration(TimePoint t, TimeSpan dt) for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - + std::vector applicable_schemes; // Check and process future trips for the person - auto future_trips = m_trip_list.get_trips_between(t, t + parameters.get(), weekend); + auto future_trips = + m_trip_list.get_trips_between(t, t + dt + parameters.get(), weekend); for (const auto& trip : future_trips) { auto& target_location = find_location(trip->migration_destination.type, *person); // Get applicable testing schemes for the person and future trip location - auto applicable_schemes = - m_testing_strategy.get_applicable_schemes(*person, target_location, trip->time, t); - - // Process each applicable scheme - for (const auto* scheme : applicable_schemes) { - auto result = scheme->run_scheme(personal_rng, *person, t); - if (!result) { - // Handle the case where the test fails (e.g., person cannot make the trip) - } - // Optionally, save the test result in the person's record - person->add_test_result(t + scheme->get_type().get_default().required_time, scheme->get_type(), result); - } + applicable_schemes = m_testing_strategy.get_applicable_schemes(*person, target_location, trip->time, t); } auto testing_migration_rule = [&](auto rule) -> bool { //run migration rule and check if migration can actually happen @@ -200,6 +190,16 @@ void World::migration(TimePoint t, TimeSpan dt) testing_migration_rule(&go_to_event)) || (has_locations({LocationType::Home}) && testing_migration_rule(&go_to_quarantine)); } + + // Process each applicable scheme + for (const auto* scheme : applicable_schemes) { + auto result = scheme->run_scheme(personal_rng, *person, t); + if (!result) { + // Handle the case where the test fails (e.g., person cannot make the trip) + } + // Optionally, save the test result in the person's record + person->add_test_result(t + scheme->get_type().get_default().required_time, scheme->get_type(), result); + } } } From 0d387e5e86354f4cc9002ceea204a3591ded83d3 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:29:01 +0100 Subject: [PATCH 07/37] Resolve compilation error --- cpp/models/abm/infection.h | 2 +- cpp/models/abm/parameters.h | 15 --------------- cpp/models/abm/world.cpp | 6 +----- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/cpp/models/abm/infection.h b/cpp/models/abm/infection.h index 00f0ca5ed2..f882d22af2 100644 --- a/cpp/models/abm/infection.h +++ b/cpp/models/abm/infection.h @@ -20,11 +20,11 @@ #ifndef EPI_ABM_INFECTION_H #define EPI_ABM_INFECTION_H -#include "abm/person.h" #include "abm/time.h" #include "abm/infection_state.h" #include "abm/virus_variant.h" #include "abm/parameters.h" +#include "abm/person.h" #include diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 6f9741c3bc..15cf0081cf 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -330,21 +330,6 @@ struct GenericTest { } }; -/** - * @brief Parameters of an AntibodyTest. - */ -struct AntibodyTest : public GenericTest { - using Type = TestParameters; - static Type get_default() - { - return Type{0.8, 0.88, minutes(30), hours(24)}; - } - static std::string name() - { - return "AntigenTest"; - } -}; - /** * @brief Parameters of an AntigenTest. */ diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index cd7268af72..f8dc2a2e79 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -167,7 +167,7 @@ void World::migration(TimePoint t, TimeSpan dt) auto& target_location = find_location(target_type, *person); auto& current_location = person->get_location(); // Get applicable testing schemes for the person and future trip location - auto applicable_schemes = m_testing_strategy.get_applicable_schemes( + applicable_schemes = m_testing_strategy.get_applicable_schemes( *person, target_location, t + parameters.get(), t); if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { @@ -294,9 +294,5 @@ const TestingStrategy& World::get_testing_strategy() const return m_testing_strategy; } -void World::update_trip_list(TimePoint /*t*/, TimeSpan /*dt*/) -{ -} - } // namespace abm } // namespace mio From 39813d630de0ce3758d0dd2df003907e202d99dc Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:43:29 +0100 Subject: [PATCH 08/37] Simplify the testing in advance function --- cpp/models/abm/testing_strategy.cpp | 37 ++--------------------- cpp/models/abm/testing_strategy.h | 11 ------- cpp/models/abm/world.cpp | 47 ++++++----------------------- 3 files changed, 13 insertions(+), 82 deletions(-) diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 16175535cc..a07e70bd7d 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -100,11 +100,6 @@ void TestingScheme::update_activity_status(TimePoint t) m_is_active = (m_start_date <= t && t <= m_end_date); } -bool TestingScheme::is_active_at_time(TimePoint t) const -{ - return m_start_date <= t && t <= m_end_date; -} - 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) { @@ -118,12 +113,6 @@ bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& perso return true; } -bool TestingScheme::is_applicable(const Person& person, TimePoint trip_time, TimePoint curr_time) const -{ - return m_testing_criteria.evaluate(person, curr_time) && (!person.has_valid_test_result(m_test_type, trip_time)) && - (trip_time - curr_time >= m_test_type.get_default().required_time); -} - TestingStrategy::TestingStrategy( const std::unordered_map>& location_to_schemes_map) : m_location_to_schemes_map(location_to_schemes_map) @@ -169,7 +158,9 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p 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); + auto result = !ts.is_active() || ts.run_scheme(rng, person, t); + person.add_test_result(t + ts.get_type().get_default().required_time, ts.get_type(), result); + return result; })) { return false; } @@ -177,27 +168,5 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p return true; } -std::vector TestingStrategy::get_applicable_schemes(const Person& person, - const Location& location, TimePoint trip_time, - TimePoint curr_time) -{ - std::vector applicable_schemes; - - // Combine two vectors of schemes at corresponding location and location stype - std::vector* 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) { - for (const auto& scheme : *vec_ptr) { - if (scheme.is_active_at_time(trip_time) && scheme.is_applicable(person, trip_time, curr_time)) { - applicable_schemes.push_back(&scheme); - } - } - } - - return applicable_schemes; -} - } // namespace abm } // namespace mio diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 331bf4e6ae..1ed799a217 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -243,17 +243,6 @@ class TestingStrategy */ bool run_strategy(Person::RandomNumberGenerator& rng, Person& person, const Location& location, TimePoint t); - /** - * @brief Get and returns a list of testing schemes that are applicable to a given person at a specific location and time. - * @param person The Person for whom the testing schemes are to be evaluated. - * @param location The Location where the testing schemes are to be applied. - * @param trip_time The TimePoint at which the trip takes place. - * @param curr_time The current TimePoint. - * @return A vector of pointers to the TestingScheme instances that are applicable. - */ - std::vector get_applicable_schemes(const Person& person, const Location& location, - TimePoint trip_time, TimePoint curr_time); - private: std::unordered_map> m_location_to_schemes_map; ///< Set of schemes that are checked for testing. diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index f8dc2a2e79..a0053bb9d7 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -146,7 +146,7 @@ void World::migration(TimePoint t, TimeSpan dt) m_trip_list.reset_index(); } - // Agents plan for tests + // Agents do tests in advance if applicable for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); @@ -156,50 +156,23 @@ void World::migration(TimePoint t, TimeSpan dt) m_trip_list.get_trips_between(t, t + dt + parameters.get(), weekend); for (const auto& trip : future_trips) { auto& target_location = find_location(trip->migration_destination.type, *person); - - // Get applicable testing schemes for the person and future trip location - applicable_schemes = m_testing_strategy.get_applicable_schemes(*person, target_location, trip->time, t); + m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); } - auto testing_migration_rule = [&](auto rule) -> bool { + auto testing_migration_rule = [&](auto rule) -> void { //run migration rule and check if migration can actually happen auto target_type = rule(personal_rng, *person, t + parameters.get(), dt, parameters); - auto& target_location = find_location(target_type, *person); - auto& current_location = person->get_location(); + auto& target_location = find_location(target_type, *person); // Get applicable testing schemes for the person and future trip location - applicable_schemes = m_testing_strategy.get_applicable_schemes( - *person, target_location, t + parameters.get(), t); - - if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { - if (target_location != current_location && - target_location.get_number_persons() < target_location.get_capacity().persons) { - - return true; - } - } - return false; + m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); }; //run migration rules one after the other if the corresponding location type exists //shortcutting of bool operators ensures the rules stop after the first rule is applied - if (m_use_migration_rules) { - (has_locations({LocationType::School, LocationType::Home}) && testing_migration_rule(&go_to_school)) || - (has_locations({LocationType::Work, LocationType::Home}) && testing_migration_rule(&go_to_work)) || - (has_locations({LocationType::BasicsShop, LocationType::Home}) && - testing_migration_rule(&go_to_shop)) || - (has_locations({LocationType::SocialEvent, LocationType::Home}) && - testing_migration_rule(&go_to_event)) || - (has_locations({LocationType::Home}) && testing_migration_rule(&go_to_quarantine)); - } - - // Process each applicable scheme - for (const auto* scheme : applicable_schemes) { - auto result = scheme->run_scheme(personal_rng, *person, t); - if (!result) { - // Handle the case where the test fails (e.g., person cannot make the trip) - } - // Optionally, save the test result in the person's record - person->add_test_result(t + scheme->get_type().get_default().required_time, scheme->get_type(), result); - } + testing_migration_rule(&go_to_school); + testing_migration_rule(&go_to_work); + testing_migration_rule(&go_to_shop); + testing_migration_rule(&go_to_event); + testing_migration_rule(&go_to_quarantine); } } From 0509292ab17340369d61d89ac735756ae475b14b Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 14 Dec 2023 18:27:43 +0100 Subject: [PATCH 09/37] Test for getting test result in Person --- cpp/models/abm/person.cpp | 9 ++++----- cpp/models/abm/person.h | 2 +- cpp/models/abm/testing_strategy.cpp | 6 ++++++ cpp/models/abm/world.cpp | 12 +++++++----- cpp/tests/test_abm_person.cpp | 16 ++++++++++++++++ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 5a7e6501ad..1b82cafc1e 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -320,15 +320,14 @@ void Person::add_test_result(TimePoint t, GenericTest type, bool result) m_test_results.push_back(test_result); } -bool Person::has_valid_test_result(GenericTest type, TimePoint t) const +const Person::TestResult* Person::get_test_result(GenericTest type, TimePoint t) const { for (const auto& result : m_test_results) { - if (result.type.name == type.name && result.result == true && - (result.time_of_testing + result.type.get_default().validity_period) > t) { - return true; + if (result.type.name == type.name && (result.time_of_testing + result.type.get_default().validity_period) > t) { + return &result; } } - return false; + return nullptr; } } // namespace abm diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index f28894687b..d0e910c84f 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -522,7 +522,7 @@ class Person void add_test_result(TimePoint t, GenericTest type, bool result); - bool has_valid_test_result(GenericTest type, TimePoint t) const; + const Person::TestResult* get_test_result(GenericTest type, TimePoint t) const; private: observer_ptr m_location; ///< Current Location of the Person. diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index a07e70bd7d..36360e12c9 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -158,6 +158,12 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p for (auto vec_ptr : schemes_vector) { if (!std::all_of(vec_ptr->begin(), vec_ptr->end(), [&rng, &person, t](TestingScheme& ts) { + auto test_result = person.get_test_result(ts.get_type(), t); + // If the agent has a test result valid until now, use the result directly + if (test_result != nullptr) { + return test_result->result; + } + // If not, perform the test and save result auto result = !ts.is_active() || ts.run_scheme(rng, person, t); person.add_test_result(t + ts.get_type().get_default().required_time, ts.get_type(), result); return result; diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index a0053bb9d7..ce1fc6f994 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -168,11 +168,13 @@ void World::migration(TimePoint t, TimeSpan dt) }; //run migration rules one after the other if the corresponding location type exists //shortcutting of bool operators ensures the rules stop after the first rule is applied - testing_migration_rule(&go_to_school); - testing_migration_rule(&go_to_work); - testing_migration_rule(&go_to_shop); - testing_migration_rule(&go_to_event); - testing_migration_rule(&go_to_quarantine); + if (m_use_migration_rules) { + testing_migration_rule(&go_to_school); + testing_migration_rule(&go_to_work); + testing_migration_rule(&go_to_shop); + testing_migration_rule(&go_to_event); + testing_migration_rule(&go_to_quarantine); + } } } diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index fd14098de8..b4268d5e7c 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -325,3 +325,19 @@ TEST(Person, rng) ASSERT_EQ(p.get_rng_counter(), mio::Counter(1)); ASSERT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{1})); } + +TEST(Person, getTestResult) +{ + mio::abm::Location location(mio::abm::LocationType::School, 0, NUM_AGE_GROUPS); + auto person = make_test_person(location); + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::seconds(8640); //0.1 days + + mio::abm::GenericTest test; + person.add_test_result(t, test, true); + ASSERT_TRUE(person.get_test_result(test, t)); + ASSERT_TRUE(person.get_test_result(test, t + dt)); + + dt = mio::abm::days(2); + ASSERT_FALSE(person.get_test_result(test, t + dt)); +} From 562dca4f20d4a30a89a0d638a44fd91feff1ad16 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:59:26 +0100 Subject: [PATCH 10/37] Add test to run TestStrategy for future Trips and Migration rules --- cpp/models/abm/trip_list.cpp | 4 +- cpp/models/abm/world.cpp | 42 ++++++----- cpp/tests/test_abm_world.cpp | 132 ++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 22 deletions(-) diff --git a/cpp/models/abm/trip_list.cpp b/cpp/models/abm/trip_list.cpp index 94dc1837c8..2ded95b013 100644 --- a/cpp/models/abm/trip_list.cpp +++ b/cpp/models/abm/trip_list.cpp @@ -73,8 +73,8 @@ std::vector TripList::get_trips_between(TimePoint from_time, TimePo const auto& trips = weekend ? m_trips_weekend : m_trips_weekday; for (const auto& trip : trips) { - if (trip.time.seconds() > from_time.time_since_midnight().seconds() && - trip.time.seconds() < to_time.time_since_midnight().seconds()) { + if (trip.time.seconds() >= from_time.time_since_midnight().seconds() && + trip.time.seconds() <= to_time.time_since_midnight().seconds()) { futureTrips.push_back(&trip); } } diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 6141db9a8a..e1a90df93e 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -129,8 +129,8 @@ void World::migration(TimePoint t, TimeSpan dt) if (num_trips != 0) { while (m_trip_list.get_current_index() < num_trips && m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds()) { - auto& trip = m_trip_list.get_next_trip(weekend); - auto& person = m_persons[trip.person_id]; + auto& trip = m_trip_list.get_next_trip(weekend); + auto& person = m_persons[trip.person_id]; auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); if (!person->is_in_quarantine() && person->get_infection_state(t) != InfectionState::Dead) { auto& target_location = get_individualized_location(trip.migration_destination); @@ -147,35 +147,39 @@ void World::migration(TimePoint t, TimeSpan dt) } // Agents do tests in advance if applicable + // Perform tests for future migration rules of person for (auto i = size_t(0); i < m_persons.size(); ++i) { - auto&& person = m_persons[i]; - auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - std::vector applicable_schemes; - // Check and process future trips for the person - auto future_trips = - m_trip_list.get_trips_between(t, t + dt + parameters.get(), weekend); - for (const auto& trip : future_trips) { - auto& target_location = find_location(trip->migration_destination.type, *person); - m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); - } - auto testing_migration_rule = [&](auto rule) -> void { + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + auto testing_migration_rule = [&](auto rule) -> bool { //run migration rule and check if migration can actually happen auto target_type = rule(personal_rng, *person, t + parameters.get(), dt, parameters); auto& target_location = find_location(target_type, *person); // Get applicable testing schemes for the person and future trip location - m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); + return m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); }; //run migration rules one after the other if the corresponding location type exists //shortcutting of bool operators ensures the rules stop after the first rule is applied if (m_use_migration_rules) { - testing_migration_rule(&go_to_school); - testing_migration_rule(&go_to_work); - testing_migration_rule(&go_to_shop); - testing_migration_rule(&go_to_event); - testing_migration_rule(&go_to_quarantine); + (has_locations({LocationType::School, LocationType::Home}) && testing_migration_rule(&go_to_school)) || + (has_locations({LocationType::Work, LocationType::Home}) && testing_migration_rule(&go_to_work)) || + (has_locations({LocationType::BasicsShop, LocationType::Home}) && + testing_migration_rule(&go_to_shop)) || + (has_locations({LocationType::SocialEvent, LocationType::Home}) && + testing_migration_rule(&go_to_event)) || + (has_locations({LocationType::Home}) && testing_migration_rule(&go_to_quarantine)); } } + // Perform tests for future trips + auto future_trips = + m_trip_list.get_trips_between(t + dt, t + dt + parameters.get(), weekend); + for (const auto& trip : future_trips) { + auto& person = m_persons[trip->person_id]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + auto& target_location = find_location(trip->migration_destination.type, *person); + m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); + } } void World::begin_step(TimePoint t, TimeSpan dt) diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 4275202b86..eac5955e48 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -432,7 +432,7 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) const auto start_date = mio::abm::TimePoint(20); const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); const auto probability = 1.0; - const auto test_type = mio::abm::PCRTest(); + const auto test_type = mio::abm::AntigenTest(); auto testing_scheme = mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); @@ -447,6 +447,7 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) .Times(testing::AtLeast(2)) .WillOnce(testing::Return(0.7)) .WillOnce(testing::Return(0.4)); + current_time = mio::abm::TimePoint(60 * 60 * 24 * 2); ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), false); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, @@ -719,3 +720,132 @@ TEST(TestWorld, copyWorld) ASSERT_NE(copied_world.get_persons()[1].get_location().get_type(), world.get_persons()[1].get_location().get_type()); } + +TEST(TestWorld, runTestStrategyForFutureTrips) +{ + using testing::Return; + auto t = mio::abm::TimePoint(0); + auto world = mio::abm::World(num_age_groups); + auto rng = mio::RandomNumberGenerator(); + world.use_migration_rules(false); + + //setup so the person does not do transition + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto event_id = world.add_location(mio::abm::LocationType::SocialEvent); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto& event = world.get_individualized_location(event_id); + auto& work = world.get_individualized_location(work_id); + auto& home = world.get_individualized_location(home_id); + + auto testing_criteria = mio::abm::TestingCriteria(); + testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); + testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); + const auto testing_frequency = mio::abm::days(0); + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 1.0; + const auto test_type = mio::abm::AntigenTest(); + auto testing_scheme = + mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); + world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); + world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); + + ScopedMockDistribution>>> + mock_exponential_dist; + EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no infections + + auto& person = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + person.set_assigned_location(event_id); + person.set_assigned_location(work_id); + person.set_assigned_location(home_id); + auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); + person.add_test_result(mio::abm::TimePoint(0), test_type, true); + mio::abm::TripList& data = world.get_trip_list(); + mio::abm::Trip trip1(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); + mio::abm::Trip trip2(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(18), event_id, work_id); + mio::abm::Trip trip3(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(20), home_id, event_id); + data.add_trip(trip1); + data.add_trip(trip2); + data.add_trip(trip3); + + data.use_weekday_trips_on_weekend(); + + // Test if the person advance with the scheduled trips + // as the person already has test result, which is valid for the day. + EXPECT_EQ(person.get_location(), home); + world.evolve(mio::abm::TimePoint(0), mio::abm::hours(10)); + EXPECT_EQ(person.get_location(), work); + person.migrate_to(home); + world.evolve(mio::abm::TimePoint(0), mio::abm::hours(20)); + EXPECT_EQ(person.get_location(), event); + person.migrate_to(home); + world.evolve(mio::abm::TimePoint(0), mio::abm::hours(22)); + EXPECT_EQ(person.get_location(), home); + + // Test if the trip on the next day could not be performed due to a new test result. + person.migrate_to(home); + world.evolve(mio::abm::TimePoint(60 * 60 * 24), mio::abm::hours(10)); + EXPECT_EQ(person.get_location(), home); +} + +TEST(TestWorld, runTestStrategyForFutureMigrationRule) +{ + using testing::Return; + auto t = mio::abm::TimePoint(0); + auto world = mio::abm::World(num_age_groups); + auto rng = mio::RandomNumberGenerator(); + + //setup so the person does not do transition + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto event_id = world.add_location(mio::abm::LocationType::SocialEvent); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto& work = world.get_individualized_location(work_id); + auto& home = world.get_individualized_location(home_id); + + auto& person = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + person.set_assigned_location(event_id); + person.set_assigned_location(work_id); + person.set_assigned_location(home_id); + auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); + + auto testing_criteria = mio::abm::TestingCriteria(); + testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); + testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); + const auto testing_frequency = mio::abm::days(0); + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 1.0; + const auto test_type = mio::abm::AntigenTest(); + auto testing_scheme = + mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); + world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); + world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); + person.add_test_result(mio::abm::TimePoint(0), test_type, true); + + ScopedMockDistribution>>> + mock_exponential_dist; + EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no infections + + // Test if the person advance with the scheduled trips + // as the person already has test result, which is valid for the day. + EXPECT_EQ(person.get_location(), home); + world.evolve(mio::abm::TimePoint(0), mio::abm::hours(10)); + EXPECT_EQ(person.get_location(), work); + // Test if the trip on the next day could not be performed due to a new test result. + world.evolve(mio::abm::TimePoint(60 * 60 * 24), mio::abm::hours(10)); + EXPECT_EQ(person.get_location(), home); +} \ No newline at end of file From 267767cf1d5eead7d9a42d856aa6ba3ad03efb93 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 4 Jan 2024 19:43:31 +0100 Subject: [PATCH 11/37] Remove unused variables in the tests running TestStrategy for future Trips and Migration rules --- cpp/tests/test_abm_world.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index eac5955e48..ae89ce22d2 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -726,7 +726,6 @@ TEST(TestWorld, runTestStrategyForFutureTrips) using testing::Return; auto t = mio::abm::TimePoint(0); auto world = mio::abm::World(num_age_groups); - auto rng = mio::RandomNumberGenerator(); world.use_migration_rules(false); //setup so the person does not do transition @@ -765,8 +764,8 @@ TEST(TestWorld, runTestStrategyForFutureTrips) person.set_assigned_location(event_id); person.set_assigned_location(work_id); person.set_assigned_location(home_id); - auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); person.add_test_result(mio::abm::TimePoint(0), test_type, true); + mio::abm::TripList& data = world.get_trip_list(); mio::abm::Trip trip1(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); mio::abm::Trip trip2(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(18), event_id, work_id); @@ -797,10 +796,8 @@ TEST(TestWorld, runTestStrategyForFutureTrips) TEST(TestWorld, runTestStrategyForFutureMigrationRule) { - using testing::Return; auto t = mio::abm::TimePoint(0); auto world = mio::abm::World(num_age_groups); - auto rng = mio::RandomNumberGenerator(); //setup so the person does not do transition world.parameters @@ -820,7 +817,6 @@ TEST(TestWorld, runTestStrategyForFutureMigrationRule) person.set_assigned_location(event_id); person.set_assigned_location(work_id); person.set_assigned_location(home_id); - auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); auto testing_criteria = mio::abm::TestingCriteria(); testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); @@ -836,10 +832,6 @@ TEST(TestWorld, runTestStrategyForFutureMigrationRule) world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); person.add_test_result(mio::abm::TimePoint(0), test_type, true); - ScopedMockDistribution>>> - mock_exponential_dist; - EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no infections - // Test if the person advance with the scheduled trips // as the person already has test result, which is valid for the day. EXPECT_EQ(person.get_location(), home); From 609e4f90f90db146535e2e3aee3bc91ac2bab6cf Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 4 Jan 2024 19:43:36 +0100 Subject: [PATCH 12/37] Remove unused variables in the tests running TestStrategy for future Trips and Migration rules --- cpp/tests/test_abm_world.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index ae89ce22d2..0efb0c24d8 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -723,7 +723,6 @@ TEST(TestWorld, copyWorld) TEST(TestWorld, runTestStrategyForFutureTrips) { - using testing::Return; auto t = mio::abm::TimePoint(0); auto world = mio::abm::World(num_age_groups); world.use_migration_rules(false); @@ -756,10 +755,6 @@ TEST(TestWorld, runTestStrategyForFutureTrips) world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); - ScopedMockDistribution>>> - mock_exponential_dist; - EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no infections - auto& person = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); person.set_assigned_location(event_id); person.set_assigned_location(work_id); From e266ad9d73c7fd690b77c1630517d6374f113427 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Fri, 5 Jan 2024 19:18:43 +0100 Subject: [PATCH 13/37] Add migration plan for Person class --- cpp/models/abm/person.cpp | 19 +++++ cpp/models/abm/person.h | 34 +++++++- cpp/models/abm/testing_strategy.cpp | 1 - cpp/models/abm/trip_list.cpp | 2 +- cpp/models/abm/world.cpp | 66 ++++++---------- cpp/models/abm/world.h | 8 +- cpp/tests/test_abm_person.cpp | 19 ++++- cpp/tests/test_abm_world.cpp | 116 ---------------------------- 8 files changed, 101 insertions(+), 164 deletions(-) diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 1b82cafc1e..cad7e6f788 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -44,6 +44,8 @@ Person::Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age , m_person_id(person_id) , m_cells{0} , m_last_transport_mode(TransportMode::Unknown) + , m_test_results() + , m_migration_planning() { m_random_workgroup = UniformDistribution::get_instance()(rng); m_random_schoolgroup = UniformDistribution::get_instance()(rng); @@ -330,5 +332,22 @@ const Person::TestResult* Person::get_test_result(GenericTest type, TimePoint t) return nullptr; } +void Person::add_migration_plan(TimePoint t, Location& location) +{ + std::pair new_plan = {t, location}; + m_migration_planning.push_back(new_plan); +} + +std::vector> Person::get_migration_plan(TimePoint from_time, TimePoint to_time) +{ + std::vector> result; + for (auto plan : m_migration_planning) { + if (plan.first >= from_time && plan.first < to_time) { + result.push_back(plan); + } + } + return result; +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index d0e910c84f..196f0a271e 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -516,14 +516,40 @@ class Person struct TestResult { TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. - GenericTest type; ///< The type of the test. + GenericTest type; ///< The type of the test. bool result; ///< The result of the test. }; + /** + * @brief Add TestResult to the Person + * @param[in] GenericTest The TestType of the result. + * @param[in] type The TimePoint of the result. + * @param[in] result The result of the test. + */ void add_test_result(TimePoint t, GenericTest type, bool result); + /** + * @brief Get the TestResult performed at a TimePoint of the Person based on the TestType. + * @param[in] type The TestType of the result. + * @param[in] t The TimePoint of the result. + */ const Person::TestResult* get_test_result(GenericTest type, TimePoint t) const; + /** + * @brief Get the latest #Infection or #Vaccination and its initial TimePoint of the Person. + * @param[in] t TimePoint of the migration plan. + * @param[in] location Location of the destination of the migration plan. + */ + void add_migration_plan(TimePoint t, Location& location); + + /** + * @brief Get all the migration plans between a certain time interval. + * @param[in] from_time TimePoint of the migration plan. + * @param[in] to_time TimePoint of the migration plan. + * @return The vector of all the migration plans between mentioned time period. + */ + std::vector> get_migration_plan(TimePoint from_time, TimePoint to_time); + private: observer_ptr m_location; ///< Current Location of the Person. std::vector m_assigned_locations; /**! Vector with the indices of the assigned Locations so that the @@ -544,8 +570,10 @@ class Person uint32_t m_person_id; ///< Id of the Person. std::vector m_cells; ///< Vector with all Cell%s the Person visits at its current Location. mio::abm::TransportMode m_last_transport_mode; ///< TransportMode the Person used to get to its current Location. - Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator - std::vector m_test_results; + Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator. + std::vector m_test_results; ///< Vector to store all TestResults. + std::vector> + m_migration_planning; ///< Vector to store all migration plans (pairs of TimePoint and Location). }; } // namespace abm diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index f7ba62128c..0d02378671 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -172,7 +172,6 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p if (location.get_type() == mio::abm::LocationType::Home && person.is_in_quarantine()) { return true; } - //lookup schemes for this specific location as well as the location type //lookup in std::vector instead of std::map should be much faster unless for large numbers of schemes for (auto loc_key : {LocationId{location.get_index(), location.get_type()}, diff --git a/cpp/models/abm/trip_list.cpp b/cpp/models/abm/trip_list.cpp index 2ded95b013..dafe23de7e 100644 --- a/cpp/models/abm/trip_list.cpp +++ b/cpp/models/abm/trip_list.cpp @@ -71,7 +71,7 @@ std::vector TripList::get_trips_between(TimePoint from_time, TimePo { std::vector futureTrips; const auto& trips = weekend ? m_trips_weekend : m_trips_weekday; - + for (const auto& trip : trips) { if (trip.time.seconds() >= from_time.time_since_midnight().seconds() && trip.time.seconds() <= to_time.time_since_midnight().seconds()) { diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index e1a90df93e..9fe83af3ab 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -60,6 +60,13 @@ void World::evolve(TimePoint t, TimeSpan dt) log_info("ABM World interaction."); interaction(t, dt); log_info("ABM World migration."); + // Pass though dt time until the LookAheadTime and make planning for the agents + auto time_counter = t; + while (time_counter.seconds() <= (t + parameters.get()).seconds()) { + planning(time_counter, dt); + time_counter += dt; + } + // Execute the plan for the agents migration(t, dt); } @@ -73,15 +80,14 @@ void World::interaction(TimePoint t, TimeSpan dt) } } -void World::migration(TimePoint t, TimeSpan dt) +void World::planning(TimePoint t, TimeSpan dt) { PRAGMA_OMP(parallel for) bool weekend = t.is_weekend(); for (auto i = size_t(0); i < m_persons.size(); ++i) { - auto&& person = m_persons[i]; - auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); auto try_migration_rule = [&](auto rule) -> bool { //run migration rule and check if migration can actually happen auto target_type = rule(personal_rng, *person, t, dt, parameters); @@ -92,9 +98,9 @@ void World::migration(TimePoint t, TimeSpan dt) target_location.get_number_persons() < target_location.get_capacity().persons) { bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); if (wears_mask) { - person->migrate_to(target_location); + person->add_migration_plan(t, target_location); + return true; } - return true; } } return false; @@ -129,14 +135,13 @@ void World::migration(TimePoint t, TimeSpan dt) if (num_trips != 0) { while (m_trip_list.get_current_index() < num_trips && m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds()) { - auto& trip = m_trip_list.get_next_trip(weekend); - auto& person = m_persons[trip.person_id]; + auto& trip = m_trip_list.get_next_trip(weekend); + auto& person = m_persons[trip.person_id]; auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); if (!person->is_in_quarantine() && person->get_infection_state(t) != InfectionState::Dead) { auto& target_location = get_individualized_location(trip.migration_destination); if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { - person->apply_mask_intervention(personal_rng, target_location); - person->migrate_to(target_location, trip.trip_mode); + person->add_migration_plan(t, target_location); } } m_trip_list.increase_index(); @@ -145,41 +150,20 @@ void World::migration(TimePoint t, TimeSpan dt) if (((t).days() < std::floor((t + dt).days()))) { m_trip_list.reset_index(); } +} - // Agents do tests in advance if applicable - // Perform tests for future migration rules of person +void World::migration(TimePoint t, TimeSpan dt) +{ + PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { - auto&& person = m_persons[i]; - auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - auto testing_migration_rule = [&](auto rule) -> bool { - //run migration rule and check if migration can actually happen - auto target_type = - rule(personal_rng, *person, t + parameters.get(), dt, parameters); - auto& target_location = find_location(target_type, *person); - // Get applicable testing schemes for the person and future trip location - return m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); - }; - //run migration rules one after the other if the corresponding location type exists - //shortcutting of bool operators ensures the rules stop after the first rule is applied - if (m_use_migration_rules) { - (has_locations({LocationType::School, LocationType::Home}) && testing_migration_rule(&go_to_school)) || - (has_locations({LocationType::Work, LocationType::Home}) && testing_migration_rule(&go_to_work)) || - (has_locations({LocationType::BasicsShop, LocationType::Home}) && - testing_migration_rule(&go_to_shop)) || - (has_locations({LocationType::SocialEvent, LocationType::Home}) && - testing_migration_rule(&go_to_event)) || - (has_locations({LocationType::Home}) && testing_migration_rule(&go_to_quarantine)); + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + auto migration_planning = person->get_migration_plan(t, t + dt); + for (auto plan : migration_planning) { + person->apply_mask_intervention(personal_rng, plan.second); + person->migrate_to(plan.second); } } - // Perform tests for future trips - auto future_trips = - m_trip_list.get_trips_between(t + dt, t + dt + parameters.get(), weekend); - for (const auto& trip : future_trips) { - auto& person = m_persons[trip->person_id]; - auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - auto& target_location = find_location(trip->migration_destination.type, *person); - m_testing_strategy.run_strategy(personal_rng, *person, target_location, t + dt); - } } void World::begin_step(TimePoint t, TimeSpan dt) diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 29c62f8588..9c0b8e6bcf 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -318,7 +318,13 @@ class World */ void interaction(TimePoint t, TimeSpan dt); /** - * @brief Person%s move in the World according to rules. + * @brief Person%s plan to move in the World according to rules. + * @param[in] t The current TimePoint. + * @param[in] dt The length of the time step of the Simulation. + */ + void planning(TimePoint t, TimeSpan dt); + /** + * @brief Person%s move in the World according to planning. * @param[in] t The current TimePoint. * @param[in] dt The length of the time step of the Simulation. */ diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index dea6db6403..f73fd2d6d2 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -326,7 +326,7 @@ TEST(Person, rng) ASSERT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{1})); } -TEST(Person, getTestResult) +TEST(Person, addAndGetTestResult) { mio::abm::Location location(mio::abm::LocationType::School, 0, num_age_groups); auto person = make_test_person(location); @@ -341,3 +341,20 @@ TEST(Person, getTestResult) dt = mio::abm::days(2); ASSERT_FALSE(person.get_test_result(test, t + dt)); } + +TEST(Person, addAndGetMigrationPlanning) +{ + mio::abm::Location location(mio::abm::LocationType::School, 0, num_age_groups); + auto person = make_test_person(location); + auto t0 = mio::abm::TimePoint(0); + auto t1 = mio::abm::TimePoint(60 * 60); + auto t2 = mio::abm::TimePoint(60 * 60 * 2); + + person.add_migration_plan(t0, location); + person.add_migration_plan(t1, location); + person.add_migration_plan(t2, location); + auto migration_plan = person.get_migration_plan(t0, t0 + mio::abm::hours(2)); + ASSERT_EQ(migration_plan.size(), 2); + ASSERT_EQ(migration_plan[0].first, t0); + ASSERT_EQ(migration_plan[1].first, t1); +} diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 0efb0c24d8..27604361a3 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -720,119 +720,3 @@ TEST(TestWorld, copyWorld) ASSERT_NE(copied_world.get_persons()[1].get_location().get_type(), world.get_persons()[1].get_location().get_type()); } - -TEST(TestWorld, runTestStrategyForFutureTrips) -{ - auto t = mio::abm::TimePoint(0); - auto world = mio::abm::World(num_age_groups); - world.use_migration_rules(false); - - //setup so the person does not do transition - world.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - world.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - - auto home_id = world.add_location(mio::abm::LocationType::Home); - auto event_id = world.add_location(mio::abm::LocationType::SocialEvent); - auto work_id = world.add_location(mio::abm::LocationType::Work); - auto& event = world.get_individualized_location(event_id); - auto& work = world.get_individualized_location(work_id); - auto& home = world.get_individualized_location(home_id); - - auto testing_criteria = mio::abm::TestingCriteria(); - testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); - testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); - const auto testing_frequency = mio::abm::days(0); - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 1.0; - const auto test_type = mio::abm::AntigenTest(); - auto testing_scheme = - mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); - world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); - world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); - - auto& person = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); - person.set_assigned_location(event_id); - person.set_assigned_location(work_id); - person.set_assigned_location(home_id); - person.add_test_result(mio::abm::TimePoint(0), test_type, true); - - mio::abm::TripList& data = world.get_trip_list(); - mio::abm::Trip trip1(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); - mio::abm::Trip trip2(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(18), event_id, work_id); - mio::abm::Trip trip3(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(20), home_id, event_id); - data.add_trip(trip1); - data.add_trip(trip2); - data.add_trip(trip3); - - data.use_weekday_trips_on_weekend(); - - // Test if the person advance with the scheduled trips - // as the person already has test result, which is valid for the day. - EXPECT_EQ(person.get_location(), home); - world.evolve(mio::abm::TimePoint(0), mio::abm::hours(10)); - EXPECT_EQ(person.get_location(), work); - person.migrate_to(home); - world.evolve(mio::abm::TimePoint(0), mio::abm::hours(20)); - EXPECT_EQ(person.get_location(), event); - person.migrate_to(home); - world.evolve(mio::abm::TimePoint(0), mio::abm::hours(22)); - EXPECT_EQ(person.get_location(), home); - - // Test if the trip on the next day could not be performed due to a new test result. - person.migrate_to(home); - world.evolve(mio::abm::TimePoint(60 * 60 * 24), mio::abm::hours(10)); - EXPECT_EQ(person.get_location(), home); -} - -TEST(TestWorld, runTestStrategyForFutureMigrationRule) -{ - auto t = mio::abm::TimePoint(0); - auto world = mio::abm::World(num_age_groups); - - //setup so the person does not do transition - world.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - world.parameters - .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2; - - auto home_id = world.add_location(mio::abm::LocationType::Home); - auto event_id = world.add_location(mio::abm::LocationType::SocialEvent); - auto work_id = world.add_location(mio::abm::LocationType::Work); - auto& work = world.get_individualized_location(work_id); - auto& home = world.get_individualized_location(home_id); - - auto& person = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); - person.set_assigned_location(event_id); - person.set_assigned_location(work_id); - person.set_assigned_location(home_id); - - auto testing_criteria = mio::abm::TestingCriteria(); - testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); - testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); - const auto testing_frequency = mio::abm::days(0); - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 1.0; - const auto test_type = mio::abm::AntigenTest(); - auto testing_scheme = - mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); - world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); - world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); - person.add_test_result(mio::abm::TimePoint(0), test_type, true); - - // Test if the person advance with the scheduled trips - // as the person already has test result, which is valid for the day. - EXPECT_EQ(person.get_location(), home); - world.evolve(mio::abm::TimePoint(0), mio::abm::hours(10)); - EXPECT_EQ(person.get_location(), work); - // Test if the trip on the next day could not be performed due to a new test result. - world.evolve(mio::abm::TimePoint(60 * 60 * 24), mio::abm::hours(10)); - EXPECT_EQ(person.get_location(), home); -} \ No newline at end of file From 0d4d789e794f24843f2b90adaa38053396d43eae Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:50:05 +0100 Subject: [PATCH 14/37] Add check to see if the Person plan ahead of time --- cpp/models/abm/parameters.h | 2 +- cpp/models/abm/world.cpp | 19 +++++++---- cpp/models/abm/world.h | 3 +- cpp/tests/test_abm_world.cpp | 63 ++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index af1ef3dcba..0ce7bda51c 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -537,7 +537,7 @@ struct LookAheadTime { using Type = TimeSpan; static Type get_default(AgeGroup /*size*/) { - return TimeSpan(days(1)); + return TimeSpan(seconds(0)); } static std::string name() { diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 9fe83af3ab..4dc279ab54 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -60,10 +60,11 @@ void World::evolve(TimePoint t, TimeSpan dt) log_info("ABM World interaction."); interaction(t, dt); log_info("ABM World migration."); - // Pass though dt time until the LookAheadTime and make planning for the agents + // Pass though dt time until the LookAheadTime and make planning for the agents. auto time_counter = t; + std::unordered_map personId_to_loc_map; while (time_counter.seconds() <= (t + parameters.get()).seconds()) { - planning(time_counter, dt); + planning(time_counter, dt, personId_to_loc_map); time_counter += dt; } // Execute the plan for the agents @@ -80,7 +81,7 @@ void World::interaction(TimePoint t, TimeSpan dt) } } -void World::planning(TimePoint t, TimeSpan dt) +void World::planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map) { PRAGMA_OMP(parallel for) bool weekend = t.is_weekend(); @@ -90,15 +91,19 @@ void World::planning(TimePoint t, TimeSpan dt) auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); auto try_migration_rule = [&](auto rule) -> bool { //run migration rule and check if migration can actually happen - auto target_type = rule(personal_rng, *person, t, dt, parameters); - auto& target_location = find_location(target_type, *person); - auto& current_location = person->get_location(); + auto target_type = rule(personal_rng, *person, t, dt, parameters); + auto& target_location = find_location(target_type, *person); + Location* current_location = &(person->get_location()); + if (personId_to_loc_map.find(person->get_person_id()) != personId_to_loc_map.end()) { + current_location = personId_to_loc_map[person->get_person_id()]; + } if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { - if (target_location != current_location && + if (target_location != *current_location && target_location.get_number_persons() < target_location.get_capacity().persons) { bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); if (wears_mask) { person->add_migration_plan(t, target_location); + personId_to_loc_map[person->get_person_id()] = &target_location; return true; } } diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 9c0b8e6bcf..8ffeb73ab2 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -321,8 +321,9 @@ class World * @brief Person%s plan to move in the World according to rules. * @param[in] t The current TimePoint. * @param[in] dt The length of the time step of the Simulation. + * @param[in] personId_to_loc_map A map used to track Person's location for planning. */ - void planning(TimePoint t, TimeSpan dt); + void planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map); /** * @brief Person%s move in the World according to planning. * @param[in] t The current TimePoint. diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 27604361a3..25b56a0aed 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -720,3 +720,66 @@ TEST(TestWorld, copyWorld) ASSERT_NE(copied_world.get_persons()[1].get_location().get_type(), world.get_persons()[1].get_location().get_type()); } + +TEST(TestWorld, personPlanning) +{ + using testing::Return; + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto world = mio::abm::World(num_age_groups); + //setup so the person doesn't do transition + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + world.parameters + .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = + 2 * dt.days(); + //setup so the person look 2 hours ahead. + world.parameters.get() = mio::abm::hours(2); + + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto school_id = world.add_location(mio::abm::LocationType::School); + auto work_id = world.add_location(mio::abm::LocationType::Work); + + ScopedMockDistribution>>> mock_uniform_dist; + EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) + .Times(testing::AtLeast(8)) + .WillOnce(testing::Return(0.8)) // draw random work group + .WillOnce(testing::Return(0.8)) // draw random school group + .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random school hour + .WillOnce(testing::Return(0.8)) // draw random work group + .WillOnce(testing::Return(0.8)) // draw random school group + .WillOnce(testing::Return(0.8)) // draw random work hour + .WillOnce(testing::Return(0.8)) // draw random school hour + .WillRepeatedly(testing::Return(1.0)); + + auto& p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); + auto& p1 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + + p1.set_assigned_location(school_id); + p2.set_assigned_location(school_id); + p1.set_assigned_location(work_id); + p2.set_assigned_location(work_id); + p1.set_assigned_location(home_id); + p2.set_assigned_location(home_id); + + auto& school = world.get_individualized_location(school_id); + auto& work = world.get_individualized_location(work_id); + auto& home = world.get_individualized_location(home_id); + + world.evolve(t, dt); + // Check whether the 2 people migrate correctly. + EXPECT_EQ(p1.get_location(), work); + EXPECT_EQ(p2.get_location(), school); + EXPECT_EQ(school.get_number_persons(), 1); + EXPECT_EQ(work.get_number_persons(), 1); + + // Check whether the 2 people plan to go home after. + auto p1_migration_plan = p1.get_migration_plan(t, t + mio::abm::hours(3)); + auto p2_migration_plan = p2.get_migration_plan(t, t + mio::abm::hours(3)); + EXPECT_EQ(p1_migration_plan.size(), 2); + EXPECT_EQ(p2_migration_plan.size(), 2); + EXPECT_EQ(p1_migration_plan[1].second, home); + EXPECT_EQ(p2_migration_plan[1].second, home); +} \ No newline at end of file From 69419cfd4d07c20e86b6634ff1ec914090d4fc6f Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:28:16 +0100 Subject: [PATCH 15/37] Add check for parameter LookAheadTime --- cpp/models/abm/analyze_result.h | 4 ++++ cpp/models/abm/trip_list.cpp | 15 --------------- cpp/tests/test_abm_world.cpp | 11 ++++++++--- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/cpp/models/abm/analyze_result.h b/cpp/models/abm/analyze_result.h index 0e93be84a9..e82012d44b 100644 --- a/cpp/models/abm/analyze_result.h +++ b/cpp/models/abm/analyze_result.h @@ -195,6 +195,10 @@ std::vector ensemble_params_percentile(const std::vector().days(); return result; }); + param_percentil(node, [](auto&& model) -> auto& { + static auto result = model.parameters.template get().days(); + return result; + }); } return percentile; diff --git a/cpp/models/abm/trip_list.cpp b/cpp/models/abm/trip_list.cpp index dafe23de7e..88ef819de4 100644 --- a/cpp/models/abm/trip_list.cpp +++ b/cpp/models/abm/trip_list.cpp @@ -67,20 +67,5 @@ void TripList::add_trip(Trip trip, bool weekend) } } -std::vector TripList::get_trips_between(TimePoint from_time, TimePoint to_time, bool weekend) const -{ - std::vector futureTrips; - const auto& trips = weekend ? m_trips_weekend : m_trips_weekday; - - for (const auto& trip : trips) { - if (trip.time.seconds() >= from_time.time_since_midnight().seconds() && - trip.time.seconds() <= to_time.time_since_midnight().seconds()) { - futureTrips.push_back(&trip); - } - } - - return futureTrips; -} - } // namespace abm } // namespace mio diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 25b56a0aed..a289c91ac4 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -482,6 +482,7 @@ TEST(TestWorld, checkParameterConstraints) params.get()[mio::abm::MaskType::FFP2] = 0.6; params.get()[mio::abm::MaskType::Surgical] = 0.7; params.get() = mio::abm::TimePoint(0); + params.get() = mio::abm::hours(1); ASSERT_EQ(params.check_constraints(), false); params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -1.; @@ -543,6 +544,10 @@ TEST(TestWorld, checkParameterConstraints) params.get() = mio::abm::TimePoint(-2); ASSERT_EQ(params.check_constraints(), true); + params.get() = mio::abm::TimePoint(2); + + params.get() = mio::abm::hours(-1); + ASSERT_EQ(params.check_constraints(), true); } TEST(TestWorld, copyWorld) @@ -734,7 +739,7 @@ TEST(TestWorld, personPlanning) world.parameters .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2 * dt.days(); - //setup so the person look 2 hours ahead. + //setup so the person look 2 hours ahead. world.parameters.get() = mio::abm::hours(2); auto home_id = world.add_location(mio::abm::LocationType::Home); @@ -766,10 +771,10 @@ TEST(TestWorld, personPlanning) auto& school = world.get_individualized_location(school_id); auto& work = world.get_individualized_location(work_id); - auto& home = world.get_individualized_location(home_id); + auto& home = world.get_individualized_location(home_id); world.evolve(t, dt); - // Check whether the 2 people migrate correctly. + // Check whether the 2 people migrate correctly. EXPECT_EQ(p1.get_location(), work); EXPECT_EQ(p2.get_location(), school); EXPECT_EQ(school.get_number_persons(), 1); From 4a29d4d091b8f3ebd63e4e69053705d2f552576b Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:40:02 +0100 Subject: [PATCH 16/37] Change Person's m_test_results from vector to CustomIndexArray --- cpp/models/abm/parameters.h | 23 +++++++++++++++++----- cpp/models/abm/person.cpp | 26 ++++++------------------- cpp/models/abm/person.h | 30 +++++++++++++++-------------- cpp/models/abm/testing_strategy.cpp | 10 ++++++---- cpp/models/abm/testing_strategy.h | 3 ++- cpp/tests/test_abm_person.cpp | 10 ++-------- 6 files changed, 50 insertions(+), 52 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 0ce7bda51c..3a9b7da403 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -308,6 +308,18 @@ struct HighViralLoadProtectionFactor { } }; +/** + * @brief The index for all the testing types. + * Can be used as 0-based index. + */ +enum class TestingTypeIndex : std::uint32_t +{ + GenericTest = 0, + AntigenTest = 1, + PCRTest = 2, + Count //last!! +}; + /** * @brief Parameters that describe the reliability of a test. */ @@ -316,13 +328,14 @@ struct TestParameters { UncertainValue specificity; TimeSpan required_time; TimeSpan validity_period; + TestingTypeIndex test_type; }; struct GenericTest { using Type = TestParameters; static Type get_default() { - return Type{0.9, 0.99, hours(24), hours(24)}; + return Type{0.9, 0.99, hours(24), hours(24), TestingTypeIndex::GenericTest}; } static std::string name() { @@ -337,7 +350,7 @@ struct AntigenTest : public GenericTest { using Type = TestParameters; static Type get_default() { - return Type{0.8, 0.88, minutes(30), hours(24)}; + return Type{0.8, 0.88, minutes(30), hours(24), TestingTypeIndex::AntigenTest}; } static std::string name() { @@ -352,7 +365,7 @@ struct PCRTest : public GenericTest { using Type = TestParameters; static Type get_default() { - return Type{0.9, 0.99, days(1), days(3)}; + return Type{0.9, 0.99, days(1), days(3), TestingTypeIndex::PCRTest}; } static std::string name() { @@ -502,7 +515,7 @@ struct AgeGroupGotoSchool { using Type = CustomIndexArray; static Type get_default(AgeGroup num_agegroups) { - auto a = Type(num_agegroups, false); + auto a = Type(num_agegroups, false); a[AgeGroup(1)] = true; return a; } @@ -519,7 +532,7 @@ struct AgeGroupGotoWork { using Type = CustomIndexArray; static Type get_default(AgeGroup num_agegroups) { - auto a = Type(num_agegroups, false); + auto a = Type(num_agegroups, false); a[AgeGroup(2)] = true; a[AgeGroup(3)] = true; return a; diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index cad7e6f788..8f70eaf6c5 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -44,7 +44,7 @@ Person::Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age , m_person_id(person_id) , m_cells{0} , m_last_transport_mode(TransportMode::Unknown) - , m_test_results() + , m_test_results({TestingTypeIndex::Count}, TestResult()) , m_migration_planning() { m_random_workgroup = UniformDistribution::get_instance()(rng); @@ -303,33 +303,19 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const t.days() - latest_protection.second.days()); } -void Person::add_test_result(TimePoint t, GenericTest type, bool result) +void Person::add_test_result(TimePoint t, TestingTypeIndex type_index, bool result) { TestResult test_result; test_result.time_of_testing = t; - test_result.type = type; + test_result.type = type_index; test_result.result = result; // Remove outdated test results or replace the old result of the same type - m_test_results.erase(std::remove_if(m_test_results.begin(), m_test_results.end(), - [test_result](const TestResult& old_result) { - return old_result.type.name == test_result.type.name || - (old_result.time_of_testing + - old_result.type.get_default().validity_period) < - test_result.time_of_testing; - }), - m_test_results.end()); - - m_test_results.push_back(test_result); + m_test_results[{type_index}] = test_result; } -const Person::TestResult* Person::get_test_result(GenericTest type, TimePoint t) const +const Person::TestResult Person::get_test_result(TestingTypeIndex type_index) const { - for (const auto& result : m_test_results) { - if (result.type.name == type.name && (result.time_of_testing + result.type.get_default().validity_period) > t) { - return &result; - } - } - return nullptr; + return m_test_results[{type_index}]; } void Person::add_migration_plan(TimePoint t, Location& location) diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 196f0a271e..b8cc408bdb 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -145,6 +145,15 @@ class Person return (m_person_id == other.m_person_id); } + /** + * @brief The TestResult of a Person. + */ + struct TestResult { + TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. + TestingTypeIndex type = TestingTypeIndex::GenericTest; ///< The TestingTypeIndex of the test. + bool result; ///< The result of the test. + }; + /** * @brief Time passes and the Person interacts with the population at its current Location. * The Person might become infected. @@ -514,26 +523,19 @@ class Person loc, age, id); } - struct TestResult { - TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. - GenericTest type; ///< The type of the test. - bool result; ///< The result of the test. - }; - /** * @brief Add TestResult to the Person - * @param[in] GenericTest The TestType of the result. - * @param[in] type The TimePoint of the result. + * @param[in] t The TimePoint of the result. + * @param[in] type The TestingTypeIndex of the result. * @param[in] result The result of the test. */ - void add_test_result(TimePoint t, GenericTest type, bool result); + void add_test_result(TimePoint t, TestingTypeIndex type, bool result); /** - * @brief Get the TestResult performed at a TimePoint of the Person based on the TestType. - * @param[in] type The TestType of the result. - * @param[in] t The TimePoint of the result. + * @brief Get the most recent TestResult performed from the Person based on the TestType. + * @param[in] type The TestingTypeIndex of the result. */ - const Person::TestResult* get_test_result(GenericTest type, TimePoint t) const; + const TestResult get_test_result(TestingTypeIndex type) const; /** * @brief Get the latest #Infection or #Vaccination and its initial TimePoint of the Person. @@ -571,7 +573,7 @@ class Person std::vector m_cells; ///< Vector with all Cell%s the Person visits at its current Location. mio::abm::TransportMode m_last_transport_mode; ///< TransportMode the Person used to get to its current Location. Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator. - std::vector m_test_results; ///< Vector to store all TestResults. + CustomIndexArray m_test_results; ///< CustomIndexArray for TestResults. std::vector> m_migration_planning; ///< Vector to store all migration plans (pairs of TimePoint and Location). }; diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 0d02378671..a833d9effd 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -184,14 +184,16 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p //apply all testing schemes that are found auto& schemes = iter_schemes->second; if (!std::all_of(schemes.begin(), schemes.end(), [&rng, &person, t](TestingScheme& ts) { - auto test_result = person.get_test_result(ts.get_type(), t); + auto test_result = person.get_test_result(ts.get_type().get_default().test_type); // If the agent has a test result valid until now, use the result directly - if (test_result != nullptr) { - return test_result->result; + if ((test_result.type != TestingTypeIndex::Count) && + (test_result.time_of_testing + ts.get_type().get_default().validity_period < t)) { + return test_result.result; } // If not, perform the test and save result auto result = !ts.is_active() || ts.run_scheme(rng, person, t); - person.add_test_result(t + ts.get_type().get_default().required_time, ts.get_type(), result); + person.add_test_result(t + ts.get_type().get_default().required_time, + ts.get_type().get_default().test_type, result); return result; })) { return false; diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 4ab2ebfae4..a42a9ceba4 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -162,7 +162,8 @@ class TestingScheme * @brief Gets the type of the TestingScheme. * @return The type of the TestingScheme. */ - GenericTest get_type() const { + GenericTest get_type() const + { return m_test_type; } diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index f73fd2d6d2..e728de1fe3 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -331,15 +331,9 @@ TEST(Person, addAndGetTestResult) mio::abm::Location location(mio::abm::LocationType::School, 0, num_age_groups); auto person = make_test_person(location); auto t = mio::abm::TimePoint(0); - auto dt = mio::abm::seconds(8640); //0.1 days - mio::abm::GenericTest test; - person.add_test_result(t, test, true); - ASSERT_TRUE(person.get_test_result(test, t)); - ASSERT_TRUE(person.get_test_result(test, t + dt)); - - dt = mio::abm::days(2); - ASSERT_FALSE(person.get_test_result(test, t + dt)); + person.add_test_result(t, mio::abm::TestingTypeIndex::GenericTest, true); + ASSERT_TRUE(person.get_test_result(mio::abm::TestingTypeIndex::GenericTest).result); } TEST(Person, addAndGetMigrationPlanning) From 7458989aa96d3c235486ca3fb7b61d2ce78155e1 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Fri, 19 Jan 2024 19:07:41 +0100 Subject: [PATCH 17/37] Fix error in test abm migration and person --- cpp/models/abm/parameters.h | 3 ++- cpp/tests/test_abm_migration_rules.cpp | 33 +++++++++++++------------- cpp/tests/test_abm_person.cpp | 7 +++--- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index ef0101bc44..43bd244666 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -580,7 +580,8 @@ using ParametersBase = InfectivityDistributions, DetectInfection, MaskProtection, AerosolTransmissionRates, LockdownDate, QuarantineDuration, SocialEventRate, BasicShoppingRate, WorkRatio, SchoolRatio, GotoWorkTimeMinimum, GotoWorkTimeMaximum, GotoSchoolTimeMinimum, GotoSchoolTimeMaximum, AgeGroupGotoSchool, - AgeGroupGotoWork, InfectionProtectionFactor, SeverityProtectionFactor, HighViralLoadProtectionFactor, LookAheadTime>; + AgeGroupGotoWork, InfectionProtectionFactor, SeverityProtectionFactor, HighViralLoadProtectionFactor, + LookAheadTime>; /** * @brief Maximum number of Person%s an infectious Person can infect at the respective Location. diff --git a/cpp/tests/test_abm_migration_rules.cpp b/cpp/tests/test_abm_migration_rules.cpp index 474e12d205..62f7a8685a 100644 --- a/cpp/tests/test_abm_migration_rules.cpp +++ b/cpp/tests/test_abm_migration_rules.cpp @@ -44,10 +44,10 @@ TEST(TestMigrationRules, student_goes_to_school) auto child_rng = mio::abm::Person::RandomNumberGenerator(rng, p_child); auto adult_rng = mio::abm::Person::RandomNumberGenerator(rng, p_child); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - params.get() = false; + params.get() = false; params.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 or 35-59) - params.get() = false; + params.get() = false; params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; @@ -88,10 +88,10 @@ TEST(TestMigrationRules, students_go_to_school_in_different_times) mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - params.get() = false; + params.get() = false; params.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 or 35-59) - params.get() = false; + params.get() = false; params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; @@ -148,10 +148,10 @@ TEST(TestMigrationRules, students_go_to_school_in_different_times_with_smaller_t auto dt = mio::abm::seconds(1800); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - params.get() = false; + params.get() = false; params.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 or 35-59) - params.get() = false; + params.get() = false; params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; @@ -212,10 +212,10 @@ TEST(TestMigrationRules, worker_goes_to_work) mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - params.get() = false; + params.get() = false; params.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 or 35-59) - params.get() = false; + params.get() = false; params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; @@ -252,10 +252,10 @@ TEST(TestMigrationRules, worker_goes_to_work_with_non_dividable_timespan) mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - params.get() = false; + params.get() = false; params.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 or 35-59) - params.get() = false; + params.get() = false; params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; @@ -293,10 +293,10 @@ TEST(TestMigrationRules, workers_go_to_work_in_different_times) auto dt = mio::abm::hours(1); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - params.get() = false; + params.get() = false; params.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 or 35-59) - params.get() = false; + params.get() = false; params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; @@ -328,10 +328,11 @@ TEST(TestMigrationRules, work_return) TEST(TestMigrationRules, quarantine) { - auto rng = mio::RandomNumberGenerator(); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); - auto test_params = mio::abm::TestParameters{1.0,1.0}; + auto rng = mio::RandomNumberGenerator(); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); + auto test_params = mio::abm::TestParameters{1.0, 1.0, mio::abm::minutes(30), mio::abm::hours(24), + mio::abm::TestingTypeIndex::GenericTest}; mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 4ce3e3a0cf..57951d0490 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -102,8 +102,9 @@ TEST(TestPerson, setGetAssignedLocation) TEST(TestPerson, quarantine) { using testing::Return; - auto rng = mio::RandomNumberGenerator(); - auto test_params = mio::abm::TestParameters{1.01,1.01}; //100% safe test + auto rng = mio::RandomNumberGenerator(); + auto test_params = mio::abm::TestParameters{1.01, 1.01, mio::abm::minutes(30), mio::abm::hours(24), + mio::abm::TestingTypeIndex::GenericTest}; //100% safe test auto infection_parameters = mio::abm::Parameters(num_age_groups); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); @@ -128,7 +129,7 @@ TEST(TestPerson, quarantine) auto person = make_test_person(home, age_group_35_to_59, mio::abm::InfectionState::InfectedSymptoms, t_morning, infection_parameters); auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); - + person.get_tested(rng_person, t_morning, test_params); ASSERT_EQ(person.get_infection_state(t_morning), mio::abm::InfectionState::InfectedSymptoms); From 09bbacfb112a66559fc02a9648e203ebaa20cdff Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:32:52 +0100 Subject: [PATCH 18/37] Fix an error in run_strategy() --- cpp/models/abm/person.h | 2 +- cpp/models/abm/testing_strategy.cpp | 5 ++++- cpp/tests/test_abm_testing_strategy.cpp | 15 ++++++++------- cpp/tests/test_abm_world.cpp | 14 ++++++++------ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 0440e42894..d56215de02 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -150,7 +150,7 @@ class Person */ struct TestResult { TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. - TestingTypeIndex type = TestingTypeIndex::GenericTest; ///< The TestingTypeIndex of the test. + TestingTypeIndex type = TestingTypeIndex::Count; ///< The TestingTypeIndex of the test. bool result; ///< The result of the test. }; diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 33e51c0247..85ea4e1e16 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -185,9 +185,12 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p auto& schemes = iter_schemes->second; if (!std::all_of(schemes.begin(), schemes.end(), [&rng, &person, t](TestingScheme& ts) { auto test_result = person.get_test_result(ts.get_type().get_default().test_type); + std::cout << "type: " << (test_result.type != TestingTypeIndex::Count) << "\n"; + std::cout << "test time: " << test_result.time_of_testing.hours() << "\n"; + std::cout << "valid time: " << ts.get_type().get_default().validity_period.hours() << "\n"; // If the agent has a test result valid until now, use the result directly if ((test_result.type != TestingTypeIndex::Count) && - (test_result.time_of_testing + ts.get_type().get_default().validity_period < t)) { + (test_result.time_of_testing + ts.get_type().get_default().validity_period >= t)) { return test_result.result; } // If not, perform the test and save result diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 7442701625..60c6b2a40a 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -110,12 +110,12 @@ TEST(TestTestingScheme, runScheme) TEST(TestTestingScheme, initAndRunTestingStrategy) { - auto rng = mio::RandomNumberGenerator(); - const auto testing_min_time = mio::abm::days(1); - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 0.8; - const auto test_type = mio::abm::PCRTest(); + auto rng = mio::RandomNumberGenerator(); + const auto testing_min_time = mio::abm::days(1); + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 0.8; + const auto test_type = mio::abm::PCRTest(); std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; @@ -148,5 +148,6 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) false); // Person tests and tests positive ASSERT_EQ(test_strategy.run_strategy(rng_person2, person2, loc_work, start_date), true); // Person tests and tests negative - ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date), true); // Person doesn't test + ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date), + false); // Person doesn't test } diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index a289c91ac4..3e56943eec 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -428,11 +428,12 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); - const auto testing_frequency = mio::abm::days(1); - const auto start_date = mio::abm::TimePoint(20); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 1.0; - const auto test_type = mio::abm::AntigenTest(); + const auto testing_frequency = mio::abm::days(1); + const auto start_date = mio::abm::TimePoint(20); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 1.0; + const auto test_type = mio::abm::AntigenTest(); + test_type.get_default().validity_period = mio::abm::seconds(1); auto testing_scheme = mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_type, probability); @@ -448,7 +449,8 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) .WillOnce(testing::Return(0.7)) .WillOnce(testing::Return(0.4)); current_time = mio::abm::TimePoint(60 * 60 * 24 * 2); - ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), false); + ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time + mio::abm::hours(1)), + false); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); //doesn't get added because of == operator From 941df85d783d6ad3e070b523515751741e447701 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:37:07 +0100 Subject: [PATCH 19/37] Remove some debugging cout from run_strategy() --- cpp/models/abm/testing_strategy.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 85ea4e1e16..a74b048585 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -185,9 +185,6 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p auto& schemes = iter_schemes->second; if (!std::all_of(schemes.begin(), schemes.end(), [&rng, &person, t](TestingScheme& ts) { auto test_result = person.get_test_result(ts.get_type().get_default().test_type); - std::cout << "type: " << (test_result.type != TestingTypeIndex::Count) << "\n"; - std::cout << "test time: " << test_result.time_of_testing.hours() << "\n"; - std::cout << "valid time: " << ts.get_type().get_default().validity_period.hours() << "\n"; // If the agent has a test result valid until now, use the result directly if ((test_result.type != TestingTypeIndex::Count) && (test_result.time_of_testing + ts.get_type().get_default().validity_period >= t)) { From 44f9388ebddcba731607b28bc47cb0252413db29 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Sun, 11 Feb 2024 17:18:41 +0100 Subject: [PATCH 20/37] Add structure for Person to remember its lastest planning time --- cpp/models/abm/analyze_result.h | 2 +- cpp/models/abm/parameters.h | 12 ++--- cpp/models/abm/person.cpp | 2 + cpp/models/abm/person.h | 9 ++++ cpp/models/abm/world.cpp | 91 +++++++++++++++++---------------- cpp/tests/test_abm_world.cpp | 6 +-- 6 files changed, 69 insertions(+), 53 deletions(-) diff --git a/cpp/models/abm/analyze_result.h b/cpp/models/abm/analyze_result.h index e82012d44b..32e3a0832e 100644 --- a/cpp/models/abm/analyze_result.h +++ b/cpp/models/abm/analyze_result.h @@ -196,7 +196,7 @@ std::vector ensemble_params_percentile(const std::vector auto& { - static auto result = model.parameters.template get().days(); + static auto result = model.parameters.template get().days(); return result; }); } diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 43bd244666..d4a235fa3c 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -559,9 +559,9 @@ struct AgeGroupGotoWork { }; /** - * @brief The TimeSpan agents look forward to take tests. + * @brief The TimeSpan agents plan forward to take tests. */ -struct LookAheadTime { +struct PlanAheadTime { using Type = TimeSpan; static Type get_default(AgeGroup /*size*/) { @@ -569,7 +569,7 @@ struct LookAheadTime { } static std::string name() { - return "LookAheadTime"; + return "PlanAheadTime"; } }; @@ -581,7 +581,7 @@ using ParametersBase = QuarantineDuration, SocialEventRate, BasicShoppingRate, WorkRatio, SchoolRatio, GotoWorkTimeMinimum, GotoWorkTimeMaximum, GotoSchoolTimeMinimum, GotoSchoolTimeMaximum, AgeGroupGotoSchool, AgeGroupGotoWork, InfectionProtectionFactor, SeverityProtectionFactor, HighViralLoadProtectionFactor, - LookAheadTime>; + PlanAheadTime>; /** * @brief Maximum number of Person%s an infectious Person can infect at the respective Location. @@ -778,8 +778,8 @@ class Parameters : public ParametersBase return true; } - if (this->get().seconds() < 0.0) { - log_error("Constraint check: Parameter LookAheadTime smaller {:d}", 0); + if (this->get().seconds() < 0.0) { + log_error("Constraint check: Parameter PlanAheadTime smaller {:d}", 0); return true; } diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 7858c1def3..70de0bb368 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -47,6 +47,7 @@ Person::Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age , m_last_transport_mode(TransportMode::Unknown) , m_test_results({TestingTypeIndex::Count}, TestResult()) , m_migration_planning() + , m_planned_time(TimePoint(-1)) { m_random_workgroup = UniformDistribution::get_instance()(rng); m_random_schoolgroup = UniformDistribution::get_instance()(rng); @@ -313,6 +314,7 @@ void Person::add_migration_plan(TimePoint t, Location& location) { std::pair new_plan = {t, location}; m_migration_planning.push_back(new_plan); + m_planned_time = t; } std::vector> Person::get_migration_plan(TimePoint from_time, TimePoint to_time) diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index d56215de02..b3bfc8c52b 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -548,6 +548,14 @@ class Person */ std::vector> get_migration_plan(TimePoint from_time, TimePoint to_time); + /** + * @brief Get the agent's lastest TimePoint of its planning. + * @return The agent's lastest TimePoint of its planning. + */ + TimePoint get_planned_time() { + return m_planned_time; + } + private: observer_ptr m_location; ///< Current Location of the Person. std::vector m_assigned_locations; /**! Vector with the indices of the assigned Locations so that the @@ -572,6 +580,7 @@ class Person CustomIndexArray m_test_results; ///< CustomIndexArray for TestResults. std::vector> m_migration_planning; ///< Vector to store all migration plans (pairs of TimePoint and Location). + TimePoint m_planned_time; ///< TimePoint of the last recored planning. }; } // namespace abm diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 3e6275729f..570d0f1776 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -60,10 +60,10 @@ void World::evolve(TimePoint t, TimeSpan dt) log_info("ABM World interaction."); interaction(t, dt); log_info("ABM World migration."); - // Pass though dt time until the LookAheadTime and make planning for the agents. + // Pass though dt time until the PlanAheadTime and make planning for the agents. auto time_counter = t; std::unordered_map personId_to_loc_map; - while (time_counter.seconds() <= (t + parameters.get()).seconds()) { + while (time_counter.seconds() <= (t + parameters.get()).seconds()) { planning(time_counter, dt, personId_to_loc_map); time_counter += dt; } @@ -87,50 +87,55 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_map bool { - //run migration rule and check if migration can actually happen - auto target_type = rule(personal_rng, *person, t, dt, parameters); - auto& target_location = find_location(target_type, *person); - Location* current_location = &(person->get_location()); - if (personId_to_loc_map.find(person->get_person_id()) != personId_to_loc_map.end()) { - current_location = personId_to_loc_map[person->get_person_id()]; - } - if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { - if (target_location != *current_location && - target_location.get_number_persons() < target_location.get_capacity().persons) { - bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); - if (wears_mask) { - person->add_migration_plan(t, target_location); - personId_to_loc_map[person->get_person_id()] = &target_location; - return true; + auto&& person = m_persons[i]; + if ((person->get_planned_time() < t) || + (person->get_infection_state(t) != person->get_infection_state(t - dt))) { + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + auto try_migration_rule = [&](auto rule) -> bool { + //run migration rule and check if migration can actually happen + auto target_type = rule(personal_rng, *person, t, dt, parameters); + auto& target_location = find_location(target_type, *person); + Location* current_location = &(person->get_location()); + if (personId_to_loc_map.find(person->get_person_id()) != personId_to_loc_map.end()) { + current_location = personId_to_loc_map[person->get_person_id()]; + } + if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + if (target_location != *current_location && + target_location.get_number_persons() < target_location.get_capacity().persons) { + bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); + if (wears_mask) { + person->add_migration_plan(t, target_location); + personId_to_loc_map[person->get_person_id()] = &target_location; + return true; + } } } - } - return false; - }; + return false; + }; - //run migration rules one after the other if the corresponding location type exists - //shortcutting of bool operators ensures the rules stop after the first rule is applied - if (m_use_migration_rules) { - (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || - (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || - (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || - (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || - (has_locations({LocationType::School, LocationType::Home}) && try_migration_rule(&go_to_school)) || - (has_locations({LocationType::Work, LocationType::Home}) && try_migration_rule(&go_to_work)) || - (has_locations({LocationType::BasicsShop, LocationType::Home}) && try_migration_rule(&go_to_shop)) || - (has_locations({LocationType::SocialEvent, LocationType::Home}) && try_migration_rule(&go_to_event)) || - (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); - } - else { - //no daily routine migration, just infection related - (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || - (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || - (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || - (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || - (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); + //run migration rules one after the other if the corresponding location type exists + //shortcutting of bool operators ensures the rules stop after the first rule is applied + if (m_use_migration_rules) { + (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || + (has_locations({LocationType::School, LocationType::Home}) && try_migration_rule(&go_to_school)) || + (has_locations({LocationType::Work, LocationType::Home}) && try_migration_rule(&go_to_work)) || + (has_locations({LocationType::BasicsShop, LocationType::Home}) && + try_migration_rule(&go_to_shop)) || + (has_locations({LocationType::SocialEvent, LocationType::Home}) && + try_migration_rule(&go_to_event)) || + (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); + } + else { + //no daily routine migration, just infection related + (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || + (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); + } } } diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 3e56943eec..5701b12cdb 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -484,7 +484,7 @@ TEST(TestWorld, checkParameterConstraints) params.get()[mio::abm::MaskType::FFP2] = 0.6; params.get()[mio::abm::MaskType::Surgical] = 0.7; params.get() = mio::abm::TimePoint(0); - params.get() = mio::abm::hours(1); + params.get() = mio::abm::hours(1); ASSERT_EQ(params.check_constraints(), false); params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -1.; @@ -548,7 +548,7 @@ TEST(TestWorld, checkParameterConstraints) ASSERT_EQ(params.check_constraints(), true); params.get() = mio::abm::TimePoint(2); - params.get() = mio::abm::hours(-1); + params.get() = mio::abm::hours(-1); ASSERT_EQ(params.check_constraints(), true); } @@ -742,7 +742,7 @@ TEST(TestWorld, personPlanning) .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2 * dt.days(); //setup so the person look 2 hours ahead. - world.parameters.get() = mio::abm::hours(2); + world.parameters.get() = mio::abm::hours(2); auto home_id = world.add_location(mio::abm::LocationType::Home); auto school_id = world.add_location(mio::abm::LocationType::School); From c523cfc71d9550a6c9fe68f37c563b3465166acb Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:48:48 +0200 Subject: [PATCH 21/37] Remove tests for planning in World --- cpp/tests/test_abm_world.cpp | 78 ------------------------------------ 1 file changed, 78 deletions(-) diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 46a03f3ec4..c4032484b6 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -776,81 +776,3 @@ TEST(TestWorld, copyWorld) ASSERT_NE(copied_world.get_persons()[1].get_location().get_type(), world.get_persons()[1].get_location().get_type()); } - -// TEST(TestWorld, personPlanning) -// { -// using testing::Return; -// auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); -// auto dt = mio::abm::hours(1); -// auto world = mio::abm::World(num_age_groups); -// //setup so p1 doesn't do transition -// world.parameters -// .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = -// 2 * dt.days(); -// world.parameters -// .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = -// 2 * dt.days(); -// world.parameters.get().set_multiple({age_group_5_to_14}, true); -// world.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); -// // Setup so the person look 2 hours ahead. -// world.parameters.get() = mio::abm::hours(2); - -// auto home_id = world.add_location(mio::abm::LocationType::Home); -// auto school_id = world.add_location(mio::abm::LocationType::School); -// auto work_id = world.add_location(mio::abm::LocationType::Work); -// auto hospital_id = world.add_location(mio::abm::LocationType::Hospital); - -// ScopedMockDistribution>>> mock_uniform_dist; -// EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) -// .Times(testing::AtLeast(8)) -// .WillOnce(testing::Return(0.8)) // draw random work group -// .WillOnce(testing::Return(0.8)) // draw random school group -// .WillOnce(testing::Return(0.8)) // draw random work hour -// .WillOnce(testing::Return(0.8)) // draw random school hour -// .WillOnce(testing::Return(0.8)) // draw random work group -// .WillOnce(testing::Return(0.8)) // draw random school group -// .WillOnce(testing::Return(0.8)) // draw random work hour -// .WillOnce(testing::Return(0.8)) // draw random school hour -// .WillRepeatedly(testing::Return(1.0)); - -// auto& p1 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); -// auto& p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); - -// p1.set_assigned_location(school_id); -// p2.set_assigned_location(school_id); -// p1.set_assigned_location(work_id); -// p2.set_assigned_location(work_id); -// p1.set_assigned_location(home_id); -// p2.set_assigned_location(home_id); - -// auto& school = world.get_individualized_location(school_id); -// auto& work = world.get_individualized_location(work_id); -// auto& home = world.get_individualized_location(home_id); - -// ScopedMockDistribution>>> -// mock_exponential_dist; -// EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillRepeatedly(Return(1.)); //no state transitions - -// world.evolve(t, dt); - -// // Check whether the 2 people migrate correctly. -// EXPECT_EQ(p1.get_location(), work); -// EXPECT_EQ(p2.get_location(), school); -// EXPECT_EQ(school.get_number_persons(), 1); -// EXPECT_EQ(work.get_number_persons(), 1); - -// // Check whether the 2 people plan to go home after. -// auto p1_migration_plan = p1.get_migration_plan(t, t + mio::abm::hours(3)); -// auto p2_migration_plan = p2.get_migration_plan(t, t + mio::abm::hours(3)); -// EXPECT_EQ(p1_migration_plan.size(), 2); -// EXPECT_EQ(p2_migration_plan.size(), 2); -// EXPECT_EQ(p1_migration_plan[1].second, home); -// EXPECT_EQ(p2_migration_plan[1].second, home); - -// // Test of no daily routine migration, just infection related -// world.use_migration_rules(false); -// auto& p3 = add_test_person(world, hospital_id, age_group_60_to_79, mio::abm::InfectionState::Recovered, t); -// p3.set_assigned_location(home_id); -// p3.set_assigned_location(hospital_id); -// world.evolve(t, dt); -// } \ No newline at end of file From b82e845255bf121bfadba1266deb1a184a3d80cb Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:31:01 +0200 Subject: [PATCH 22/37] Fix the error of PRAGMA_OMP(parallel for) in World::planning --- cpp/models/abm/world.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 570d0f1776..83810aed0f 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -83,9 +83,9 @@ void World::interaction(TimePoint t, TimeSpan dt) void World::planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map) { - PRAGMA_OMP(parallel for) bool weekend = t.is_weekend(); - + + PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; if ((person->get_planned_time() < t) || From 26d5a67ab82264f625e30b176d44d2a96e2c1313 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:26:41 +0200 Subject: [PATCH 23/37] Attemp 2 --- cpp/models/abm/world.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 83810aed0f..ba1c8a8fcb 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -83,8 +83,7 @@ void World::interaction(TimePoint t, TimeSpan dt) void World::planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map) { - bool weekend = t.is_weekend(); - + PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; @@ -139,6 +138,7 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_map Date: Thu, 11 Jul 2024 10:46:07 +0200 Subject: [PATCH 24/37] Attemp 3 --- cpp/models/abm/world.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index ba1c8a8fcb..08860cb7bd 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -63,6 +63,7 @@ void World::evolve(TimePoint t, TimeSpan dt) // Pass though dt time until the PlanAheadTime and make planning for the agents. auto time_counter = t; std::unordered_map personId_to_loc_map; + PRAGMA_OMP(parallel while) while (time_counter.seconds() <= (t + parameters.get()).seconds()) { planning(time_counter, dt, personId_to_loc_map); time_counter += dt; @@ -83,8 +84,6 @@ void World::interaction(TimePoint t, TimeSpan dt) void World::planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map) { - - PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; if ((person->get_planned_time() < t) || @@ -164,6 +163,7 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_map Date: Fri, 12 Jul 2024 14:49:43 +0200 Subject: [PATCH 25/37] Attemp 4 with pragma omp critical --- cpp/models/abm/world.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index 08860cb7bd..b32da681b7 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -63,7 +63,7 @@ void World::evolve(TimePoint t, TimeSpan dt) // Pass though dt time until the PlanAheadTime and make planning for the agents. auto time_counter = t; std::unordered_map personId_to_loc_map; - PRAGMA_OMP(parallel while) + PRAGMA_OMP(parallel for) while (time_counter.seconds() <= (t + parameters.get()).seconds()) { planning(time_counter, dt, personId_to_loc_map); time_counter += dt; @@ -102,6 +102,7 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_mapapply_mask_intervention(personal_rng, target_location); if (wears_mask) { + #pragma omp critical person->add_migration_plan(t, target_location); personId_to_loc_map[person->get_person_id()] = &target_location; return true; @@ -150,6 +151,7 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_mapis_in_quarantine(t, parameters) && person->get_infection_state(t) != InfectionState::Dead) { auto& target_location = get_individualized_location(trip.migration_destination); if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + #pragma omp critical person->add_migration_plan(t, target_location); } } From f7bc458ac9d6ad6228b92ab4595514c6ebb6c5a1 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:50:04 +0200 Subject: [PATCH 26/37] Add PRAGMA_OMP(critical) to World --- cpp/models/abm/world.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index b32da681b7..c64aced01c 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -102,7 +102,7 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_mapapply_mask_intervention(personal_rng, target_location); if (wears_mask) { - #pragma omp critical + PRAGMA_OMP(critical) person->add_migration_plan(t, target_location); personId_to_loc_map[person->get_person_id()] = &target_location; return true; @@ -151,7 +151,7 @@ void World::planning(TimePoint t, TimeSpan dt, std::unordered_mapis_in_quarantine(t, parameters) && person->get_infection_state(t) != InfectionState::Dead) { auto& target_location = get_individualized_location(trip.migration_destination); if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { - #pragma omp critical + PRAGMA_OMP(critical) person->add_migration_plan(t, target_location); } } From ec9c8783776ba2ca4572082b32918eb99ceb6aa1 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:15:17 +0200 Subject: [PATCH 27/37] Fix error with PRAGMA_OMP(parallel for) to World --- cpp/models/abm/world.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index c64aced01c..b95fc612a3 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -63,7 +63,6 @@ void World::evolve(TimePoint t, TimeSpan dt) // Pass though dt time until the PlanAheadTime and make planning for the agents. auto time_counter = t; std::unordered_map personId_to_loc_map; - PRAGMA_OMP(parallel for) while (time_counter.seconds() <= (t + parameters.get()).seconds()) { planning(time_counter, dt, personId_to_loc_map); time_counter += dt; @@ -84,6 +83,7 @@ void World::interaction(TimePoint t, TimeSpan dt) void World::planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map) { + PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { auto&& person = m_persons[i]; if ((person->get_planned_time() < t) || From 4e906afb30827c6d8a8d8f29b7a20f4dd374570c Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:21:01 +0200 Subject: [PATCH 28/37] Improve comments and adjust TestResult to have a variable named is_allowed_to_enter --- cpp/models/abm/analyze_result.h | 4 ---- cpp/models/abm/parameters.h | 22 +--------------------- cpp/models/abm/person.cpp | 6 +++--- cpp/models/abm/person.h | 8 ++++---- cpp/models/abm/testing_strategy.cpp | 17 ++++++++++------- cpp/models/abm/trip_list.h | 11 ----------- cpp/models/abm/world.h | 8 +------- cpp/tests/test_abm_person.cpp | 2 +- cpp/tests/test_abm_testing_strategy.cpp | 10 ++++++---- cpp/tests/test_abm_world.cpp | 5 +---- 10 files changed, 27 insertions(+), 66 deletions(-) diff --git a/cpp/models/abm/analyze_result.h b/cpp/models/abm/analyze_result.h index 0e27015065..05107af313 100644 --- a/cpp/models/abm/analyze_result.h +++ b/cpp/models/abm/analyze_result.h @@ -192,10 +192,6 @@ std::vector ensemble_params_percentile(const std::vector().days(); return result; }); - param_percentil(node, [](auto&& model) -> auto& { - static auto result = model.parameters.template get().days(); - return result; - }); } return percentile; diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 644c914b4b..4bc7839ed6 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -547,21 +547,6 @@ struct AgeGroupGotoWork { } }; -/** - * @brief The TimeSpan agents plan forward to take tests. - */ -struct PlanAheadTime { - using Type = TimeSpan; - static Type get_default(AgeGroup /*size*/) - { - return TimeSpan(seconds(0)); - } - static std::string name() - { - return "PlanAheadTime"; - } -}; - using ParametersBase = ParameterSet; + TestData>; /** * @brief Maximum number of Person%s an infectious Person can infect at the respective Location. @@ -780,11 +765,6 @@ class Parameters : public ParametersBase return true; } - if (this->get().seconds() < 0.0) { - log_error("Constraint check: Parameter PlanAheadTime smaller {:d}", 0); - return true; - } - return false; } diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index d13b3840a2..8b97961e04 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -268,9 +268,9 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const void Person::add_test_result(TimePoint t, TestType type, bool result) { TestResult test_result; - test_result.time_of_testing = t; - test_result.type = type; - test_result.result = result; + test_result.time_of_testing = t; + test_result.type = type; + test_result.is_allowed_to_enter = result; // Remove outdated test results or replace the old result of the same type m_test_results[{type}] = test_result; } diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 04567a1b3c..30fbdb267a 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -73,7 +73,7 @@ class Person struct TestResult { TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. TestType type = TestType::Count; ///< The TestType of the test. - bool result; ///< The result of the test. + bool is_allowed_to_enter; ///< Whether the person is allowed to enter the Location. }; /** @@ -437,15 +437,15 @@ class Person /** * @brief Add TestResult to the Person - * @param[in] t The TimePoint of the result. - * @param[in] type The TestType of the result. + * @param[in] t The TimePoint of the test. + * @param[in] type The TestType of the test. * @param[in] result The result of the test. */ void add_test_result(TimePoint t, TestType type, bool result); /** * @brief Get the most recent TestResult performed from the Person based on the TestType. - * @param[in] type The TestType of the result. + * @param[in] type The TestType of the test. */ const TestResult get_test_result(TestType type) const; diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 0456ed8d7a..530c99eb10 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -192,14 +192,17 @@ bool TestingStrategy::run_strategy(PersonalRandomNumberGenerator& rng, Person& p // If the agent has a test result valid until now, use the result directly if ((test_result.type != TestType::Count) && (test_result.time_of_testing + ts.get_test_parameters().validity_period >= t)) { - return test_result.result; + return test_result.is_allowed_to_enter; } - // If not, perform the test and save result. - // In this case, the time_of_testing in the past (i.e. the agent has already performed it). - auto result = !ts.is_active() || ts.run_scheme(rng, person, t); - person.add_test_result(t - ts.get_test_parameters().required_time, - ts.get_test_parameters().type, result); - return result; + // If not, check if the test scheme is active, perform the test and save result. + auto is_person_allowed_to_enter = true; + if (ts.is_active()) { + is_person_allowed_to_enter = ts.run_scheme(rng, person, t); + // In this case, the time_of_testing in the past (i.e. the agent has already performed it). + person.add_test_result(t - ts.get_test_parameters().required_time, + ts.get_test_parameters().type, is_person_allowed_to_enter); + } + return is_person_allowed_to_enter; })) { return false; } diff --git a/cpp/models/abm/trip_list.h b/cpp/models/abm/trip_list.h index 19f7dbf982..a32831440f 100644 --- a/cpp/models/abm/trip_list.h +++ b/cpp/models/abm/trip_list.h @@ -31,8 +31,6 @@ namespace mio namespace abm { -class Infection; - /** * @brief A trip describes a migration from one Location to another Location. */ @@ -194,15 +192,6 @@ class TripList return m_current_index; } - /** - * @brief Get trips between two TimePoints. - * @param from_time The time from which to find the trips. - * @param to_time The time to which to find the trips. - * @param weekend Whether to search in weekend trips or weekday trips. - * @return A vector of pointers to the trips after the given time. - */ - std::vector get_trips_between(TimePoint from_time, TimePoint to_time, bool weekend) const; - private: std::vector m_trips_weekday; ///< The list of Trip%s a Person makes on a weekday. std::vector m_trips_weekend; ///< The list of Trip%s a Person makes on a weekend day. diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 1e7c2ca69f..7cb00153c6 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -454,13 +454,7 @@ class World * @param[in] dt The length of the time step of the Simulation. */ void interaction(TimePoint t, TimeSpan dt); - /** - * @brief Person%s plan to move in the World according to rules. - * @param[in] t The current TimePoint. - * @param[in] dt The length of the time step of the Simulation. - * @param[in] personId_to_loc_map A map used to track Person's location for planning. - */ - void planning(TimePoint t, TimeSpan dt, std::unordered_map& personId_to_loc_map); + /** * @brief Person%s move in the World according to planning. * @param[in] t The current TimePoint. diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 438c6d1de7..8cba6aab33 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -330,5 +330,5 @@ TEST(Person, addAndGetTestResult) auto t = mio::abm::TimePoint(0); person.add_test_result(t, mio::abm::TestType::Generic, true); - ASSERT_TRUE(person.get_test_result(mio::abm::TestType::Generic).result); + ASSERT_TRUE(person.get_test_result(mio::abm::TestType::Generic).is_allowed_to_enter); } diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index a324d71ff4..258d7cd4d8 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -68,7 +68,8 @@ TEST(TestTestingScheme, runScheme) const auto start_date = mio::abm::TimePoint(0); const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); const auto probability = 0.8; - const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; + const auto test_params_pcr = + mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; @@ -115,7 +116,8 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) const auto start_date = mio::abm::TimePoint(0); const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); const auto probability = 0.8; - const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; + const auto test_params_pcr = + mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; @@ -136,7 +138,7 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) - .Times(testing::Exactly(2)) //only sampled twice, testing criteria don't apply to third person + .Times(testing::Exactly(2)) //only sampled twice, testing criteria don't apply to third test strategy run. .WillOnce(testing::Return(0.7)) .WillOnce(testing::Return(0.5)); @@ -149,5 +151,5 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) ASSERT_EQ(test_strategy.run_strategy(rng_person2, person2, loc_work, start_date), true); // Person tests and tests negative ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date), - false); // Person doesn't test + false); // Person doesn't test but used the last result (false to enter) } diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 9d261125c0..f3d185b847 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -170,7 +170,7 @@ TEST(TestWorld, evolveStateTransition) EXPECT_EQ(p3.get_infection_state(t + dt), mio::abm::InfectionState::InfectedSymptoms); } -TEST(TestWorld, evolve) +TEST(TestWorld, evolveMigration) { using testing::Return; @@ -187,8 +187,6 @@ TEST(TestWorld, evolve) 2 * dt.days(); world.parameters.get().set_multiple({age_group_5_to_14}, true); world.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); - // Setup so the person look 2 hours ahead. - world.parameters.get() = mio::abm::hours(2); auto home_id = world.add_location(mio::abm::LocationType::Home); auto school_id = world.add_location(mio::abm::LocationType::School); @@ -501,7 +499,6 @@ TEST(TestWorld, checkParameterConstraints) params.get()[mio::abm::MaskType::FFP2] = 0.6; params.get()[mio::abm::MaskType::Surgical] = 0.7; params.get() = mio::abm::TimePoint(0); - params.get() = mio::abm::hours(1); ASSERT_EQ(params.check_constraints(), false); params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -1.; From aa36dfdf609ed7dcef31ac7850cb091a5fb5f681 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:37:50 +0200 Subject: [PATCH 29/37] Remove m_time_of_last_test for Person --- cpp/benchmarks/abm.cpp | 23 ++++------ cpp/examples/abm_history_object.cpp | 5 +- cpp/examples/abm_minimal.cpp | 7 ++- cpp/models/abm/README.md | 3 +- cpp/models/abm/person.cpp | 4 +- cpp/models/abm/person.h | 10 ---- cpp/models/abm/testing_strategy.cpp | 33 ++++++------- cpp/models/abm/testing_strategy.h | 7 +-- cpp/simulations/abm.cpp | 17 +++---- cpp/tests/test_abm_person.cpp | 2 - cpp/tests/test_abm_testing_strategy.cpp | 46 +++++++++---------- cpp/tests/test_abm_world.cpp | 10 ++-- .../memilio/simulation/abm.cpp | 2 +- 13 files changed, 68 insertions(+), 101 deletions(-) diff --git a/cpp/benchmarks/abm.cpp b/cpp/benchmarks/abm.cpp index 7246db43a7..b09ab0503b 100644 --- a/cpp/benchmarks/abm.cpp +++ b/cpp/benchmarks/abm.cpp @@ -92,21 +92,18 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list(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::TestType::Antigen; auto test_parameters = world.parameters.get()[test_type]; 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_parameters, probability); + auto testing_scheme_work = + mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_parameters, probability); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work); // Assign infection state to each person. diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 33491dcb7b..0f2d721733 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -41,7 +41,7 @@ int main() world.parameters.get() = 4.; // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - world.parameters.get() = false; + world.parameters.get() = false; world.parameters.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 and 35-59) world.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); @@ -104,15 +104,14 @@ int main() .get()[{age_group_15_to_34, age_group_15_to_34}] = 10.0; // People can get tested at work (and do this with 0.5 probability) from time point 0 to day 10. - 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(10); auto test_type = mio::abm::TestType::Antigen; auto test_parameters = world.parameters.get()[test_type]; 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_parameters, probability); + auto testing_scheme_work = + mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_parameters, probability); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work); // Assign infection state to each person. diff --git a/cpp/models/abm/README.md b/cpp/models/abm/README.md index 70ec2fdca4..b9011c2f88 100644 --- a/cpp/models/abm/README.md +++ b/cpp/models/abm/README.md @@ -98,7 +98,6 @@ add_household_group_to_world(world, twoPersonHousehold_group); During the simulation, people can get tested, and we have to specify the scheme for that: ```cpp -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); @@ -107,7 +106,7 @@ auto test_at_work = std::vector{mio::abm::LocationTy auto testing_criteria_work = std::vector{mio::abm::TestingCriteria({}, test_at_work, {})}; auto testing_scheme_work = - mio::abm::TestingScheme(testing_criteria_work, testing_min_time, start_date, end_date, test_type, probability); + mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_type, probability); world.get_testing_strategy().add_testing_scheme(testing_scheme_work); ``` diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 8b97961e04..35c7af326d 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -39,7 +39,6 @@ Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, Loca , m_quarantine_start(TimePoint(-(std::numeric_limits::max() / 2))) , m_age(age) , m_time_at_location(0) - , m_time_of_last_test(TimePoint(-(std::numeric_limits::max() / 2))) , m_mask(Mask(MaskType::Community)) , m_wears_mask(false) , m_mask_compliance((uint32_t)LocationType::Count, 0.) @@ -155,8 +154,7 @@ void Person::remove_quarantine() bool Person::get_tested(PersonalRandomNumberGenerator& rng, TimePoint t, const TestParameters& params) { - ScalarType random = UniformDistribution::get_instance()(rng); - m_time_of_last_test = t; + ScalarType random = UniformDistribution::get_instance()(rng); if (is_infected(t)) { // true positive if (random < params.sensitivity) { diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 30fbdb267a..2b9be4e436 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -163,15 +163,6 @@ class Person m_time_at_location += dt; } - /** - * @brief Get the TimePoint of the last negative test. - * @return TimePoint since the last test. - */ - TimePoint get_time_of_last_test() const - { - return m_time_of_last_test; - } - /** * @brief Set an assigned Location of the Person. * @@ -463,7 +454,6 @@ class Person double m_random_schoolgroup; ///< Value to determine if the Person goes to school or stays at home during lockdown. double m_random_goto_work_hour; ///< Value to determine at what time the Person goes to work. double m_random_goto_school_hour; ///< Value to determine at what time the Person goes to school. - TimePoint m_time_of_last_test; ///< TimePoint of the last negative test. Mask m_mask; ///< The Mask of the Person. bool m_wears_mask = false; ///< Whether the Person currently wears a Mask. std::vector m_mask_compliance; ///< Vector of Mask compliance values for all #LocationType%s. diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 530c99eb10..4643357171 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -70,11 +70,9 @@ bool TestingCriteria::evaluate(const Person& p, TimePoint t) const (m_infection_states.none() || m_infection_states[static_cast(p.get_infection_state(t))]); } -TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimeSpan minimal_time_since_last_test, - TimePoint start_date, TimePoint end_date, TestParameters test_parameters, - double probability) +TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimePoint start_date, TimePoint end_date, + TestParameters test_parameters, double probability) : m_testing_criteria(testing_criteria) - , m_minimal_time_since_last_test(minimal_time_since_last_test) , m_start_date(start_date) , m_end_date(end_date) , m_test_parameters(test_parameters) @@ -84,9 +82,8 @@ TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimeSpan m bool TestingScheme::operator==(const TestingScheme& other) const { - return this->m_testing_criteria == other.m_testing_criteria && - this->m_minimal_time_since_last_test == other.m_minimal_time_since_last_test && - this->m_start_date == other.m_start_date && this->m_end_date == other.m_end_date && + return this->m_testing_criteria == other.m_testing_criteria && this->m_start_date == other.m_start_date && + this->m_end_date == other.m_end_date && this->m_test_parameters.sensitivity == other.m_test_parameters.sensitivity && this->m_test_parameters.specificity == other.m_test_parameters.specificity && this->m_probability == other.m_probability; @@ -105,12 +102,10 @@ void TestingScheme::update_activity_status(TimePoint t) bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& person, TimePoint t) const { - if (t - person.get_time_of_last_test() > m_minimal_time_since_last_test) { - if (m_testing_criteria.evaluate(person, t)) { - double random = UniformDistribution::get_instance()(rng); - if (random < m_probability) { - return !person.get_tested(rng, t, m_test_parameters); - } + if (m_testing_criteria.evaluate(person, t)) { + double random = UniformDistribution::get_instance()(rng); + if (random < m_probability) { + return !person.get_tested(rng, t, m_test_parameters); } } return true; @@ -195,13 +190,13 @@ bool TestingStrategy::run_strategy(PersonalRandomNumberGenerator& rng, Person& p return test_result.is_allowed_to_enter; } // If not, check if the test scheme is active, perform the test and save result. - auto is_person_allowed_to_enter = true; - if (ts.is_active()) { - is_person_allowed_to_enter = ts.run_scheme(rng, person, t); - // In this case, the time_of_testing in the past (i.e. the agent has already performed it). - person.add_test_result(t - ts.get_test_parameters().required_time, - ts.get_test_parameters().type, is_person_allowed_to_enter); + if (!ts.is_active()) { + return true; } + bool is_person_allowed_to_enter = ts.run_scheme(rng, person, t); + // In this case, the time_of_testing in the past (i.e. the agent has already performed it). + person.add_test_result(t - ts.get_test_parameters().required_time, ts.get_test_parameters().type, + is_person_allowed_to_enter); return is_person_allowed_to_enter; })) { return false; diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 7bf1bb8d4a..d9692bf31a 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -108,15 +108,13 @@ class TestingScheme /** * @brief Create a TestingScheme. * @param[in] testing_criteria Vector of TestingCriteria that are checked for testing. - * @param[in] minimal_time_since_last_test TimeSpan of how often this scheme applies, i. e., when a new test is - * performed after a Person's last test. * @param start_date Starting date of the scheme. * @param end_date Ending date of the scheme. * @param test_parameters The parameters of test to be performed. * @param probability Probability of the test to be performed if a testing rule applies. */ - TestingScheme(const TestingCriteria& testing_criteria, TimeSpan minimal_time_since_last_test, TimePoint start_date, - TimePoint end_date, TestParameters test_parameters, ScalarType probability); + TestingScheme(const TestingCriteria& testing_criteria, TimePoint start_date, TimePoint end_date, + TestParameters test_parameters, ScalarType probability); /** * @brief Compares two TestingScheme%s for functional equality. @@ -161,7 +159,6 @@ class TestingScheme private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. - TimeSpan m_minimal_time_since_last_test; ///< Shortest period of time between two tests. TimePoint m_start_date; ///< Starting date of the scheme. TimePoint m_end_date; ///< Ending date of the scheme. TestParameters m_test_parameters; ///< Parameters of the test. diff --git a/cpp/simulations/abm.cpp b/cpp/simulations/abm.cpp index 099019f38c..381a79abf6 100644 --- a/cpp/simulations/abm.cpp +++ b/cpp/simulations/abm.cpp @@ -324,16 +324,15 @@ void create_assign_locations(mio::abm::World& world) world.get_location(event).set_capacity(100, 375); auto testing_criteria = mio::abm::TestingCriteria(); - auto testing_min_time = mio::abm::days(2); auto start_date = mio::abm::TimePoint(0); auto end_date = mio::abm::TimePoint(0) + mio::abm::days(60); auto probability = mio::UncertainValue<>(); assign_uniform_distribution(probability, 0.5, 1.0); - auto test_params = world.parameters.get()[mio::abm::TestType::Antigen]; - auto testing_scheme = mio::abm::TestingScheme(testing_criteria, testing_min_time, start_date, end_date, test_params, - probability.draw_sample()); + auto test_params = world.parameters.get()[mio::abm::TestType::Antigen]; + auto testing_scheme = + mio::abm::TestingScheme(testing_criteria, start_date, end_date, test_params, probability.draw_sample()); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); @@ -427,18 +426,16 @@ void create_assign_locations(mio::abm::World& world) // add the testing schemes for school and work auto testing_criteria_school = mio::abm::TestingCriteria(); - testing_min_time = mio::abm::days(7); - auto testing_scheme_school = mio::abm::TestingScheme(testing_criteria_school, testing_min_time, start_date, - end_date, test_params, probability.draw_sample()); + auto testing_scheme_school = + mio::abm::TestingScheme(testing_criteria_school, start_date, end_date, test_params, probability.draw_sample()); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::School, testing_scheme_school); auto test_at_work = std::vector{mio::abm::LocationType::Work}; auto testing_criteria_work = mio::abm::TestingCriteria(); assign_uniform_distribution(probability, 0.1, 0.5); - testing_min_time = mio::abm::days(1); - auto testing_scheme_work = mio::abm::TestingScheme(testing_criteria_work, testing_min_time, start_date, end_date, - test_params, probability.draw_sample()); + auto testing_scheme_work = + mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_params, probability.draw_sample()); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work); } diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 8cba6aab33..c95f7f042c 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -166,7 +166,6 @@ TEST(TestPerson, get_tested) EXPECT_EQ(susceptible.is_in_quarantine(t, params), false); EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, pcr_parameters), true); EXPECT_EQ(susceptible.is_in_quarantine(t, params), true); - EXPECT_EQ(susceptible.get_time_of_last_test(), mio::abm::TimePoint(0)); // Test antigen test ScopedMockDistribution>>> @@ -181,7 +180,6 @@ TEST(TestPerson, get_tested) EXPECT_EQ(infected.get_tested(rng_infected, t, antigen_parameters), false); EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, antigen_parameters), false); EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, antigen_parameters), true); - EXPECT_EQ(susceptible.get_time_of_last_test(), mio::abm::TimePoint(0)); } TEST(TestPerson, getCells) diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 258d7cd4d8..1eb74ebc52 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -64,18 +64,17 @@ TEST(TestTestingScheme, runScheme) auto testing_criteria1 = mio::abm::TestingCriteria({}, test_infection_states1); std::vector testing_criterias = {testing_criteria1}; - const auto testing_min_time = mio::abm::days(1); - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 0.8; + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 0.8; const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; - auto testing_scheme1 = mio::abm::TestingScheme(testing_criteria1, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme1 = + mio::abm::TestingScheme(testing_criteria1, start_date, end_date, test_params_pcr, probability); ASSERT_EQ(testing_scheme1.is_active(), false); testing_scheme1.update_activity_status(mio::abm::TimePoint(10)); @@ -86,8 +85,8 @@ TEST(TestTestingScheme, runScheme) std::vector test_infection_states2 = {mio::abm::InfectionState::Recovered}; auto testing_criteria2 = mio::abm::TestingCriteria({}, test_infection_states2); - auto testing_scheme2 = mio::abm::TestingScheme(testing_criteria2, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme2 = + mio::abm::TestingScheme(testing_criteria2, start_date, end_date, test_params_pcr, probability); mio::abm::Location loc_home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location loc_work(mio::abm::LocationType::Work, 0, num_age_groups); @@ -98,37 +97,38 @@ TEST(TestTestingScheme, runScheme) ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) - .Times(testing::Exactly(4)) - .WillOnce(testing::Return(0.7)) - .WillOnce(testing::Return(0.5)) - .WillOnce(testing::Return(0.7)) - .WillOnce(testing::Return(0.5)); + .Times(testing::Exactly(6)) + .WillOnce(testing::Return(0.7)) // Person 1 got test + .WillOnce(testing::Return(0.5)) // Person 1 tested positive and cannot enter + .WillOnce(testing::Return(0.7)) // Person 2 got test + .WillOnce(testing::Return(0.5)) // Person 2 tested negative and can enter + .WillOnce(testing::Return(0.7)) // Person 1 got test + .WillOnce(testing::Return(0.9)); // Person 1 tested negative and can enter ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date), false); // Person tests and tests positive ASSERT_EQ(testing_scheme2.run_scheme(rng_person2, person2, start_date), true); // Person tests and tests negative ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date), - true); // Person doesn't test + true); // Person tests and tests negative } TEST(TestTestingScheme, initAndRunTestingStrategy) { - auto rng = mio::RandomNumberGenerator(); - const auto testing_min_time = mio::abm::days(1); - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 0.8; + auto rng = mio::RandomNumberGenerator(); + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 0.8; const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; auto testing_criteria1 = mio::abm::TestingCriteria({}, test_infection_states); - auto testing_scheme1 = mio::abm::TestingScheme(testing_criteria1, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme1 = + mio::abm::TestingScheme(testing_criteria1, start_date, end_date, test_params_pcr, probability); testing_scheme1.update_activity_status(mio::abm::TimePoint(0)); std::vector test_infection_states2 = {mio::abm::InfectionState::Recovered}; auto testing_criteria2 = mio::abm::TestingCriteria({}, test_infection_states2); - auto testing_scheme2 = mio::abm::TestingScheme(testing_criteria2, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme2 = + mio::abm::TestingScheme(testing_criteria2, start_date, end_date, test_params_pcr, probability); mio::abm::Location loc_work(mio::abm::LocationType::Work, 0); auto person1 = make_test_person(loc_work, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index f3d185b847..726d19b732 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -443,15 +443,13 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); - const auto testing_frequency = mio::abm::days(1); - const auto start_date = mio::abm::TimePoint(20); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 1.0; + const auto start_date = mio::abm::TimePoint(20); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 1.0; const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; - auto testing_scheme = mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme = mio::abm::TestingScheme(testing_criteria, start_date, end_date, test_params_pcr, probability); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), diff --git a/pycode/memilio-simulation/memilio/simulation/abm.cpp b/pycode/memilio-simulation/memilio/simulation/abm.cpp index 079b5c1a8d..d3b9366335 100644 --- a/pycode/memilio-simulation/memilio/simulation/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/abm.cpp @@ -157,7 +157,7 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_class(m, "TestingScheme") .def(py::init(), - py::arg("testing_criteria"), py::arg("testing_min_time_since_last_test"), py::arg("start_date"), + py::arg("testing_criteria"), py::arg("start_date"), py::arg("end_date"), py::arg("test_parameters"), py::arg("probability")) .def_property_readonly("active", &mio::abm::TestingScheme::is_active); From 8eed843b07f17636d7fcb7300a3bb9c93a7d8971 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:50:56 +0200 Subject: [PATCH 30/37] Fix error in pycode abm.cpp --- pycode/memilio-simulation/memilio/simulation/abm.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pycode/memilio-simulation/memilio/simulation/abm.cpp b/pycode/memilio-simulation/memilio/simulation/abm.cpp index d3b9366335..c42b352e8b 100644 --- a/pycode/memilio-simulation/memilio/simulation/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/abm.cpp @@ -155,10 +155,10 @@ PYBIND11_MODULE(_simulation_abm, m) py::arg("age_groups"), py::arg("infection_states")); pymio::bind_class(m, "TestingScheme") - .def(py::init(), - py::arg("testing_criteria"), py::arg("start_date"), - py::arg("end_date"), py::arg("test_parameters"), py::arg("probability")) + py::arg("testing_criteria"), py::arg("start_date"), py::arg("end_date"), py::arg("test_parameters"), + py::arg("probability")) .def_property_readonly("active", &mio::abm::TestingScheme::is_active); pymio::bind_class(m, "Vaccination") From 7c03e5f099f7906097daa5d912ab3af0c43518e7 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:05:26 +0200 Subject: [PATCH 31/37] Fix error in pycode test_abm.py --- pycode/memilio-simulation/memilio/simulation_test/test_abm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py index c1604b5d38..1267d01087 100644 --- a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py +++ b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py @@ -56,8 +56,8 @@ def test_locations(self): testing_inf_states = [] testing_crit = abm.TestingCriteria( testing_ages, testing_inf_states) - testing_scheme = abm.TestingScheme(testing_crit, abm.days( - 1), t0, t0 + abm.days(1), world.parameters.TestData[abm.TestType.Antigen], 1.0) + testing_scheme = abm.TestingScheme( + testing_crit, t0, t0 + abm.days(1), world.parameters.TestData[abm.TestType.Antigen], 1.0) # initially false, will only active once simulation starts self.assertEqual(testing_scheme.active, False) From cc625cb8ec74314643848e994ed8c632313f0955 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:31:47 +0200 Subject: [PATCH 32/37] Move checking old test result to run_scheme() --- cpp/models/abm/testing_strategy.cpp | 28 +++++++++++-------------- cpp/models/abm/testing_strategy.h | 9 -------- cpp/tests/test_abm_testing_strategy.cpp | 9 ++++---- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 4643357171..8421557e06 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -102,10 +102,20 @@ void TestingScheme::update_activity_status(TimePoint t) bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& person, TimePoint t) const { + auto test_result = person.get_test_result(m_test_parameters.type); + // If the agent has a test result valid until now, use the result directly + if ((test_result.type != TestType::Count) && + (test_result.time_of_testing + m_test_parameters.validity_period >= t)) { + return test_result.is_allowed_to_enter; + } if (m_testing_criteria.evaluate(person, t)) { double random = UniformDistribution::get_instance()(rng); if (random < m_probability) { - return !person.get_tested(rng, t, m_test_parameters); + // In this case, the time_of_testing in the past (i.e. the agent has already performed it). + bool is_person_allowed_to_enter = !person.get_tested(rng, t, m_test_parameters); + person.add_test_result(t - m_test_parameters.required_time, m_test_parameters.type, + is_person_allowed_to_enter); + return is_person_allowed_to_enter; } } return true; @@ -183,21 +193,7 @@ bool TestingStrategy::run_strategy(PersonalRandomNumberGenerator& rng, Person& p //apply all testing schemes that are found auto& schemes = iter_schemes->schemes; if (!std::all_of(schemes.begin(), schemes.end(), [&rng, &person, t](TestingScheme& ts) { - auto test_result = person.get_test_result(ts.get_test_parameters().type); - // If the agent has a test result valid until now, use the result directly - if ((test_result.type != TestType::Count) && - (test_result.time_of_testing + ts.get_test_parameters().validity_period >= t)) { - return test_result.is_allowed_to_enter; - } - // If not, check if the test scheme is active, perform the test and save result. - if (!ts.is_active()) { - return true; - } - bool is_person_allowed_to_enter = ts.run_scheme(rng, person, t); - // In this case, the time_of_testing in the past (i.e. the agent has already performed it). - person.add_test_result(t - ts.get_test_parameters().required_time, ts.get_test_parameters().type, - is_person_allowed_to_enter); - return is_person_allowed_to_enter; + return !ts.is_active() || ts.run_scheme(rng, person, t); })) { return false; } diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index d9692bf31a..bcda480e14 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -148,15 +148,6 @@ class TestingScheme */ bool run_scheme(PersonalRandomNumberGenerator& rng, Person& person, TimePoint t) const; - /** - * @brief Gets the TestParameters of the TestingScheme. - * @return The TestParameters of the TestingScheme. - */ - TestParameters get_test_parameters() const - { - return m_test_parameters; - } - private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. TimePoint m_start_date; ///< Starting date of the scheme. diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 1eb74ebc52..7e353c266a 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -97,17 +97,16 @@ TEST(TestTestingScheme, runScheme) ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) - .Times(testing::Exactly(6)) + .Times(testing::Exactly(4)) .WillOnce(testing::Return(0.7)) // Person 1 got test .WillOnce(testing::Return(0.5)) // Person 1 tested positive and cannot enter .WillOnce(testing::Return(0.7)) // Person 2 got test - .WillOnce(testing::Return(0.5)) // Person 2 tested negative and can enter - .WillOnce(testing::Return(0.7)) // Person 1 got test - .WillOnce(testing::Return(0.9)); // Person 1 tested negative and can enter + .WillOnce(testing::Return(0.5)); // Person 2 tested negative and can enter + ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date), false); // Person tests and tests positive ASSERT_EQ(testing_scheme2.run_scheme(rng_person2, person2, start_date), true); // Person tests and tests negative ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date), - true); // Person tests and tests negative + false); // Person doesn't test but used the last result (false to enter) } TEST(TestTestingScheme, initAndRunTestingStrategy) From b44be5b37ca280ff5140ffd867a03a50cbacd98e Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:36:03 +0200 Subject: [PATCH 33/37] Modifications according to David comments --- cpp/models/abm/person.cpp | 8 ++------ cpp/models/abm/person.h | 9 --------- cpp/models/abm/test_type.h | 10 +++++++++- cpp/models/abm/testing_strategy.cpp | 15 +++++++-------- cpp/models/abm/testing_strategy.h | 6 ------ cpp/tests/test_abm_person.cpp | 14 ++++++++++++-- cpp/tests/test_abm_testing_strategy.cpp | 14 ++++++++------ 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 35c7af326d..8a7e8a6447 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -265,15 +265,11 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const void Person::add_test_result(TimePoint t, TestType type, bool result) { - TestResult test_result; - test_result.time_of_testing = t; - test_result.type = type; - test_result.is_allowed_to_enter = result; // Remove outdated test results or replace the old result of the same type - m_test_results[{type}] = test_result; + m_test_results[{type}] = {t, result}; } -const Person::TestResult Person::get_test_result(TestType type) const +const TestResult Person::get_test_result(TestType type) const { return m_test_results[{type}]; } diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index a1ac1acbfe..b3c5860c46 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -69,15 +69,6 @@ class Person return (m_person_id == other.m_person_id); } - /** - * @brief The TestResult of a Person. - */ - struct TestResult { - TimePoint time_of_testing; ///< The TimePoint when the Person performs the test. - TestType type = TestType::Count; ///< The TestType of the test. - bool is_allowed_to_enter; ///< Whether the person is allowed to enter the Location. - }; - /** * @brief Get the latest #Infection of the Person. * @return The latest #Infection of the Person. diff --git a/cpp/models/abm/test_type.h b/cpp/models/abm/test_type.h index 253e31e4b5..9d497fe919 100644 --- a/cpp/models/abm/test_type.h +++ b/cpp/models/abm/test_type.h @@ -22,7 +22,7 @@ #define MIO_ABM_TEST_TYPE_H #include - +#include namespace mio { namespace abm @@ -40,6 +40,14 @@ enum class TestType : std::uint32_t Count }; +/** +* @brief The TestResult of a Person. +*/ +struct TestResult { + TimePoint time_of_testing = TimePoint(std::numeric_limits::min()); ///< The TimePoint when the Person performs the test. + bool result; ///< The test result. +}; + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 8421557e06..3677378c6c 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -104,18 +104,17 @@ bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& perso { auto test_result = person.get_test_result(m_test_parameters.type); // If the agent has a test result valid until now, use the result directly - if ((test_result.type != TestType::Count) && + if ((test_result.time_of_testing != TimePoint(std::numeric_limits::min())) && (test_result.time_of_testing + m_test_parameters.validity_period >= t)) { - return test_result.is_allowed_to_enter; + return !test_result.result; } - if (m_testing_criteria.evaluate(person, t)) { + // Otherwise, the time_of_testing in the past (i.e. the agent has already performed it). + if (m_testing_criteria.evaluate(person, t - m_test_parameters.required_time)) { double random = UniformDistribution::get_instance()(rng); if (random < m_probability) { - // In this case, the time_of_testing in the past (i.e. the agent has already performed it). - bool is_person_allowed_to_enter = !person.get_tested(rng, t, m_test_parameters); - person.add_test_result(t - m_test_parameters.required_time, m_test_parameters.type, - is_person_allowed_to_enter); - return is_person_allowed_to_enter; + bool result = person.get_tested(rng, t - m_test_parameters.required_time, m_test_parameters); + person.add_test_result(t - m_test_parameters.required_time, m_test_parameters.type, result); + return !result; } } return true; diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index bcda480e14..d7386aae0c 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -127,12 +127,6 @@ class TestingScheme */ bool is_active() const; - /** - * @brief Gets the activity status of the scheme at a given TimePoint. - * @return Whether the TestingScheme is active at a given TimePoint. - */ - bool is_active_at_time(TimePoint t) const; - /** * @brief Checks if the scheme is active at a given time and updates activity status. * @param[in] t TimePoint to be updated at. diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index f7208fb2a8..42d29ea537 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -326,7 +326,17 @@ TEST(Person, addAndGetTestResult) mio::abm::Location location(mio::abm::LocationType::School, 0, num_age_groups); auto person = make_test_person(location); auto t = mio::abm::TimePoint(0); - + // Tests if m_test_results initialized correctly + EXPECT_EQ(person.get_test_result(mio::abm::TestType::Generic).time_of_testing, + mio::abm::TimePoint(std::numeric_limits::min())); + EXPECT_FALSE(person.get_test_result(mio::abm::TestType::Generic).result); + EXPECT_EQ(person.get_test_result(mio::abm::TestType::Antigen).time_of_testing, + mio::abm::TimePoint(std::numeric_limits::min())); + EXPECT_FALSE(person.get_test_result(mio::abm::TestType::Antigen).result); + EXPECT_EQ(person.get_test_result(mio::abm::TestType::PCR).time_of_testing, + mio::abm::TimePoint(std::numeric_limits::min())); + EXPECT_FALSE(person.get_test_result(mio::abm::TestType::PCR).result); + // Test if m_test_results updated person.add_test_result(t, mio::abm::TestType::Generic, true); - ASSERT_TRUE(person.get_test_result(mio::abm::TestType::Generic).is_allowed_to_enter); + EXPECT_TRUE(person.get_test_result(mio::abm::TestType::Generic).result); } diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 7e353c266a..4ba855bb5e 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -103,9 +103,11 @@ TEST(TestTestingScheme, runScheme) .WillOnce(testing::Return(0.7)) // Person 2 got test .WillOnce(testing::Return(0.5)); // Person 2 tested negative and can enter - ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date), false); // Person tests and tests positive - ASSERT_EQ(testing_scheme2.run_scheme(rng_person2, person2, start_date), true); // Person tests and tests negative - ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date), + ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date + test_params_pcr.required_time), + false); // Person tests and tests positive + ASSERT_EQ(testing_scheme2.run_scheme(rng_person2, person2, start_date + test_params_pcr.required_time), + true); // Person tests and tests negative + ASSERT_EQ(testing_scheme1.run_scheme(rng_person1, person1, start_date + test_params_pcr.required_time), false); // Person doesn't test but used the last result (false to enter) } @@ -145,10 +147,10 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) mio::abm::TestingStrategy(std::vector{}); test_strategy.add_testing_scheme(mio::abm::LocationType::Work, testing_scheme1); test_strategy.add_testing_scheme(mio::abm::LocationType::Work, testing_scheme2); - ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date), + ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date + test_params_pcr.required_time), false); // Person tests and tests positive - ASSERT_EQ(test_strategy.run_strategy(rng_person2, person2, loc_work, start_date), + ASSERT_EQ(test_strategy.run_strategy(rng_person2, person2, loc_work, start_date + test_params_pcr.required_time), true); // Person tests and tests negative - ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date), + ASSERT_EQ(test_strategy.run_strategy(rng_person1, person1, loc_work, start_date + test_params_pcr.required_time), false); // Person doesn't test but used the last result (false to enter) } From a37bab8e3a240564960560ba689a9caadc97685c Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:12:37 +0200 Subject: [PATCH 34/37] Small adjustments in comments --- cpp/models/abm/person.cpp | 2 +- cpp/models/abm/person.h | 4 ++-- cpp/models/abm/testing_strategy.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 8a7e8a6447..6dbeb9cfdc 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -269,7 +269,7 @@ void Person::add_test_result(TimePoint t, TestType type, bool result) m_test_results[{type}] = {t, result}; } -const TestResult Person::get_test_result(TestType type) const +TestResult Person::get_test_result(TestType type) const { return m_test_results[{type}]; } diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index b3c5860c46..6dee0b4273 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -383,7 +383,7 @@ class Person } /** - * @brief Get the latest #Infection or #Vaccination and its initial TimePoint of the Person. + * @brief Get the latest #ExposureType and its initial TimePoint of the Person. */ std::pair get_latest_protection() const; @@ -431,7 +431,7 @@ class Person * @brief Get the most recent TestResult performed from the Person based on the TestType. * @param[in] type The TestType of the test. */ - const TestResult get_test_result(TestType type) const; + TestResult get_test_result(TestType type) const; private: LocationId m_location; ///< Current Location of the Person. diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 3677378c6c..49c50e5e20 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -104,7 +104,7 @@ bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& perso { auto test_result = person.get_test_result(m_test_parameters.type); // If the agent has a test result valid until now, use the result directly - if ((test_result.time_of_testing != TimePoint(std::numeric_limits::min())) && + if ((test_result.time_of_testing > TimePoint(std::numeric_limits::min())) && (test_result.time_of_testing + m_test_parameters.validity_period >= t)) { return !test_result.result; } From 80dc615f79203d6e8cb672d28df5624b5c4ac534 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:00:34 +0200 Subject: [PATCH 35/37] Move validity_period from TestParameter to TestingScheme --- cpp/benchmarks/abm.cpp | 23 ++++++++++--------- cpp/examples/abm_history_object.cpp | 5 +++-- cpp/examples/abm_minimal.cpp | 5 +++-- cpp/models/abm/parameters.h | 7 +++--- cpp/models/abm/testing_strategy.cpp | 13 ++++++----- cpp/models/abm/testing_strategy.h | 6 +++-- cpp/simulations/abm.cpp | 17 +++++++------- cpp/tests/test_abm_mobility_rules.cpp | 9 ++++---- cpp/tests/test_abm_model.cpp | 6 +++-- cpp/tests/test_abm_person.cpp | 6 ++--- cpp/tests/test_abm_testing_strategy.cpp | 30 ++++++++++++------------- 11 files changed, 68 insertions(+), 59 deletions(-) diff --git a/cpp/benchmarks/abm.cpp b/cpp/benchmarks/abm.cpp index 83f5e74f3a..4d104caca5 100644 --- a/cpp/benchmarks/abm.cpp +++ b/cpp/benchmarks/abm.cpp @@ -92,18 +92,21 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list(10); // People can get tested at work (and do this with 0.5 probability) from time point 0 to day 30. + auto validity_period = 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::TestType::Antigen; auto test_parameters = model.parameters.get()[test_type]; auto testing_criteria_work = mio::abm::TestingCriteria(); - auto testing_scheme_work = - mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_parameters, probability); + auto testing_scheme_work = mio::abm::TestingScheme(testing_criteria_work, validity_period, start_date, end_date, + test_parameters, probability); model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work); // Assign infection state to each person. diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 786e1a0a49..9abb173dcf 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -104,14 +104,15 @@ int main() .get()[{age_group_15_to_34, age_group_15_to_34}] = 10.0; // People can get tested at work (and do this with 0.5 probability) from time point 0 to day 10. + auto validity_period = 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(10); auto test_type = mio::abm::TestType::Antigen; auto test_parameters = model.parameters.get()[test_type]; auto testing_criteria_work = mio::abm::TestingCriteria(); - auto testing_scheme_work = - mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_parameters, probability); + auto testing_scheme_work = mio::abm::TestingScheme(testing_criteria_work, validity_period, start_date, end_date, + test_parameters, probability); model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work); // Assign infection state to each person. diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 072667ce91..f4ab449f45 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -314,7 +314,6 @@ struct TestParameters { UncertainValue<> sensitivity; UncertainValue<> specificity; TimeSpan required_time; - TimeSpan validity_period; TestType type; /** @@ -356,9 +355,9 @@ struct TestData { static auto get_default(AgeGroup /*size*/) { Type default_val = Type({TestType::Count}); - default_val[{TestType::Generic}] = TestParameters{0.9, 0.99, hours(48), hours(72), TestType::Generic}; - default_val[{TestType::Antigen}] = TestParameters{0.8, 0.88, minutes(30), hours(48), TestType::Antigen}; - default_val[{TestType::PCR}] = TestParameters{0.9, 0.99, hours(48), hours(72), TestType::PCR}; + default_val[{TestType::Generic}] = TestParameters{0.9, 0.99, hours(48), TestType::Generic}; + default_val[{TestType::Antigen}] = TestParameters{0.8, 0.88, minutes(30), TestType::Antigen}; + default_val[{TestType::PCR}] = TestParameters{0.9, 0.99, hours(48), TestType::PCR}; return default_val; } static std::string name() diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 49c50e5e20..8b52e36402 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -70,9 +70,10 @@ bool TestingCriteria::evaluate(const Person& p, TimePoint t) const (m_infection_states.none() || m_infection_states[static_cast(p.get_infection_state(t))]); } -TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimePoint start_date, TimePoint end_date, - TestParameters test_parameters, double probability) +TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimeSpan validity_period, TimePoint start_date, + TimePoint end_date, TestParameters test_parameters, double probability) : m_testing_criteria(testing_criteria) + , m_validity_period(validity_period) , m_start_date(start_date) , m_end_date(end_date) , m_test_parameters(test_parameters) @@ -82,8 +83,8 @@ TestingScheme::TestingScheme(const TestingCriteria& testing_criteria, TimePoint bool TestingScheme::operator==(const TestingScheme& other) const { - return this->m_testing_criteria == other.m_testing_criteria && this->m_start_date == other.m_start_date && - this->m_end_date == other.m_end_date && + return this->m_testing_criteria == other.m_testing_criteria && this->m_validity_period == other.m_validity_period && + this->m_start_date == other.m_start_date && this->m_end_date == other.m_end_date && this->m_test_parameters.sensitivity == other.m_test_parameters.sensitivity && this->m_test_parameters.specificity == other.m_test_parameters.specificity && this->m_probability == other.m_probability; @@ -105,7 +106,7 @@ bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& perso auto test_result = person.get_test_result(m_test_parameters.type); // If the agent has a test result valid until now, use the result directly if ((test_result.time_of_testing > TimePoint(std::numeric_limits::min())) && - (test_result.time_of_testing + m_test_parameters.validity_period >= t)) { + (test_result.time_of_testing + m_validity_period >= t)) { return !test_result.result; } // Otherwise, the time_of_testing in the past (i.e. the agent has already performed it). @@ -113,7 +114,7 @@ bool TestingScheme::run_scheme(PersonalRandomNumberGenerator& rng, Person& perso double random = UniformDistribution::get_instance()(rng); if (random < m_probability) { bool result = person.get_tested(rng, t - m_test_parameters.required_time, m_test_parameters); - person.add_test_result(t - m_test_parameters.required_time, m_test_parameters.type, result); + person.add_test_result(t, m_test_parameters.type, result); return !result; } } diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index d7386aae0c..00578e7379 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -108,13 +108,14 @@ class TestingScheme /** * @brief Create a TestingScheme. * @param[in] testing_criteria Vector of TestingCriteria that are checked for testing. + * @param validity_period The valid TimeSpan of the test. * @param start_date Starting date of the scheme. * @param end_date Ending date of the scheme. * @param test_parameters The parameters of test to be performed. * @param probability Probability of the test to be performed if a testing rule applies. */ - TestingScheme(const TestingCriteria& testing_criteria, TimePoint start_date, TimePoint end_date, - TestParameters test_parameters, ScalarType probability); + TestingScheme(const TestingCriteria& testing_criteria, TimeSpan validity_period, TimePoint start_date, + TimePoint end_date, TestParameters test_parameters, ScalarType probability); /** * @brief Compares two TestingScheme%s for functional equality. @@ -144,6 +145,7 @@ class TestingScheme private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. + TimeSpan m_validity_period; ///< The valid TimeSpan of the test. TimePoint m_start_date; ///< Starting date of the scheme. TimePoint m_end_date; ///< Ending date of the scheme. TestParameters m_test_parameters; ///< Parameters of the test. diff --git a/cpp/simulations/abm.cpp b/cpp/simulations/abm.cpp index 33752c9b0e..0cb6f79673 100644 --- a/cpp/simulations/abm.cpp +++ b/cpp/simulations/abm.cpp @@ -324,15 +324,16 @@ void create_assign_locations(mio::abm::Model& model) model.get_location(event).set_capacity(100, 375); auto testing_criteria = mio::abm::TestingCriteria(); + auto validity_period = mio::abm::days(2); auto start_date = mio::abm::TimePoint(0); auto end_date = mio::abm::TimePoint(0) + mio::abm::days(60); auto probability = mio::UncertainValue<>(); assign_uniform_distribution(probability, 0.5, 1.0); - auto test_params = model.parameters.get()[mio::abm::TestType::Antigen]; - auto testing_scheme = - mio::abm::TestingScheme(testing_criteria, start_date, end_date, test_params, probability.draw_sample()); + auto test_params = model.parameters.get()[mio::abm::TestType::Antigen]; + auto testing_scheme = mio::abm::TestingScheme(testing_criteria, validity_period, start_date, end_date, test_params, + probability.draw_sample()); model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::SocialEvent, testing_scheme); @@ -425,17 +426,17 @@ void create_assign_locations(mio::abm::Model& model) // add the testing schemes for school and work auto testing_criteria_school = mio::abm::TestingCriteria(); - - auto testing_scheme_school = - mio::abm::TestingScheme(testing_criteria_school, start_date, end_date, test_params, probability.draw_sample()); + validity_period = mio::abm::days(7); + auto testing_scheme_school = mio::abm::TestingScheme(testing_criteria_school, validity_period, start_date, end_date, + test_params, probability.draw_sample()); model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::School, testing_scheme_school); auto test_at_work = std::vector{mio::abm::LocationType::Work}; auto testing_criteria_work = mio::abm::TestingCriteria(); assign_uniform_distribution(probability, 0.1, 0.5); - auto testing_scheme_work = - mio::abm::TestingScheme(testing_criteria_work, start_date, end_date, test_params, probability.draw_sample()); + auto testing_scheme_work = mio::abm::TestingScheme(testing_criteria_work, validity_period, start_date, end_date, + test_params, probability.draw_sample()); model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme_work); } diff --git a/cpp/tests/test_abm_mobility_rules.cpp b/cpp/tests/test_abm_mobility_rules.cpp index 1d40158a45..b37b26b7c7 100644 --- a/cpp/tests/test_abm_mobility_rules.cpp +++ b/cpp/tests/test_abm_mobility_rules.cpp @@ -365,11 +365,10 @@ TEST(TestMobilityRules, work_return) TEST(TestMobilityRules, quarantine) { - auto rng = mio::RandomNumberGenerator(); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); - auto test_params = - mio::abm::TestParameters{1.0, 1.0, mio::abm::minutes(30), mio::abm::hours(24), mio::abm::TestType::Generic}; + auto rng = mio::RandomNumberGenerator(); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); + auto test_params = mio::abm::TestParameters{1.0, 1.0, mio::abm::minutes(30), mio::abm::TestType::Generic}; mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); mio::abm::Location hospital(mio::abm::LocationType::Hospital, 0, num_age_groups); diff --git a/cpp/tests/test_abm_model.cpp b/cpp/tests/test_abm_model.cpp index afaa022084..46232ba401 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -443,13 +443,15 @@ TEST(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedNoSymptoms); + auto validity_period = mio::abm::days(1); const auto start_date = mio::abm::TimePoint(20); const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); const auto probability = 1.0; const auto test_params_pcr = - mio::abm::TestParameters{0.9, 0.99, mio::abm::minutes(30), mio::abm::hours(24), mio::abm::TestType::Generic}; + mio::abm::TestParameters{0.9, 0.99, mio::abm::minutes(30), mio::abm::TestType::Generic}; - auto testing_scheme = mio::abm::TestingScheme(testing_criteria, start_date, end_date, test_params_pcr, probability); + auto testing_scheme = + mio::abm::TestingScheme(testing_criteria, validity_period, start_date, end_date, test_params_pcr, probability); model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); ASSERT_EQ(model.get_testing_strategy().run_strategy(rng_person, person, work, current_time), diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 42d29ea537..0356d35384 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -92,9 +92,9 @@ TEST(TestPerson, setGetAssignedLocation) TEST(TestPerson, quarantine) { using testing::Return; - auto rng = mio::RandomNumberGenerator(); - auto test_params = mio::abm::TestParameters{1.01, 1.01, mio::abm::minutes(30), mio::abm::hours(24), - mio::abm::TestType::Generic}; //100% safe test + auto rng = mio::RandomNumberGenerator(); + auto test_params = + mio::abm::TestParameters{1.01, 1.01, mio::abm::minutes(30), mio::abm::TestType::Generic}; //100% safe test auto infection_parameters = mio::abm::Parameters(num_age_groups); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 4ba855bb5e..73b16b496e 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -64,17 +64,17 @@ TEST(TestTestingScheme, runScheme) auto testing_criteria1 = mio::abm::TestingCriteria({}, test_infection_states1); std::vector testing_criterias = {testing_criteria1}; - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 0.8; - const auto test_params_pcr = - mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; + auto validity_period = mio::abm::days(1); + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 0.8; + const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::TestType::PCR}; std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; auto testing_scheme1 = - mio::abm::TestingScheme(testing_criteria1, start_date, end_date, test_params_pcr, probability); + mio::abm::TestingScheme(testing_criteria1, validity_period, start_date, end_date, test_params_pcr, probability); ASSERT_EQ(testing_scheme1.is_active(), false); testing_scheme1.update_activity_status(mio::abm::TimePoint(10)); @@ -86,7 +86,7 @@ TEST(TestTestingScheme, runScheme) std::vector test_infection_states2 = {mio::abm::InfectionState::Recovered}; auto testing_criteria2 = mio::abm::TestingCriteria({}, test_infection_states2); auto testing_scheme2 = - mio::abm::TestingScheme(testing_criteria2, start_date, end_date, test_params_pcr, probability); + mio::abm::TestingScheme(testing_criteria2, validity_period, start_date, end_date, test_params_pcr, probability); mio::abm::Location loc_home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location loc_work(mio::abm::LocationType::Work, 0, num_age_groups); @@ -113,23 +113,23 @@ TEST(TestTestingScheme, runScheme) TEST(TestTestingScheme, initAndRunTestingStrategy) { - auto rng = mio::RandomNumberGenerator(); - const auto start_date = mio::abm::TimePoint(0); - const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); - const auto probability = 0.8; - const auto test_params_pcr = - mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::hours(72), mio::abm::TestType::PCR}; + auto rng = mio::RandomNumberGenerator(); + auto validity_period = mio::abm::days(1); + const auto start_date = mio::abm::TimePoint(0); + const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); + const auto probability = 0.8; + const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99, mio::abm::hours(48), mio::abm::TestType::PCR}; std::vector test_infection_states = {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}; auto testing_criteria1 = mio::abm::TestingCriteria({}, test_infection_states); auto testing_scheme1 = - mio::abm::TestingScheme(testing_criteria1, start_date, end_date, test_params_pcr, probability); + mio::abm::TestingScheme(testing_criteria1, validity_period, start_date, end_date, test_params_pcr, probability); testing_scheme1.update_activity_status(mio::abm::TimePoint(0)); std::vector test_infection_states2 = {mio::abm::InfectionState::Recovered}; auto testing_criteria2 = mio::abm::TestingCriteria({}, test_infection_states2); auto testing_scheme2 = - mio::abm::TestingScheme(testing_criteria2, start_date, end_date, test_params_pcr, probability); + mio::abm::TestingScheme(testing_criteria2, validity_period, start_date, end_date, test_params_pcr, probability); mio::abm::Location loc_work(mio::abm::LocationType::Work, 0); auto person1 = make_test_person(loc_work, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); From 4a29b0c0df82e9c0f5b4ba6a142b275e17dff941 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:03:18 +0200 Subject: [PATCH 36/37] Fix pycode --- pycode/memilio-simulation/memilio/simulation/abm.cpp | 6 +++--- .../memilio-simulation/memilio/simulation_test/test_abm.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pycode/memilio-simulation/memilio/simulation/abm.cpp b/pycode/memilio-simulation/memilio/simulation/abm.cpp index c77881b911..c3782b9b83 100644 --- a/pycode/memilio-simulation/memilio/simulation/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/abm.cpp @@ -155,10 +155,10 @@ PYBIND11_MODULE(_simulation_abm, m) py::arg("age_groups"), py::arg("infection_states")); pymio::bind_class(m, "TestingScheme") - .def(py::init(), - py::arg("testing_criteria"), py::arg("start_date"), py::arg("end_date"), py::arg("test_parameters"), - py::arg("probability")) + py::arg("testing_criteria"), py::arg("testing_validity_period"), py::arg("start_date"), + py::arg("end_date"), py::arg("test_parameters"), py::arg("probability")) .def_property_readonly("active", &mio::abm::TestingScheme::is_active); pymio::bind_class(m, "Vaccination") diff --git a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py index 87a0c71e55..fa1e40f62c 100644 --- a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py +++ b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py @@ -56,8 +56,8 @@ def test_locations(self): testing_inf_states = [] testing_crit = abm.TestingCriteria( testing_ages, testing_inf_states) - testing_scheme = abm.TestingScheme( - testing_crit, t0, t0 + abm.days(1), model.parameters.TestData[abm.TestType.Antigen], 1.0) + testing_scheme = abm.TestingScheme(testing_crit, abm.days( + 1), t0, t0 + abm.days(1), model.parameters.TestData[abm.TestType.Antigen], 1.0) # initially false, will only active once simulation starts self.assertEqual(testing_scheme.active, False) From c0f443e9008b75da724d566e0dfa50bdb59a3b87 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen <4763945+khoanguyen-dev@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:09:09 +0200 Subject: [PATCH 37/37] Small adjustments according to David's comments --- cpp/models/abm/person.h | 2 ++ cpp/models/abm/test_type.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 6dee0b4273..79994aadb1 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -429,7 +429,9 @@ class Person /** * @brief Get the most recent TestResult performed from the Person based on the TestType. + * If time_of_testing == TimePoint(std::numeric_limits::min()), there is no previous TestResult. * @param[in] type The TestType of the test. + * @return The latest TestResult of the given Type. */ TestResult get_test_result(TestType type) const; diff --git a/cpp/models/abm/test_type.h b/cpp/models/abm/test_type.h index 9d497fe919..ebe7b9b46e 100644 --- a/cpp/models/abm/test_type.h +++ b/cpp/models/abm/test_type.h @@ -44,8 +44,8 @@ enum class TestType : std::uint32_t * @brief The TestResult of a Person. */ struct TestResult { - TimePoint time_of_testing = TimePoint(std::numeric_limits::min()); ///< The TimePoint when the Person performs the test. - bool result; ///< The test result. + TimePoint time_of_testing{std::numeric_limits::min()}; ///< The TimePoint when the Person performs the test. + bool result{false}; ///< The test result. }; } // namespace abm