diff --git a/cpp/examples/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index 2c04cba986..91a05759f3 100644 --- a/cpp/examples/abm_history_object.cpp +++ b/cpp/examples/abm_history_object.cpp @@ -125,14 +125,14 @@ int main() model.get_location(work).get_infection_parameters().set(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 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, testing_min_time, start_date, end_date, + 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); diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 4e0c65c812..9abb173dcf 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -104,14 +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 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, testing_min_time, start_date, end_date, + 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); diff --git a/cpp/models/abm/README.md b/cpp/models/abm/README.md index e94f0ae708..a2644b693d 100644 --- a/cpp/models/abm/README.md +++ b/cpp/models/abm/README.md @@ -98,7 +98,6 @@ add_household_group_to_model(model, 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); model.get_testing_strategy().add_testing_scheme(testing_scheme_work); ``` diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index fdc15920d8..f4ab449f45 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -42,7 +42,7 @@ namespace abm * @brief Time that a Person is infected but not yet infectious. */ struct IncubationPeriod { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -54,7 +54,7 @@ struct IncubationPeriod { }; struct InfectedNoSymptomsToSymptoms { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -66,7 +66,7 @@ struct InfectedNoSymptomsToSymptoms { }; struct InfectedNoSymptomsToRecovered { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -78,7 +78,7 @@ struct InfectedNoSymptomsToRecovered { }; struct InfectedSymptomsToRecovered { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -90,7 +90,7 @@ struct InfectedSymptomsToRecovered { }; struct InfectedSymptomsToSevere { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -102,7 +102,7 @@ struct InfectedSymptomsToSevere { }; struct SevereToCritical { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -114,7 +114,7 @@ struct SevereToCritical { }; struct SevereToRecovered { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -126,7 +126,7 @@ struct SevereToRecovered { }; struct CriticalToRecovered { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -138,7 +138,7 @@ struct CriticalToRecovered { }; struct CriticalToDead { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -150,7 +150,7 @@ struct CriticalToDead { }; struct RecoveredToSusceptible { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -211,7 +211,7 @@ struct InfectivityDistributions { * @brief Probability that an Infection is detected. */ struct DetectInfection { - using Type = CustomIndexArray< UncertainValue<>, VirusVariant, AgeGroup>; + using Type = CustomIndexArray, VirusVariant, AgeGroup>; static Type get_default(AgeGroup size) { return Type({VirusVariant::Count, size}, 1.); @@ -226,7 +226,7 @@ struct DetectInfection { * @brief Effectiveness of a Mask of a certain MaskType% against an Infection%. */ struct MaskProtection { - using Type = CustomIndexArray< UncertainValue<>, MaskType>; + using Type = CustomIndexArray, MaskType>; static Type get_default(AgeGroup /*size*/) { return Type({MaskType::Count}, 1.); @@ -311,38 +311,40 @@ struct HighViralLoadProtectionFactor { * @brief Parameters that describe the reliability of a test. */ struct TestParameters { - UncertainValue<> sensitivity; - UncertainValue<> specificity; + UncertainValue<> sensitivity; + UncertainValue<> specificity; + TimeSpan required_time; + TestType type; - /** + /** * serialize this. * @see mio::serialize */ - template - void serialize(IOContext& io) const - { - auto obj = io.create_object("TestParameters"); - obj.add_element("Sensitivity", sensitivity); - obj.add_element("Specificity", specificity); - } - - /** + template + void serialize(IOContext& io) const + { + auto obj = io.create_object("TestParameters"); + obj.add_element("Sensitivity", sensitivity); + obj.add_element("Specificity", specificity); + } + + /** * deserialize an object of this class. * @see mio::deserialize */ - template - static IOResult deserialize(IOContext& io) - { - auto obj = io.expect_object("TestParameters"); - auto sens = obj.expect_element("Sensitivity", mio::Tag>{}); - auto spec = obj.expect_element("Specificity", mio::Tag>{}); - return apply( - io, - [](auto&& sens_, auto&& spec_) { - return TestParameters{sens_, spec_}; - }, - sens, spec); - } + template + static IOResult deserialize(IOContext& io) + { + auto obj = io.expect_object("TestParameters"); + auto sens = obj.expect_element("Sensitivity", mio::Tag>{}); + auto spec = obj.expect_element("Specificity", mio::Tag>{}); + return apply( + io, + [](auto&& sens_, auto&& spec_) { + return TestParameters{sens_, spec_}; + }, + sens, spec); + } }; /** @@ -353,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}; - default_val[{TestType::Antigen}] = TestParameters{0.8, 0.88}; - default_val[{TestType::PCR}] = TestParameters{0.9, 0.99}; + 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() @@ -398,7 +400,7 @@ struct QuarantineDuration { * @brief Parameter for the exponential distribution to decide if a Person goes shopping. */ struct BasicShoppingRate { - using Type = CustomIndexArray< UncertainValue<>, AgeGroup>; + using Type = CustomIndexArray, AgeGroup>; static auto get_default(AgeGroup size) { return Type({size}, 1.0); diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 6e5938cff3..6dbeb9cfdc 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -39,13 +39,13 @@ 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.) , m_person_id(person_id) , m_cells{0} , m_last_transport_mode(TransportMode::Unknown) + , m_test_results({TestType::Count}, TestResult()) { m_random_workgroup = UniformDistribution::get_instance()(rng); m_random_schoolgroup = UniformDistribution::get_instance()(rng); @@ -154,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) { @@ -264,5 +263,16 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const t.days() - latest_protection.second.days()); } +void Person::add_test_result(TimePoint t, TestType type, bool result) +{ + // Remove outdated test results or replace the old result of the same type + m_test_results[{type}] = {t, result}; +} + +TestResult Person::get_test_result(TestType type) const +{ + return m_test_results[{type}]; +} + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 82c5741e05..79994aadb1 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -29,6 +29,7 @@ #include "abm/person_id.h" #include "abm/personal_rng.h" #include "abm/time.h" +#include "abm/test_type.h" #include "abm/vaccine.h" #include "abm/mask.h" #include "abm/mobility_data.h" @@ -155,15 +156,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. * @@ -391,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; @@ -427,6 +419,22 @@ class Person loc, age, id); } + /** + * @brief Add TestResult to the Person + * @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. + * 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; + private: LocationId m_location; ///< Current Location of the Person. LocationType m_location_type; ///< Type of the current Location. @@ -441,14 +449,14 @@ 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. PersonId 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 + Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator. + CustomIndexArray m_test_results; ///< CustomIndexArray for TestResults. }; } // namespace abm diff --git a/cpp/models/abm/test_type.h b/cpp/models/abm/test_type.h index 253e31e4b5..ebe7b9b46e 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{std::numeric_limits::min()}; ///< The TimePoint when the Person performs the test. + bool result{false}; ///< The test result. +}; + } // namespace abm } // namespace mio diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index dba48efa2a..8b52e36402 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -70,11 +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, TimeSpan minimal_time_since_last_test, - 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_minimal_time_since_last_test(minimal_time_since_last_test) + , m_validity_period(validity_period) , m_start_date(start_date) , m_end_date(end_date) , m_test_parameters(test_parameters) @@ -84,8 +83,7 @@ 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 && + 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 && @@ -105,12 +103,19 @@ 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); - } + 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_validity_period >= t)) { + return !test_result.result; + } + // 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) { + bool result = person.get_tested(rng, t - m_test_parameters.required_time, m_test_parameters); + person.add_test_result(t, m_test_parameters.type, result); + return !result; } } return true; @@ -176,7 +181,6 @@ bool TestingStrategy::run_strategy(PersonalRandomNumberGenerator& rng, Person& p if (location.get_type() == mio::abm::LocationType::Home) { 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 key : {std::make_pair(location.get_type(), location.get_id()), diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index 53040efe9e..00578e7379 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -108,14 +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 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, TimeSpan minimal_time_since_last_test, TimePoint start_date, + TestingScheme(const TestingCriteria& testing_criteria, TimeSpan validity_period, TimePoint start_date, TimePoint end_date, TestParameters test_parameters, ScalarType probability); /** @@ -124,7 +123,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; @@ -146,7 +145,7 @@ 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. + 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 c5dce6940b..0cb6f79673 100644 --- a/cpp/simulations/abm.cpp +++ b/cpp/simulations/abm.cpp @@ -324,7 +324,7 @@ void create_assign_locations(mio::abm::Model& model) model.get_location(event).set_capacity(100, 375); auto testing_criteria = mio::abm::TestingCriteria(); - auto testing_min_time = mio::abm::days(2); + 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); @@ -332,7 +332,7 @@ void create_assign_locations(mio::abm::Model& model) 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, testing_min_time, start_date, end_date, test_params, + 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); @@ -426,18 +426,16 @@ void create_assign_locations(mio::abm::Model& model) // 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()); + 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); - testing_min_time = mio::abm::days(1); - auto testing_scheme_work = mio::abm::TestingScheme(testing_criteria_work, testing_min_time, start_date, end_date, + 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 5580d83aa1..b37b26b7c7 100644 --- a/cpp/tests/test_abm_mobility_rules.cpp +++ b/cpp/tests/test_abm_mobility_rules.cpp @@ -368,8 +368,7 @@ 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}; - + 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 57dc291e0f..46232ba401 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -443,14 +443,15 @@ TEST(TestModelTestingCriteria, 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_params_pcr = mio::abm::TestParameters{0.9, 0.99}; - - auto testing_scheme = mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, - test_params_pcr, probability); + 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::TestType::Generic}; + + 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 17ab9b4c59..0356d35384 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -92,8 +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}; //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); @@ -165,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>>> @@ -180,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) @@ -321,3 +320,23 @@ TEST(Person, rng) EXPECT_EQ(p.get_rng_counter(), mio::Counter(1)); EXPECT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{1})); } + +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); + 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 dfd44c14dc..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 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_params_pcr = mio::abm::TestParameters{0.9, 0.99}; + 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, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme1 = + 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)); @@ -85,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, 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); @@ -98,35 +98,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)); - 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 + .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 + + 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) } 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_params_pcr = mio::abm::TestParameters{0.9, 0.99}; + 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, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme1 = + 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, testing_min_time, start_date, end_date, - test_params_pcr, probability); + auto testing_scheme2 = + 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); @@ -136,7 +139,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)); @@ -144,9 +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), true); // Person doesn't test + 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) } diff --git a/pycode/memilio-simulation/memilio/simulation/abm.cpp b/pycode/memilio-simulation/memilio/simulation/abm.cpp index f02b37814b..c3782b9b83 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("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);