diff --git a/cpp/models/abm/model.cpp b/cpp/models/abm/model.cpp index 2b19d8af1a..e0e75a39ea 100755 --- a/cpp/models/abm/model.cpp +++ b/cpp/models/abm/model.cpp @@ -289,7 +289,7 @@ void Model::compute_exposure_caches(TimePoint t, TimeSpan dt) assert(m_persons[i].get_location_model_id() == m_id && "Person is not in this model but still active."); mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location], m_contact_exposure_rates_cache[location], person, - get_location(person.get_location()), t, dt); + get_location(person.get_location()), parameters, t, dt); } } // implicit taskloop barrier } // implicit single barrier diff --git a/cpp/models/abm/model_functions.cpp b/cpp/models/abm/model_functions.cpp index 8a88dddf39..b8536947f6 100644 --- a/cpp/models/abm/model_functions.cpp +++ b/cpp/models/abm/model_functions.cpp @@ -98,7 +98,8 @@ void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const } void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExposureRates& local_contact_exposure, - const Person& person, const Location& location, const TimePoint t, const TimeSpan dt) + const Person& person, const Location& location, const Parameters& params, + const TimePoint t, const TimeSpan dt) { if (person.get_location() != location.get_id()) { mio::log_debug("In add_exposure_contribution: Person {} is not at Location {}", person.get_id().get(), @@ -110,16 +111,20 @@ void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExpo auto virus = infection.get_virus_variant(); auto age = person.get_age(); // average infectivity over the time step to second order accuracy using midpoint rule + const auto infectivity = infection.get_infectivity(t + dt / 2); + const auto quarantine_factor = + person.is_in_quarantine(t, params) ? (1.0 - params.get()) : 1.0; + for (CellIndex cell : person.get_cells()) { + auto air_contribution = infectivity * quarantine_factor; + auto contact_contribution = infectivity * quarantine_factor; + if (location.get_infection_parameters().get()) { - local_air_exposure[{cell, virus}] += - infection.get_infectivity(t + dt / 2) * - location.get_cells()[cell.get()].compute_space_per_person_relative(); + air_contribution *= location.get_cells()[cell.get()].compute_space_per_person_relative(); } - else { - local_air_exposure[{cell, virus}] += infection.get_infectivity(t + dt / 2); - } - local_contact_exposure[{cell, virus, age}] += infection.get_infectivity(t + dt / 2); + + local_air_exposure[{cell, virus}] += air_contribution; + local_contact_exposure[{cell, virus, age}] += contact_contribution; } } } diff --git a/cpp/models/abm/model_functions.h b/cpp/models/abm/model_functions.h index 7eb6c9e01f..a4bd16aa67 100644 --- a/cpp/models/abm/model_functions.h +++ b/cpp/models/abm/model_functions.h @@ -61,11 +61,13 @@ ScalarType daily_transmissions_by_air(const AirExposureRates& rates, const CellI * @param[in, out] local_contact_exposure Exposure by rates contacts for the local population. * @param[in] person A person from the local population. * @param[in] location The person's current location. + * @param[in] params The parameter set of the Model. * @param[in] t Current Simulation time. * @param[in] dt Length of the current Simulation time step. */ void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExposureRates& local_contact_exposure, - const Person& person, const Location& location, const TimePoint t, const TimeSpan dt); + const Person& person, const Location& location, const Parameters& params, + const TimePoint t, const TimeSpan dt); /** * @brief Let a Person interact with the population at its current Location, possibly getting infected. diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 8a3ff0ca1a..8d92762f0e 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -530,6 +530,21 @@ struct QuarantineDuration { } }; +/** + * @brief Effectiveness of quarantine. 0.0 meaning no effect (precentual reduction of viral shed emmition), 1.0 meaning full effect. + */ +struct QuarantineEffectiveness { + using Type = ScalarType; + static auto get_default(AgeGroup /*size*/) + { + return 0.0; + } + static std::string name() + { + return "QuarantineEffectivness"; + } +}; + /** * @brief Parameter for the exponential distribution to decide if a Person goes shopping. */ @@ -687,10 +702,10 @@ using ParametersBase = TimeInfectedCriticalToRecovered, SymptomsPerInfectedNoSymptoms, SeverePerInfectedSymptoms, CriticalPerInfectedSevere, DeathsPerInfectedSevere, DeathsPerInfectedCritical, ViralLoadDistributions, InfectivityDistributions, VirusShedFactor, DetectInfection, MaskProtection, AerosolTransmissionRates, - LockdownDate, QuarantineDuration, SocialEventRate, BasicShoppingRate, WorkRatio, SchoolRatio, - GotoWorkTimeMinimum, GotoWorkTimeMaximum, GotoSchoolTimeMinimum, GotoSchoolTimeMaximum, - AgeGroupGotoSchool, AgeGroupGotoWork, InfectionProtectionFactor, SeverityProtectionFactor, - HighViralLoadProtectionFactor, TestData>; + LockdownDate, QuarantineDuration, QuarantineEffectiveness, SocialEventRate, BasicShoppingRate, + WorkRatio, SchoolRatio, GotoWorkTimeMinimum, GotoWorkTimeMaximum, GotoSchoolTimeMinimum, + GotoSchoolTimeMaximum, AgeGroupGotoSchool, AgeGroupGotoWork, InfectionProtectionFactor, + SeverityProtectionFactor, HighViralLoadProtectionFactor, TestData>; /** * @brief Maximum number of Person%s an infectious Person can infect at the respective Location. @@ -984,6 +999,11 @@ class Parameters : public ParametersBase return true; } + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter QuarantineEffectiveness not between {:d,:d}", 0.0, 1.0); + return true; + } + return false; } diff --git a/cpp/tests/abm_helpers.cpp b/cpp/tests/abm_helpers.cpp index 8f70ba384d..7d0e043303 100644 --- a/cpp/tests/abm_helpers.cpp +++ b/cpp/tests/abm_helpers.cpp @@ -61,9 +61,9 @@ void interact_testing(mio::abm::PersonalRandomNumberGenerator& personal_rng, mio std::for_each(local_contact_exposure.begin(), local_contact_exposure.end(), [](auto& r) { r = 0.0; }); - // caclculate current exposures + // calculate current exposures for (const mio::abm::Person& p : local_population) { - add_exposure_contribution(local_air_exposure, local_contact_exposure, p, location, t, dt); + add_exposure_contribution(local_air_exposure, local_contact_exposure, p, location, global_parameters, t, dt); } // run interaction mio::abm::interact(personal_rng, person, location, local_air_exposure, local_contact_exposure, t, dt, diff --git a/cpp/tests/test_abm_location.cpp b/cpp/tests/test_abm_location.cpp index 3163db0ff9..0d57d18fa6 100644 --- a/cpp/tests/test_abm_location.cpp +++ b/cpp/tests/test_abm_location.cpp @@ -103,6 +103,7 @@ TEST_F(TestLocation, interact) // Setup location with some chance of exposure mio::abm::Location location(mio::abm::LocationType::Work, 0, num_age_groups); + location.get_infection_parameters().get() = true; auto infected1 = make_test_person(this->get_rng(), location, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t, params); auto infected2 = make_test_person(this->get_rng(), location, age_group_80_plus, @@ -185,4 +186,4 @@ TEST_F(TestLocation, adjustContactRates) auto adjusted_contacts_rate = loc.get_infection_parameters().get()[{mio::AgeGroup(0), mio::AgeGroup(0)}]; EXPECT_EQ(adjusted_contacts_rate, 2); -} +} \ No newline at end of file diff --git a/cpp/tests/test_abm_model.cpp b/cpp/tests/test_abm_model.cpp index dbf398632a..de0a3015e7 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -691,6 +691,8 @@ TEST_F(TestModel, checkParameterConstraints) params.get()[mio::abm::MaskType::Community] = 0.5; params.get()[mio::abm::MaskType::FFP2] = 0.6; params.get()[mio::abm::MaskType::Surgical] = 0.7; + params.get() = 0.5; + params.get() = mio::abm::days(14); params.get() = mio::abm::TimePoint(0); ASSERT_EQ(params.check_constraints(), false); @@ -789,6 +791,11 @@ TEST_F(TestModel, checkParameterConstraints) EXPECT_TRUE(params.check_constraints()); params.get()[mio::abm::MaskType::Surgical] = 0.7; + params.get() = -0.1; + EXPECT_TRUE(params.check_constraints()); + params.get() = 1.5; + EXPECT_TRUE(params.check_constraints()); + params.get() = mio::abm::TimePoint(-2); EXPECT_TRUE(params.check_constraints()); mio::set_log_level(mio::LogLevel::warn); diff --git a/pycode/memilio-surrogatemodel/memilio/surrogatemodel_test/test_surrogatemodel_ode_secir_groups.py b/pycode/memilio-surrogatemodel/memilio/surrogatemodel_test/test_surrogatemodel_ode_secir_groups.py index 80f9c0f90d..1d10b6167a 100644 --- a/pycode/memilio-surrogatemodel/memilio/surrogatemodel_test/test_surrogatemodel_ode_secir_groups.py +++ b/pycode/memilio-surrogatemodel/memilio/surrogatemodel_test/test_surrogatemodel_ode_secir_groups.py @@ -505,6 +505,7 @@ def test_network_fit(self, mock_population, mock_baseline, mock_minimum): model.network_fit( path=self.path, model=md, modeltype='classic', training_parameter=training_parameter, filename="data_secir_groups.pickle") + # check error message error_message_part1 = "[Errno 2] No such file or directory" error_message_part2 = self.path