diff --git a/cpp/benchmarks/abm.cpp b/cpp/benchmarks/abm.cpp index e5d7c75216..27c84a65d7 100644 --- a/cpp/benchmarks/abm.cpp +++ b/cpp/benchmarks/abm.cpp @@ -1,5 +1,5 @@ #include "abm/simulation.h" -#include "memilio/utils/stl_util.h" + #include "benchmark/benchmark.h" mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list seeds) @@ -23,10 +23,10 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list::get_instance()( + auto age = mio::AgeGroup(mio::UniformIntDistribution::get_instance()( world.get_rng(), size_t(0), world.parameters.get_num_groups() - 1)); - auto& person = world.add_person(home, age); - person.set_assigned_location(home); + auto person = world.add_person(home, age); + world.get_person(person).set_assigned_location(home); home_size++; } @@ -49,7 +49,7 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list::get_instance()(prng, 0.0, 1.0) < pct_infected) { @@ -114,7 +114,7 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list seeds) { diff --git a/cpp/examples/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index 1c9bd0c07c..81706ddf7c 100644 --- a/cpp/examples/abm_history_object.cpp +++ b/cpp/examples/abm_history_object.cpp @@ -17,16 +17,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "abm/abm.h" #include "abm/household.h" -#include +#include "abm/lockdown_rules.h" +#include "abm/simulation.h" #include "abm/world.h" -#include "memilio/io/io.h" #include "abm/location_type.h" +#include "memilio/io/history.h" + #include #include -#include -#include "memilio/io/history.h" std::string convert_loc_id_to_string(std::tuple tuple_id) { @@ -78,7 +77,6 @@ int main() // 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); - // There are 3 households for each household group. int n_households = 3; @@ -110,21 +108,21 @@ int main() // Add one social event with 5 maximum contacts. // Maximum contacs limit the number of people that a person can infect while being at this location. auto event = world.add_location(mio::abm::LocationType::SocialEvent); - world.get_individualized_location(event).get_infection_parameters().set(5); + world.get_location(event).get_infection_parameters().set(5); // Add hospital and ICU with 5 maximum contacs. auto hospital = world.add_location(mio::abm::LocationType::Hospital); - world.get_individualized_location(hospital).get_infection_parameters().set(5); + world.get_location(hospital).get_infection_parameters().set(5); auto icu = world.add_location(mio::abm::LocationType::ICU); - world.get_individualized_location(icu).get_infection_parameters().set(5); + world.get_location(icu).get_infection_parameters().set(5); // Add one supermarket, maximum constacts are assumed to be 20. auto shop = world.add_location(mio::abm::LocationType::BasicsShop); - world.get_individualized_location(shop).get_infection_parameters().set(20); + world.get_location(shop).get_infection_parameters().set(20); // At every school, the maximum contacts are 20. auto school = world.add_location(mio::abm::LocationType::School); - world.get_individualized_location(school).get_infection_parameters().set(20); + world.get_location(school).get_infection_parameters().set(20); // At every workplace, maximum contacts are 10. auto work = world.add_location(mio::abm::LocationType::Work); - world.get_individualized_location(work).get_infection_parameters().set(10); + world.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); @@ -141,7 +139,7 @@ int main() // The infection states are chosen randomly. auto persons = world.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::Person::RandomNumberGenerator(world.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(world.get_rng(), person); mio::abm::InfectionState infection_state = (mio::abm::InfectionState)(rand() % ((uint32_t)mio::abm::InfectionState::Count - 1)); if (infection_state != mio::abm::InfectionState::Susceptible) @@ -150,19 +148,20 @@ int main() } // Assign locations to the people - for (auto& person : persons) { + for (auto& person : world.get_persons()) { + const auto pid = person.get_id(); //assign shop and event - person.set_assigned_location(event); - person.set_assigned_location(shop); + world.assign_location(pid, event); + world.assign_location(pid, shop); //assign hospital and ICU - person.set_assigned_location(hospital); - person.set_assigned_location(icu); + world.assign_location(pid, hospital); + world.assign_location(pid, icu); //assign work/school to people depending on their age if (person.get_age() == age_group_5_to_14) { - person.set_assigned_location(school); + world.assign_location(pid, school); } if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { - person.set_assigned_location(work); + world.assign_location(pid, work); } } @@ -187,7 +186,7 @@ int main() { Type location_ids{}; for (auto& location : sim.get_world().get_locations()) { - location_ids.push_back(std::make_tuple(location.get_type(), location.get_index())); + location_ids.push_back(std::make_tuple(location.get_type(), location.get_id().get())); } return location_ids; } diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 81be666e75..b9b3bd2b43 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -17,13 +17,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "abm/abm.h" #include "abm/household.h" -#include -#include -#include +#include "abm/lockdown_rules.h" +#include "abm/world.h" #include "abm/common_abm_loggers.h" +#include + int main() { // This is a minimal example with children and adults < 60 year old. @@ -79,26 +79,26 @@ int main() // Add one social event with 5 maximum contacts. // Maximum contacs limit the number of people that a person can infect while being at this location. auto event = world.add_location(mio::abm::LocationType::SocialEvent); - world.get_individualized_location(event).get_infection_parameters().set(5); + world.get_location(event).get_infection_parameters().set(5); // Add hospital and ICU with 5 maximum contacs. auto hospital = world.add_location(mio::abm::LocationType::Hospital); - world.get_individualized_location(hospital).get_infection_parameters().set(5); + world.get_location(hospital).get_infection_parameters().set(5); auto icu = world.add_location(mio::abm::LocationType::ICU); - world.get_individualized_location(icu).get_infection_parameters().set(5); + world.get_location(icu).get_infection_parameters().set(5); // Add one supermarket, maximum constacts are assumed to be 20. auto shop = world.add_location(mio::abm::LocationType::BasicsShop); - world.get_individualized_location(shop).get_infection_parameters().set(20); + world.get_location(shop).get_infection_parameters().set(20); // At every school, the maximum contacts are 20. auto school = world.add_location(mio::abm::LocationType::School); - world.get_individualized_location(school).get_infection_parameters().set(20); + world.get_location(school).get_infection_parameters().set(20); // At every workplace, maximum contacts are 20. auto work = world.add_location(mio::abm::LocationType::Work); - world.get_individualized_location(work).get_infection_parameters().set(20); + world.get_location(work).get_infection_parameters().set(20); // Increase aerosol transmission for all locations world.parameters.get() = 10.0; // Increase contact rate for all people between 15 and 34 (i.e. people meet more often in the same location) - world.get_individualized_location(work) + world.get_location(work) .get_infection_parameters() .get()[{age_group_15_to_34, age_group_15_to_34}] = 10.0; @@ -119,7 +119,7 @@ int main() for (auto& person : world.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), infection_distribution)); - auto rng = mio::abm::Person::RandomNumberGenerator(world.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(world.get_rng(), person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), world.parameters, start_date, infection_state)); @@ -128,18 +128,19 @@ int main() // Assign locations to the people for (auto& person : world.get_persons()) { + const auto id = person.get_id(); //assign shop and event - person.set_assigned_location(event); - person.set_assigned_location(shop); + world.assign_location(id, event); + world.assign_location(id, shop); //assign hospital and ICU - person.set_assigned_location(hospital); - person.set_assigned_location(icu); + world.assign_location(id, hospital); + world.assign_location(id, icu); //assign work/school to people depending on their age if (person.get_age() == age_group_0_to_4) { - person.set_assigned_location(school); + world.assign_location(id, school); } if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { - person.set_assigned_location(work); + world.assign_location(id, work); } } diff --git a/cpp/memilio/utils/custom_index_array.h b/cpp/memilio/utils/custom_index_array.h index 06e38b117a..1738f6298f 100644 --- a/cpp/memilio/utils/custom_index_array.h +++ b/cpp/memilio/utils/custom_index_array.h @@ -20,16 +20,10 @@ #ifndef CUSTOMINDEXARRAY_H #define CUSTOMINDEXARRAY_H -#include "memilio/config.h" -#include "memilio/math/eigen.h" #include "memilio/math/eigen_util.h" #include "memilio/utils/index.h" #include "memilio/utils/stl_util.h" -#include -#include -#include - namespace { @@ -165,6 +159,14 @@ class CustomIndexArray using Index = ::mio::Index; using InternalArrayType = Eigen::Array; + /// @brief Create an empty CustomIndexArray with size 0. Use the resize member function to add entries. + explicit CustomIndexArray() + : m_dimensions(Index::Zero()) + , m_numel(0) + , m_y() + { + } + /** * @brief CustomIndexArray constructor, that initializes the array * to constant instances of `CustsomIndexArray::Type`. diff --git a/cpp/memilio/utils/random_number_generator.cpp b/cpp/memilio/utils/random_number_generator.cpp index 1d97031845..6e71eae776 100644 --- a/cpp/memilio/utils/random_number_generator.cpp +++ b/cpp/memilio/utils/random_number_generator.cpp @@ -21,7 +21,7 @@ namespace mio { - + RandomNumberGenerator& thread_local_rng() { static thread_local auto rng = RandomNumberGenerator(); diff --git a/cpp/memilio/utils/random_number_generator.h b/cpp/memilio/utils/random_number_generator.h index c5883e7465..2ab4cadd48 100644 --- a/cpp/memilio/utils/random_number_generator.h +++ b/cpp/memilio/utils/random_number_generator.h @@ -18,8 +18,8 @@ * limitations under the License. */ -#ifndef EPI_ABM_RANDOM_NUMBER_GENERATOR_H -#define EPI_ABM_RANDOM_NUMBER_GENERATOR_H +#ifndef MIO_RANDOM_NUMBER_GENERATOR_H +#define MIO_RANDOM_NUMBER_GENERATOR_H #include "memilio/utils/compiler_diagnostics.h" #include "memilio/utils/logging.h" @@ -182,7 +182,9 @@ static_assert(sizeof(Key) == sizeof(uint32_t), "Empty Base Optimizatio * @tparam an unsigned integer type that determines the size of the counter, i.e., the length of the random sequence. */ template -struct MEMILIO_ENABLE_EBO Counter : TypeSafe>, OperatorComparison>, OperatorAdditionSubtraction> { +struct MEMILIO_ENABLE_EBO Counter : TypeSafe>, + OperatorComparison>, + OperatorAdditionSubtraction> { static_assert(std::is_unsigned::value, "Underlying Integer type must be unsigned."); using TypeSafe>::TypeSafe; }; @@ -243,12 +245,13 @@ Counter rng_totalsequence_counter(UIntN subsequence_idx, CounterS counter static_assert(N_BITS <= C_BITS, "Subsequence index must not be bigger than total sequence counter."); static_assert(N_BITS <= sizeof(UIntN) * BITS_PER_BYTE, "Subsequence index must be at least N bits"); - assert(UIntC(subsequence_idx) <= (UIntC(1) << N_BITS) && "Subsequence index is too large."); //(1 << N) is the same as (2^N) + assert(UIntC(subsequence_idx) <= (UIntC(1) << N_BITS) && + "Subsequence index is too large."); //(1 << N) is the same as (2^N) //N high bits: subsequence idx //S low bits: subsequence counter //=> C = N + S bits: total sequence counter - //example: + //example: //subsequence index uint32_t(181) = 0x000000B5 //subsequence counter uint32_t(41309) = 0x0000A15D //total sequence counter = 0x000000B50000A15D @@ -271,7 +274,7 @@ Counter rng_totalsequence_counter(UIntN subsequence_idx, CounterS counter template Counter rng_subsequence_counter(CounterC counter) { - using UIntC = typename CounterC::ValueType; + using UIntC = typename CounterC::ValueType; static const UIntC C_BYTES = sizeof(UIntC); static const UIntC S_BYTES = sizeof(UIntS); diff --git a/cpp/models/abm/CMakeLists.txt b/cpp/models/abm/CMakeLists.txt index 60cb0b1ed9..d83f510dd9 100644 --- a/cpp/models/abm/CMakeLists.txt +++ b/cpp/models/abm/CMakeLists.txt @@ -1,12 +1,16 @@ add_library(abm location.cpp location.h + location_id.h household.cpp household.h simulation.cpp simulation.h person.cpp person.h + person_id.h + personal_rng.cpp + personal_rng.h testing_strategy.cpp testing_strategy.h world.cpp @@ -16,6 +20,8 @@ add_library(abm parameters.cpp migration_rules.cpp migration_rules.h + model_functions.cpp + model_functions.h trip_list.cpp trip_list.h lockdown_rules.cpp diff --git a/cpp/models/abm/analyze_result.h b/cpp/models/abm/analyze_result.h index 0e93be84a9..05107af313 100644 --- a/cpp/models/abm/analyze_result.h +++ b/cpp/models/abm/analyze_result.h @@ -20,11 +20,8 @@ #ifndef ABM_ANALYZE_RESULT_H #define ABM_ANALYZE_RESULT_H -#include "abm/simulation.h" #include "abm/parameters.h" -#include "memilio/data/analyze_result.h" -#include #include namespace mio diff --git a/cpp/models/abm/common_abm_loggers.h b/cpp/models/abm/common_abm_loggers.h index c5029be6ea..5c8540a579 100644 --- a/cpp/models/abm/common_abm_loggers.h +++ b/cpp/models/abm/common_abm_loggers.h @@ -21,11 +21,15 @@ #ifndef ABM_COMMON_LOGGERS_H #define ABM_COMMON_LOGGERS_H +#include "abm/infection_state.h" +#include "abm/person_id.h" +#include "abm/simulation.h" #include "memilio/io/history.h" +#include "memilio/utils/time_series.h" #include "models/abm/location_type.h" #include "abm/movement_data.h" -#include "abm/abm.h" #include "memilio/utils/mioomp.h" + namespace mio { namespace abm @@ -47,7 +51,7 @@ struct movement_data { mio::abm::InfectionState infection_state; }; -mio::abm::ActivityType guess_activity_type(mio::abm::LocationType current_location) +constexpr mio::abm::ActivityType guess_activity_type(mio::abm::LocationType current_location) { switch (current_location) { case mio::abm::LocationType::Home: @@ -75,7 +79,8 @@ mio::abm::ActivityType guess_activity_type(mio::abm::LocationType current_locati * @brief Logger to log the LocationInformation of the simulation. */ struct LogLocationInformation : mio::LogOnce { - using Type = std::vector>; + using Type = std::vector< + std::tuple>; /** * @brief Log the LocationInformation of the simulation. * @param[in] sim The simulation of the abm. @@ -89,15 +94,14 @@ struct LogLocationInformation : mio::LogOnce { static Type log(const mio::abm::Simulation& sim) { Type location_information{}; - for (auto&& location : sim.get_world().get_locations()) { + for (auto& location : sim.get_world().get_locations()) { auto n_cells = location.get_cells().size(); int loc_capacity = 0; for (int i = 0; i < (int)n_cells; i++) { loc_capacity += location.get_capacity(i).persons; } - location_information.push_back(std::make_tuple(location.get_index(), location.get_type(), - location.get_geographical_location(), n_cells, - loc_capacity)); + location_information.push_back(std::make_tuple( + location.get_id(), location.get_type(), location.get_geographical_location(), n_cells, loc_capacity)); } return location_information; } @@ -107,7 +111,7 @@ struct LogLocationInformation : mio::LogOnce { * @brief Logger to log the Person%s Information in the simulation. */ struct LogPersonInformation : mio::LogOnce { - using Type = std::vector>; + using Type = std::vector>; /** * @brief Log the LocationInformation of the simulation. * @param[in] sim The simulation of the abm. @@ -119,9 +123,10 @@ struct LogPersonInformation : mio::LogOnce { static Type log(const mio::abm::Simulation& sim) { Type person_information{}; - for (auto&& person : sim.get_world().get_persons()) { + person_information.reserve(sim.get_world().get_persons().size()); + for (auto& person : sim.get_world().get_persons()) { person_information.push_back(std::make_tuple( - person.get_person_id(), sim.get_world().find_location(mio::abm::LocationType::Home, person).get_index(), + person.get_id(), sim.get_world().find_location(mio::abm::LocationType::Home, person.get_id()), person.get_age())); } return person_information; @@ -132,8 +137,8 @@ struct LogPersonInformation : mio::LogOnce { * @brief Logger to log Movement Data of the agents in the simulation. */ struct LogDataForMovement : mio::LogAlways { - using Type = std::vector>; + using Type = std::vector>; /** * @brief Log the Movement Data of the agents in the simulation. * @param[in] sim The simulation of the abm. @@ -149,9 +154,9 @@ struct LogDataForMovement : mio::LogAlways { { Type movement_data{}; for (Person p : sim.get_world().get_persons()) { - movement_data.push_back(std::make_tuple( - p.get_person_id(), p.get_location().get_index(), sim.get_time(), p.get_last_transport_mode(), - guess_activity_type(p.get_location().get_type()), p.get_infection_state(sim.get_time()))); + movement_data.push_back( + std::make_tuple(p.get_id(), p.get_location(), sim.get_time(), p.get_last_transport_mode(), + guess_activity_type(p.get_location_type()), p.get_infection_state(sim.get_time()))); } return movement_data; } @@ -173,9 +178,10 @@ struct LogInfectionState : mio::LogAlways { Eigen::VectorXd sum = Eigen::VectorXd::Zero(Eigen::Index(mio::abm::InfectionState::Count)); auto curr_time = sim.get_time(); PRAGMA_OMP(for) - for (auto&& location : sim.get_world().get_locations()) { + for (auto& location : sim.get_world().get_locations()) { for (uint32_t inf_state = 0; inf_state < (int)mio::abm::InfectionState::Count; inf_state++) { - sum[inf_state] += location.get_subpopulation(curr_time, mio::abm::InfectionState(inf_state)); + sum[inf_state] += sim.get_world().get_subpopulation(location.get_id(), curr_time, + mio::abm::InfectionState(inf_state)); } } return std::make_pair(curr_time, sum); diff --git a/cpp/models/abm/household.cpp b/cpp/models/abm/household.cpp index 78c85ceeca..c6afa00399 100755 --- a/cpp/models/abm/household.cpp +++ b/cpp/models/abm/household.cpp @@ -19,11 +19,9 @@ */ #include "abm/household.h" -#include "abm/person.h" +#include "abm/person_id.h" #include "abm/location.h" -#include "memilio/math/eigen.h" #include "memilio/utils/random_number_generator.h" -#include namespace mio { @@ -62,9 +60,8 @@ void add_household_to_world(World& world, const Household& household) { auto home = world.add_location(LocationType::Home); auto members = household.get_members(); - world.get_individualized_location(home).set_capacity(household.get_total_number_of_members(), - household.get_total_number_of_members() * - household.get_space_per_member()); + world.get_location(home).set_capacity(household.get_total_number_of_members(), + household.get_total_number_of_members() * household.get_space_per_member()); for (auto& memberTouple : members) { int count; @@ -72,8 +69,8 @@ void add_household_to_world(World& world, const Household& household) std::tie(member, count) = memberTouple; for (int j = 0; j < count; j++) { auto age_group = pick_age_group_from_age_distribution(world.get_rng(), member.get_age_weights()); - auto& person = world.add_person(home, age_group); - person.set_assigned_location(home); + auto person = world.add_person(home, age_group); + world.assign_location(person, home); } } } diff --git a/cpp/models/abm/household.h b/cpp/models/abm/household.h index 06b7d0a7c2..67af6de67a 100644 --- a/cpp/models/abm/household.h +++ b/cpp/models/abm/household.h @@ -18,8 +18,8 @@ * limitations under the License. */ -#ifndef EPI_ABM_HOUSEHOLD_H -#define EPI_ABM_HOUSEHOLD_H +#ifndef MIO_ABM_HOUSEHOLD_H +#define MIO_ABM_HOUSEHOLD_H #include "abm/world.h" #include "memilio/epidemiology/age_group.h" @@ -212,4 +212,4 @@ void add_household_group_to_world(World& world, const HouseholdGroup& household_ } // namespace abm } // namespace mio -#endif //EPI_ABM_HOUSEHOLD_H +#endif //MIO_ABM_HOUSEHOLD_H diff --git a/cpp/models/abm/infection.cpp b/cpp/models/abm/infection.cpp index a6c9ffff71..b232dd7747 100644 --- a/cpp/models/abm/infection.cpp +++ b/cpp/models/abm/infection.cpp @@ -26,7 +26,7 @@ namespace mio namespace abm { -Infection::Infection(Person::RandomNumberGenerator& rng, VirusVariant virus, AgeGroup age, const Parameters& params, +Infection::Infection(PersonalRandomNumberGenerator& rng, VirusVariant virus, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState init_state, std::pair latest_exposure, bool detected) : m_virus_variant(virus) @@ -112,7 +112,7 @@ TimePoint Infection::get_start_date() const return m_viral_load.start_date; } -TimePoint Infection::draw_infection_course(Person::RandomNumberGenerator& rng, AgeGroup age, const Parameters& params, +TimePoint Infection::draw_infection_course(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState init_state, std::pair latest_protection) { @@ -122,7 +122,7 @@ TimePoint Infection::draw_infection_course(Person::RandomNumberGenerator& rng, A return start_date; } -void Infection::draw_infection_course_forward(Person::RandomNumberGenerator& rng, AgeGroup age, +void Infection::draw_infection_course_forward(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState start_state, std::pair latest_exposure) { @@ -210,7 +210,7 @@ void Infection::draw_infection_course_forward(Person::RandomNumberGenerator& rng } } -TimePoint Infection::draw_infection_course_backward(Person::RandomNumberGenerator& rng, AgeGroup age, +TimePoint Infection::draw_infection_course_backward(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState init_state) { diff --git a/cpp/models/abm/infection.h b/cpp/models/abm/infection.h index f882d22af2..ca9dae9bdd 100644 --- a/cpp/models/abm/infection.h +++ b/cpp/models/abm/infection.h @@ -17,14 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_INFECTION_H -#define EPI_ABM_INFECTION_H +#ifndef MIO_ABM_INFECTION_H +#define MIO_ABM_INFECTION_H +#include "abm/personal_rng.h" #include "abm/time.h" #include "abm/infection_state.h" #include "abm/virus_variant.h" #include "abm/parameters.h" -#include "abm/person.h" #include @@ -53,7 +53,7 @@ class Infection /** * @brief Create an Infection for a single Person. * Draws a random infection course. - * @param[inout] rng Person::RandomNumberGenerator for the Person. + * @param[inout] rng PersonalRandomNumberGenerator of the Person. * @param[in] virus Virus type of the Infection. * @param[in] age AgeGroup to determine the ViralLoad course. * @param[in] params Parameters of the Model. @@ -62,7 +62,7 @@ class Infection * @param[in] latest_exposure [Default: {ExposureType::NoProtection, TimePoint(0)}] The pair value of last ExposureType (previous Infection/Vaccination) and TimePoint of that protection. * @param[in] detected [Default: false] If the Infection is detected. */ - Infection(Person::RandomNumberGenerator& rng, VirusVariant virus, AgeGroup age, const Parameters& params, + Infection(PersonalRandomNumberGenerator& rng, VirusVariant virus, AgeGroup age, const Parameters& params, TimePoint start_date, InfectionState start_state = InfectionState::Exposed, std::pair latest_exposure = {ExposureType::NoProtection, TimePoint(0)}, bool detected = false); @@ -119,39 +119,39 @@ class Infection * @brief Determine ViralLoad course and Infection course based on init_state. * Calls draw_infection_course_backward for all #InfectionState%s prior and draw_infection_course_forward for all * subsequent #InfectionState%s. - * @param[inout] rng Person::RandomNumberGenerator of the Person. + * @param[inout] rng PersonalRandomNumberGenerator of the Person. * @param[in] age AgeGroup of the Person. * @param[in] params Parameters of the Model. * @param[in] init_date Date of initializing the Infection. * @param[in] init_state #InfectionState at time of initializing the Infection. * @return The starting date of the Infection. */ - TimePoint draw_infection_course(Person::RandomNumberGenerator& rng, AgeGroup age, const Parameters& params, + TimePoint draw_infection_course(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState start_state, std::pair latest_protection); /** * @brief Determine ViralLoad course and Infection course prior to the given start_state. - * @param[inout] rng Person::RandomNumberGenerator of the Person. + * @param[inout] rng PersonalRandomNumberGenerator of the Person. * @param[in] age AgeGroup of the Person. * @param[in] params Parameters of the Model. * @param[in] init_date Date of initializing the Infection. * @param[in] init_state #InfectionState at time of initializing the Infection. */ - void draw_infection_course_forward(Person::RandomNumberGenerator& rng, AgeGroup age, const Parameters& params, + void draw_infection_course_forward(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState start_state, std::pair latest_protection); /** * @brief Determine ViralLoad course and Infection course subsequent to the given start_state. - * @param[inout] rng Person::RandomNumberGenerator of the Person. + * @param[inout] rng PersonalRandomNumberGenerator of the Person. * @param[in] age AgeGroup of the person. * @param[in] params Parameters of the Model. * @param[in] init_date Date of initializing the Infection. * @param[in] init_state InfectionState at time of initializing the Infection. * @return The starting date of the Infection. */ - TimePoint draw_infection_course_backward(Person::RandomNumberGenerator& rng, AgeGroup age, const Parameters& params, + TimePoint draw_infection_course_backward(PersonalRandomNumberGenerator& rng, AgeGroup age, const Parameters& params, TimePoint init_date, InfectionState init_state); std::vector> m_infection_course; ///< Start date of each #InfectionState. diff --git a/cpp/models/abm/infection_state.h b/cpp/models/abm/infection_state.h index eb32ab984c..284602dcdb 100644 --- a/cpp/models/abm/infection_state.h +++ b/cpp/models/abm/infection_state.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_INFECTION_STATE_H -#define EPI_ABM_INFECTION_STATE_H +#ifndef MIO_ABM_INFECTION_STATE_H +#define MIO_ABM_INFECTION_STATE_H #include diff --git a/cpp/models/abm/location.cpp b/cpp/models/abm/location.cpp index c1ff912720..81d803471e 100644 --- a/cpp/models/abm/location.cpp +++ b/cpp/models/abm/location.cpp @@ -17,147 +17,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/location_type.h" #include "abm/mask_type.h" -#include "abm/mask.h" #include "abm/location.h" #include "abm/random_events.h" -#include "abm/infection.h" -#include "memilio/utils/random_number_generator.h" -#include -#include namespace mio { namespace abm { -Location::Location(LocationId loc_id, size_t num_agegroups, uint32_t num_cells) - : m_id(loc_id) - , m_capacity_adapted_transmission_risk(false) +Location::Location(LocationType loc_type, LocationId loc_id, size_t num_agegroups, uint32_t num_cells) + : m_type(loc_type) + , m_id(loc_id) , m_parameters(num_agegroups) - , m_cells(num_cells, num_agegroups) + , m_cells(num_cells) , m_required_mask(MaskType::Community) , m_npi_active(false) { assert(num_cells > 0 && "Number of cells has to be larger than 0."); } -Location Location::copy_location_without_persons(size_t num_agegroups) -{ - Location copy_loc = Location(*this); - copy_loc.m_persons = std::vector>(); - copy_loc.m_cells = std::vector{num_agegroups}; - for (uint32_t idx = 0; idx < m_cells.size(); idx++) { - copy_loc.set_capacity(get_capacity(idx).persons, get_capacity(idx).volume, idx); - copy_loc.get_cached_exposure_rate_contacts(idx) = get_cached_exposure_rate_contacts(idx); - copy_loc.get_cached_exposure_rate_air(idx) = get_cached_exposure_rate_air(idx); - } - return copy_loc; -} - -ScalarType Location::transmission_contacts_per_day(uint32_t cell_index, VirusVariant virus, AgeGroup age_receiver, - size_t num_agegroups) const -{ - assert(age_receiver.get() < num_agegroups); - ScalarType prob = 0; - for (uint32_t age_transmitter = 0; age_transmitter != num_agegroups; ++age_transmitter) { - prob += m_cells[cell_index].m_cached_exposure_rate_contacts[{virus, static_cast(age_transmitter)}] * - m_parameters.get()[{age_receiver, static_cast(age_transmitter)}]; - } - return prob; -} - -ScalarType Location::transmission_air_per_day(uint32_t cell_index, VirusVariant virus, - const Parameters& global_params) const -{ - return m_cells[cell_index].m_cached_exposure_rate_air[{virus}] * - global_params.get()[{virus}]; -} - -void Location::interact(Person::RandomNumberGenerator& rng, Person& person, TimePoint t, TimeSpan dt, - const Parameters& global_params) const -{ - // TODO: we need to define what a cell is used for, as the loop may lead to incorrect results for multiple cells - auto age_receiver = person.get_age(); - ScalarType mask_protection = person.get_mask_protective_factor(global_params); - assert(person.get_cells().size() && "Person is in multiple cells. Interact logic is incorrect at the moment."); - for (auto cell_index : - person.get_cells()) { // TODO: the logic here is incorrect in case a person is in multiple cells - std::pair local_indiv_trans_prob[static_cast(VirusVariant::Count)]; - for (uint32_t v = 0; v != static_cast(VirusVariant::Count); ++v) { - VirusVariant virus = static_cast(v); - ScalarType local_indiv_trans_prob_v = - (std::min( - m_parameters.get(), - transmission_contacts_per_day(cell_index, virus, age_receiver, global_params.get_num_groups())) + - transmission_air_per_day(cell_index, virus, global_params)) * - (1 - mask_protection) * dt.days() * (1 - person.get_protection_factor(t, virus, global_params)); - - local_indiv_trans_prob[v] = std::make_pair(virus, local_indiv_trans_prob_v); - } - VirusVariant virus = - random_transition(rng, VirusVariant::Count, dt, - local_indiv_trans_prob); // use VirusVariant::Count for no virus submission - if (virus != VirusVariant::Count) { - person.add_new_infection(Infection(rng, virus, age_receiver, global_params, t + dt / 2, - mio::abm::InfectionState::Exposed, person.get_latest_protection(), - false)); // Starting time in first approximation - } - } -} - -void Location::cache_exposure_rates(TimePoint t, TimeSpan dt, size_t num_agegroups) -{ - //cache for next step so it stays constant during the step while subpopulations change - //otherwise we would have to cache all state changes during a step which uses more memory - for (auto& cell : m_cells) { - cell.m_cached_exposure_rate_contacts = {{VirusVariant::Count, AgeGroup(num_agegroups)}, 0.}; - cell.m_cached_exposure_rate_air = {{VirusVariant::Count}, 0.}; - for (auto&& p : cell.m_persons) { - if (p->is_infected(t)) { - auto& inf = p->get_infection(); - auto virus = inf.get_virus_variant(); - auto age = p->get_age(); - /* average infectivity over the time step - * to second order accuracy using midpoint rule - */ - cell.m_cached_exposure_rate_contacts[{virus, age}] += inf.get_infectivity(t + dt / 2); - cell.m_cached_exposure_rate_air[{virus}] += inf.get_infectivity(t + dt / 2); - } - } - if (m_capacity_adapted_transmission_risk) { - cell.m_cached_exposure_rate_air.array() *= cell.compute_space_per_person_relative(); - } - } -} - -void Location::add_person(Person& p, std::vector cells) -{ - std::lock_guard lk(m_mut); - m_persons.push_back(&p); - for (uint32_t cell_idx : cells) - m_cells[cell_idx].m_persons.push_back(&p); -} - -void Location::remove_person(Person& p) -{ - std::lock_guard lk(m_mut); - m_persons.erase(std::remove(m_persons.begin(), m_persons.end(), &p), m_persons.end()); - for (auto&& cell : m_cells) { - cell.m_persons.erase(std::remove(cell.m_persons.begin(), cell.m_persons.end(), &p), cell.m_persons.end()); - } -} - -size_t Location::get_number_persons() const -{ - return m_persons.size(); -} - /* For every cell in a location we have a transmission factor that is nomalized to m_capacity.volume / m_capacity.persons of the location "Home", which is 66. We multiply this rate with the individual size of each cell to obtain a "space per person" factor. */ -ScalarType Cell::compute_space_per_person_relative() +ScalarType Cell::compute_space_per_person_relative() const { if (m_capacity.volume != 0) { return 66.0 / m_capacity.volume; @@ -167,19 +52,5 @@ ScalarType Cell::compute_space_per_person_relative() } } -size_t Cell::get_subpopulation(TimePoint t, InfectionState state) const -{ - return count_if(m_persons.begin(), m_persons.end(), [&](observer_ptr p) { - return p->get_infection_state(t) == state; - }); -} - -size_t Location::get_subpopulation(TimePoint t, InfectionState state) const -{ - return count_if(m_persons.begin(), m_persons.end(), [&](observer_ptr p) { - return p->get_infection_state(t) == state; - }); -} - } // namespace abm } // namespace mio diff --git a/cpp/models/abm/location.h b/cpp/models/abm/location.h index 1c8f2d027c..921cca01a0 100644 --- a/cpp/models/abm/location.h +++ b/cpp/models/abm/location.h @@ -17,29 +17,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_LOCATION_H -#define EPI_ABM_LOCATION_H +#ifndef MIO_ABM_LOCATION_H +#define MIO_ABM_LOCATION_H -#include "abm/person.h" +#include "abm/location_id.h" #include "abm/mask_type.h" #include "abm/parameters.h" #include "abm/location_type.h" -#include "abm/infection_state.h" -#include "abm/vaccine.h" -#include "memilio/epidemiology/age_group.h" -#include "memilio/math/eigen.h" -#include "memilio/utils/custom_index_array.h" -#include "memilio/utils/time_series.h" -#include "memilio/utils/memory.h" -#include -#include -#include + +#include "boost/atomic/atomic.hpp" namespace mio { namespace abm { -class Person; + +struct GeographicalLocation { + double latitude; + double longitude; + + /** + * @brief Compare two GeographicalLocation%s. + */ + bool operator==(const GeographicalLocation& other) const + { + return (latitude == other.latitude && longitude == other.longitude); + } + + bool operator!=(const GeographicalLocation& other) const + { + return !(latitude == other.latitude && longitude == other.longitude); + } +}; + +struct CellIndex : public mio::Index { + CellIndex(size_t i) + : mio::Index(i) + { + } +}; + +using ContactExposureRates = CustomIndexArray, CellIndex, VirusVariant, AgeGroup>; +using AirExposureRates = CustomIndexArray, CellIndex, VirusVariant>; /** * @brief CellCapacity describes the size of a Cell. @@ -61,33 +80,13 @@ struct CellCapacity { * This allows a finer division of the people at the Location. */ struct Cell { - std::vector> m_persons; - CustomIndexArray m_cached_exposure_rate_contacts; - CustomIndexArray m_cached_exposure_rate_air; CellCapacity m_capacity; - Cell(size_t num_agegroups, std::vector> persons = {}) - : m_persons(std::move(persons)) - , m_cached_exposure_rate_contacts({{VirusVariant::Count, AgeGroup(num_agegroups)}, 0.}) - , m_cached_exposure_rate_air({{VirusVariant::Count}, 0.}) - , m_capacity() - { - } - /** * @brief Computes a relative cell size for the Cell. * @return The relative cell size for the Cell. */ - ScalarType compute_space_per_person_relative(); - - /** - * @brief Get subpopulation of a particular #InfectionState in the Cell. - * @param[in] t TimePoint of querry. - * @param[in] state #InfectionState of interest. - * @return Amount of Person%s of the #InfectionState in the Cell. - */ - size_t get_subpopulation(TimePoint t, InfectionState state) const; - + ScalarType compute_space_per_person_relative() const; }; // namespace mio /** @@ -96,48 +95,26 @@ struct Cell { class Location { public: - /** - * @brief Construct a Location of a certain LocationId. - * @param[in] loc_id The #LocationId. - * @param[in] num_agegroups [Default: 1] The number of age groups in the model. - * @param[in] num_cells [Default: 1] The number of Cell%s in which the Location is divided. - */ - Location(LocationId loc_id, size_t num_agegroups = 1, uint32_t num_cells = 1); - /** * @brief Construct a Location with provided parameters. * @param[in] loc_type The #LocationType. - * @param[in] index The index of the Location. + * @param[in] loc_id The index of the Location in the World. * @param[in] num_agegroups [Default: 1] The number of age groups in the model. * @param[in] num_cells [Default: 1] The number of Cell%s in which the Location is divided. */ - Location(LocationType loc_type, uint32_t loc_index, size_t num_agegroups = 1, uint32_t num_cells = 1) - : Location(LocationId{loc_index, loc_type}, num_agegroups, num_cells) - { - } + explicit Location(LocationType loc_type, LocationId loc_id, size_t num_agegroups = 1, uint32_t num_cells = 1); /** - * @brief Return a copy of the current Location object. - * @param[in] other The original #Location. + * @brief Construct a copy of a Location with a new ID. + * @param[in] other The Location to copy from. + * @param[in] id The ID for the new Location. */ - Location(const Location& other) - : m_id(other.m_id) - , m_capacity_adapted_transmission_risk(other.m_capacity_adapted_transmission_risk) - , m_parameters(other.m_parameters) - , m_persons(other.m_persons) - , m_cells(other.m_cells) - , m_required_mask(other.m_required_mask) - , m_npi_active(other.m_npi_active) - , m_geographical_location(other.m_geographical_location) + explicit Location(const Location& other, LocationId id) + : Location(other) { + m_id = id; } - /** - * @brief Return a copy of this #Location object with an empty m_persons. - * @param[in] num_agegroups The number of age groups in the model. - */ - Location copy_location_without_persons(size_t num_agegroups); - /** * @brief Compare two Location%s. */ @@ -157,73 +134,22 @@ class Location */ LocationType get_type() const { - return m_id.type; + return m_type; } /** - * @brief Get the index of this Location. - * @return The index of the Location. + * @brief Get the location's identifier in a World. + * @return The location's LocationId by value. */ - unsigned get_index() const + LocationId get_id() const { - return m_id.index; + return m_id; } - /** - * @brief Compute the transmission factor for contact transmission of the virus in a Cell. - * @param[in] cell_index Cell index of the Cell. - * @param[in] virus VirusVariant of interest. - * @param[in] age_receiver AgeGroup of the receiving Person. - * @param[in] age_transmitter AgeGroup of the transmitting Person. - * @param[in] num_agegroups The number of age groups in the model. - * @return Amount of average Infection%s with the virus from the AgeGroup of the transmitter per day. - */ - ScalarType transmission_contacts_per_day(uint32_t cell_index, VirusVariant virus, AgeGroup age_receiver, - size_t num_agegroups) const; - - /** - * @brief Compute the transmission factor for a aerosol transmission of the virus in a Cell. - * @param[in] cell_index Cell index of the Cell. - * @param[in] virus VirusVariant of interest. - * @param[in] global_params The Parameters set of the World. - * @return Amount of average Infection%s with the virus per day. - */ - ScalarType transmission_air_per_day(uint32_t cell_index, VirusVariant virus, const Parameters& global_params) const; - - /** - * @brief A Person interacts with the population at this Location and may become infected. - * @param[in, out] rng Person::RandomNumberGenerator for this Person. - * @param[in, out] person The Person that interacts with the population. - * @param[in] dt Length of the current Simulation time step. - * @param[in] params Parameters of the Model. - */ - void interact(Person::RandomNumberGenerator& rng, Person& person, TimePoint t, TimeSpan dt, - const Parameters& params) const; - - /** - * @brief Add a Person to the population at this Location. - * @param[in] person The Person arriving. - * @param[in] cell_idx [Default: 0] Index of the Cell the Person shall go to. - */ - void add_person(Person& person, std::vector cells = {0}); - - /** - * @brief Remove a Person from the population of this Location. - * @param[in] person The Person leaving. - */ - void remove_person(Person& person); - - /** - * @brief Prepare the Location for the next Simulation step. - * @param[in] t Current TimePoint of the Simulation. - * @param[in] dt The duration of the Simulation step. - * @param[in] num_agegroups The number of age groups in the model. - */ - void cache_exposure_rates(TimePoint t, TimeSpan dt, size_t num_agegroups); - /** * @brief Get the Location specific Infection parameters. * @return Parameters of the Infection that are specific to this Location. + * @{ */ LocalInfectionParameters& get_infection_parameters() { @@ -234,6 +160,7 @@ class Location { return m_parameters; } + /** @} */ /** * @brief Get the Cell%s of this Location. @@ -262,26 +189,6 @@ class Location m_required_mask = type; } - /** - * @brief Get the contact exposure rate in the Cell. - * @param[in] cell_idx Cell index of interest. - * @return Air exposure rate in the Cell. - */ - CustomIndexArray get_cached_exposure_rate_contacts(uint32_t cell_idx) const - { - return m_cells[cell_idx].m_cached_exposure_rate_contacts; - } - - /** - * @brief Get the air exposure rate in the Cell. - * @param[in] cell_idx Cell index of interest. - * @return Contact exposure rate in the cell. - */ - CustomIndexArray get_cached_exposure_rate_air(uint32_t cell_idx) const - { - return m_cells[cell_idx].m_cached_exposure_rate_air; - } - /** * @brief Set the CellCapacity of a Cell in the Location in persons and volume. * @param[in] persons Maximum number of Person%s that can visit the Cell at the same time. @@ -290,6 +197,7 @@ class Location */ void set_capacity(uint32_t persons, uint32_t volume, uint32_t cell_idx = 0) { + assert(cell_idx < m_cells.size() && "Given cell index is too large."); m_cells[cell_idx].m_capacity.persons = persons; m_cells[cell_idx].m_capacity.volume = volume; } @@ -301,19 +209,10 @@ class Location */ CellCapacity get_capacity(uint32_t cell_idx = 0) const { + assert(cell_idx < m_cells.size() && "Given cell index is too large."); return m_cells[cell_idx].m_capacity; } - /** - * @brief Set the capacity adapted transmission risk flag. - * @param[in] consider_capacity If true considers the capacity of the Cell%s of this Location for the computation of - * relative transmission risk. - */ - void set_capacity_adapted_transmission_risk_flag(bool consider_capacity) - { - m_capacity_adapted_transmission_risk = consider_capacity; - } - /** * @brief Get the information whether NPIs are active at this Location. * If true requires e.g. Mask%s when entering a Location. @@ -341,8 +240,8 @@ class Location void serialize(IOContext& io) const { auto obj = io.create_object("Location"); - obj.add_element("index", m_id.index); - obj.add_element("type", m_id.type); + obj.add_element("index", m_id); + obj.add_element("type", m_type); } /** @@ -353,30 +252,16 @@ class Location static IOResult deserialize(IOContext& io) { auto obj = io.expect_object("Location"); - auto index = obj.expect_element("index", Tag{}); - auto type = obj.expect_element("type", Tag{}); + auto index = obj.expect_element("index", Tag{}); + auto type = obj.expect_element("type", Tag{}); return apply( io, [](auto&& index_, auto&& type_) { - return Location{LocationId{index_, LocationType(type_)}}; + return Location{type_, index_}; }, index, type); } - /** - * @brief Get the total number of Person%s at the Location. - * @return Number of Person%s. - */ - size_t get_number_persons() const; - - /** - * @brief Get the number of Person%s of a particular #InfectionState for all Cell%s. - * @param[in] t TimePoint of querry. - * @param[in] state #InfectionState of interest. - * @return Amount of Person%s of the #InfectionState in all Cell%s. - */ - size_t get_subpopulation(TimePoint t, InfectionState state) const; - /** * @brief Get the geographical location of the Location. * @return The geographical location of the Location. @@ -396,12 +281,9 @@ class Location } private: - std::mutex m_mut; ///< Mutex to protect the list of persons from concurrent modification. - LocationId m_id; ///< Id of the Location including type and index. - bool m_capacity_adapted_transmission_risk; /**< If true considers the LocationCapacity for the computation of the - transmission risk.*/ + LocationType m_type; ///< Type of the Location. + LocationId m_id; ///< Unique identifier for the Location in the World owning it. LocalInfectionParameters m_parameters; ///< Infection parameters for the Location. - std::vector> m_persons{}; ///< A vector of all Person%s at the Location. std::vector m_cells{}; ///< A vector of all Cell%s that the Location is divided in. MaskType m_required_mask; ///< Least secure type of Mask that is needed to enter the Location. bool m_npi_active; ///< If true requires e.g. Mask%s to enter the Location. diff --git a/cpp/models/abm/location_id.h b/cpp/models/abm/location_id.h new file mode 100644 index 0000000000..1914e36e40 --- /dev/null +++ b/cpp/models/abm/location_id.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Rene Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef MIO_ABM_LOCATION_ID_H +#define MIO_ABM_LOCATION_ID_H + +#include "memilio/utils/type_safe.h" +#include + +namespace mio +{ +namespace abm +{ + +/// Unique identifier for a Location within a World. +struct MEMILIO_ENABLE_EBO LocationId : public mio::TypeSafe, + public OperatorComparison { + /// @brief Create an ID. + LocationId(uint32_t id) + : mio::TypeSafe(id) + { + } + + /// @brief Create an invalid ID. + LocationId() + : mio::TypeSafe(std::numeric_limits::max()) + { + } + + /// @brief Value for invalid IDs. + const static LocationId invalid_id() + { + return LocationId(); + } +}; + +} // namespace abm +} // namespace mio + +#endif // MIO_ABM_LOCATION_ID_H diff --git a/cpp/models/abm/location_type.h b/cpp/models/abm/location_type.h index 9b685a9a7a..47eea8405f 100644 --- a/cpp/models/abm/location_type.h +++ b/cpp/models/abm/location_type.h @@ -17,12 +17,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_LOCATION_TYPE_H -#define EPI_ABM_LOCATION_TYPE_H +#ifndef MIO_ABM_LOCATION_TYPE_H +#define MIO_ABM_LOCATION_TYPE_H #include -#include -#include namespace mio { @@ -49,62 +47,7 @@ enum class LocationType : std::uint32_t Count //last! }; -static constexpr uint32_t INVALID_LOCATION_INDEX = std::numeric_limits::max(); - -/** - * LocationId identifies a Location uniquely. It consists of the LocationType of the Location and an Index. - * The index corresponds to the index into the structure m_locations from world, where all Locations are saved. - */ -struct LocationId { - uint32_t index; - LocationType type; - - bool operator==(const LocationId& rhs) const - { - return (index == rhs.index && type == rhs.type); - } - - bool operator!=(const LocationId& rhs) const - { - return !(index == rhs.index && type == rhs.type); - } - - bool operator<(const LocationId& rhs) const - { - if (type == rhs.type) { - return index < rhs.index; - } - return (type < rhs.type); - } -}; - -struct GeographicalLocation { - double latitude; - double longitude; - - /** - * @brief Compare two Location%s. - */ - bool operator==(const GeographicalLocation& other) const - { - return (latitude == other.latitude && longitude == other.longitude); - } - - bool operator!=(const GeographicalLocation& other) const - { - return !(latitude == other.latitude && longitude == other.longitude); - } -}; - } // namespace abm } // namespace mio -template <> -struct std::hash { - std::size_t operator()(const mio::abm::LocationId& loc_id) const - { - return (std::hash()(loc_id.index)) ^ (std::hash()(static_cast(loc_id.type))); - } -}; - #endif diff --git a/cpp/models/abm/lockdown_rules.h b/cpp/models/abm/lockdown_rules.h index 7ab4ab7e68..6780d6daa1 100644 --- a/cpp/models/abm/lockdown_rules.h +++ b/cpp/models/abm/lockdown_rules.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_LOCKDOWN_RULES_H -#define EPI_ABM_LOCKDOWN_RULES_H +#ifndef MIO_ABM_LOCKDOWN_RULES_H +#define MIO_ABM_LOCKDOWN_RULES_H #include "abm/time.h" #include "abm/location_type.h" diff --git a/cpp/models/abm/mask.h b/cpp/models/abm/mask.h index 80894171d4..2b9048925b 100644 --- a/cpp/models/abm/mask.h +++ b/cpp/models/abm/mask.h @@ -18,8 +18,8 @@ * limitations under the License. */ -#ifndef EPI_ABM_MASK_H -#define EPI_ABM_MASK_H +#ifndef MIO_ABM_MASK_H +#define MIO_ABM_MASK_H #include "abm/mask_type.h" #include "abm/time.h" diff --git a/cpp/models/abm/mask_type.h b/cpp/models/abm/mask_type.h index 6bac162944..8477e1d354 100644 --- a/cpp/models/abm/mask_type.h +++ b/cpp/models/abm/mask_type.h @@ -18,8 +18,8 @@ * limitations under the License. */ -#ifndef EPI_ABM_MASK_TYPE_H -#define EPI_ABM_MASK_TYPE_H +#ifndef MIO_ABM_MASK_TYPE_H +#define MIO_ABM_MASK_TYPE_H #include diff --git a/cpp/models/abm/migration_rules.cpp b/cpp/models/abm/migration_rules.cpp index ab7d4cc6d1..00844a9052 100644 --- a/cpp/models/abm/migration_rules.cpp +++ b/cpp/models/abm/migration_rules.cpp @@ -19,23 +19,18 @@ */ #include "abm/migration_rules.h" #include "abm/person.h" -#include "abm/location.h" #include "abm/random_events.h" -#include "abm/location.h" -#include "memilio/utils/random_number_generator.h" #include "abm/location_type.h" -#include - namespace mio { namespace abm { -LocationType random_migration(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType random_migration(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); auto make_transition = [current_loc](auto l) { return std::make_pair(l, l == current_loc ? 0. : 1.); }; @@ -48,10 +43,10 @@ LocationType random_migration(Person::RandomNumberGenerator& rng, const Person& return current_loc; } -LocationType go_to_school(Person::RandomNumberGenerator& /*rng*/, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_school(PersonalRandomNumberGenerator& /*rng*/, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if (current_loc == LocationType::Home && t < params.get() && t.day_of_week() < 5 && person.get_go_to_school_time(params) >= t.time_since_midnight() && @@ -67,10 +62,10 @@ LocationType go_to_school(Person::RandomNumberGenerator& /*rng*/, const Person& return current_loc; } -LocationType go_to_work(Person::RandomNumberGenerator& /*rng*/, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_work(PersonalRandomNumberGenerator& /*rng*/, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if (current_loc == LocationType::Home && t < params.get() && params.get()[person.get_age()] && t.day_of_week() < 5 && @@ -86,10 +81,10 @@ LocationType go_to_work(Person::RandomNumberGenerator& /*rng*/, const Person& pe return current_loc; } -LocationType go_to_shop(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_shop(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); //leave if (t.day_of_week() < 6 && t.hour_of_day() > 7 && t.hour_of_day() < 22 && current_loc == LocationType::Home && !person.is_in_quarantine(t, params)) { @@ -105,10 +100,10 @@ LocationType go_to_shop(Person::RandomNumberGenerator& rng, const Person& person return current_loc; } -LocationType go_to_event(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_event(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); //leave if (current_loc == LocationType::Home && t < params.get() && ((t.day_of_week() <= 4 && t.hour_of_day() >= 19) || (t.day_of_week() >= 5 && t.hour_of_day() >= 10)) && @@ -127,10 +122,10 @@ LocationType go_to_event(Person::RandomNumberGenerator& rng, const Person& perso return current_loc; } -LocationType go_to_quarantine(Person::RandomNumberGenerator& /*rng*/, const Person& person, TimePoint t, +LocationType go_to_quarantine(PersonalRandomNumberGenerator& /*rng*/, const Person& person, TimePoint t, TimeSpan /*dt*/, const Parameters& params) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if (person.is_in_quarantine(t, params) && current_loc != LocationType::Hospital && current_loc != LocationType::ICU) { return LocationType::Home; @@ -138,30 +133,30 @@ LocationType go_to_quarantine(Person::RandomNumberGenerator& /*rng*/, const Pers return current_loc; } -LocationType go_to_hospital(Person::RandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, +LocationType go_to_hospital(PersonalRandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, TimeSpan /*dt*/, const Parameters& /*params*/) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if (person.get_infection_state(t) == InfectionState::InfectedSevere) { return LocationType::Hospital; } return current_loc; } -LocationType go_to_icu(Person::RandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, TimeSpan /*dt*/, +LocationType go_to_icu(PersonalRandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, TimeSpan /*dt*/, const Parameters& /*params*/) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if (person.get_infection_state(t) == InfectionState::InfectedCritical) { return LocationType::ICU; } return current_loc; } -LocationType return_home_when_recovered(Person::RandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, +LocationType return_home_when_recovered(PersonalRandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, TimeSpan /*dt*/, const Parameters& /*params*/) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if ((current_loc == LocationType::Hospital || current_loc == LocationType::ICU) && person.get_infection_state(t) == InfectionState::Recovered) { return LocationType::Home; @@ -169,10 +164,10 @@ LocationType return_home_when_recovered(Person::RandomNumberGenerator& /*rng*/, return current_loc; } -LocationType get_buried(Person::RandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, +LocationType get_buried(PersonalRandomNumberGenerator& /*rng*/, const Person& person, const TimePoint t, TimeSpan /*dt*/, const Parameters& /*params*/) { - auto current_loc = person.get_location().get_type(); + auto current_loc = person.get_location_type(); if (person.get_infection_state(t) == InfectionState::Dead) { return LocationType::Cemetery; } diff --git a/cpp/models/abm/migration_rules.h b/cpp/models/abm/migration_rules.h index b6f395d197..b890e096d8 100644 --- a/cpp/models/abm/migration_rules.h +++ b/cpp/models/abm/migration_rules.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_MIGRATION_RULES_H -#define EPI_ABM_MIGRATION_RULES_H +#ifndef MIO_ABM_MIGRATION_RULES_H +#define MIO_ABM_MIGRATION_RULES_H #include "abm/location_type.h" #include "abm/parameters.h" @@ -32,7 +32,7 @@ namespace abm /** * @name Rules for migration between Location%s. - * @param[inout] rng Person::RandomNumberGenerator for the person. + * @param[inout] rng PersonalRandomNumberGenerator for the person. * @param[in] p Person the rule is applied to. * @param[in] t Current time. * @param[in] dt Length of the time step. @@ -45,65 +45,65 @@ namespace abm /** * @brief Completely random migration to any other Location. */ -LocationType random_migration(Person::RandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, +LocationType random_migration(PersonalRandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief School age children go to school in the morning and return later in the day. */ -LocationType go_to_school(Person::RandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, +LocationType go_to_school(PersonalRandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Adults may go shopping in their free time. */ -LocationType go_to_shop(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_shop(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Person%s might go to social events. */ -LocationType go_to_event(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_event(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Adults go to work in the morning and return later in the day. */ -LocationType go_to_work(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType go_to_work(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Person%s who are in quarantine should go home. */ -LocationType go_to_quarantine(Person::RandomNumberGenerator& rng, const Person& person, TimePoint /*t*/, +LocationType go_to_quarantine(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint /*t*/, TimeSpan /*dt*/, const Parameters& /*params*/); /** * @brief Infected Person%s may be hospitalized. */ -LocationType go_to_hospital(Person::RandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, +LocationType go_to_hospital(PersonalRandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Person%s in the hospital may be put in intensive care. */ -LocationType go_to_icu(Person::RandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, +LocationType go_to_icu(PersonalRandomNumberGenerator& rng, const Person& p, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Person%s in the hospital/icu return home when they recover. */ -LocationType return_home_when_recovered(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, +LocationType return_home_when_recovered(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params); /** * @brief Person%s in the icu go to cemetery when they are dead. */ -LocationType get_buried(Person::RandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, +LocationType get_buried(PersonalRandomNumberGenerator& rng, const Person& person, TimePoint t, TimeSpan dt, const Parameters& params); /**@}*/ } // namespace abm } // namespace mio -#endif //EPI_ABM_MIGRATION_RULES_H +#endif //MIO_ABM_MIGRATION_RULES_H diff --git a/cpp/models/abm/model_functions.cpp b/cpp/models/abm/model_functions.cpp new file mode 100644 index 0000000000..ae97a2b51f --- /dev/null +++ b/cpp/models/abm/model_functions.cpp @@ -0,0 +1,150 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele, Elisabeth Kluth, Khoa Nguyen, David Kerkmann, Rene Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "abm/model_functions.h" +#include "abm/location.h" +#include "abm/person.h" +#include "abm/random_events.h" +#include "abm/infection.h" +#include "abm/virus_variant.h" +#include "memilio/epidemiology/age_group.h" +#include "memilio/utils/logging.h" + +namespace mio +{ +namespace abm +{ + +ScalarType daily_transmissions_by_contacts(const ContactExposureRates& rates, const CellIndex cell_index, + const VirusVariant virus, const AgeGroup age_receiver, + const LocalInfectionParameters& params) +{ + assert(age_receiver < rates.size()); + ScalarType prob = 0; + for (AgeGroup age_transmitter(0); age_transmitter < rates.size(); ++age_transmitter) { + prob += + rates[{cell_index, virus, age_transmitter}] * params.get()[{age_receiver, age_transmitter}]; + } + return prob; +} + +ScalarType daily_transmissions_by_air(const AirExposureRates& rates, const CellIndex cell_index, + const VirusVariant virus, const Parameters& global_params) +{ + return rates[{cell_index, virus}] * global_params.get()[{virus}]; +} + +void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const Location& location, + const AirExposureRates& local_air_exposure, const ContactExposureRates& local_contact_exposure, + const TimePoint t, const TimeSpan dt, const Parameters& global_parameters) +{ + // make sure all dimensions are set correctly and all indices are valid + assert(location.get_cells().size() == local_air_exposure.size().get()); + assert(location.get_cells().size() == local_contact_exposure.size().get()); + assert(local_contact_exposure.size() == local_air_exposure.size()); + assert(local_contact_exposure.size() == VirusVariant::Count); + assert(local_contact_exposure.size().get() == global_parameters.get_num_groups()); + assert(person.get_age() < local_contact_exposure.size()); + assert(std::all_of(person.get_cells().begin(), person.get_cells().end(), [&](const auto& cell) { + return cell < location.get_cells().size(); + })); + + if (person.get_infection_state(t) == InfectionState::Susceptible) { + auto& local_parameters = location.get_infection_parameters(); + // TODO: we need to define what a cell is used for, as the loop may lead to incorrect results for multiple cells + auto age_receiver = person.get_age(); + ScalarType mask_protection = person.get_mask_protective_factor(global_parameters); + assert(person.get_cells().size() && "Person is in multiple cells. Interact logic is incorrect at the moment."); + for (auto cell_index : + person.get_cells()) { // TODO: the logic here is incorrect in case a person is in multiple cells + std::pair local_indiv_trans_prob[static_cast(VirusVariant::Count)]; + for (uint32_t v = 0; v != static_cast(VirusVariant::Count); ++v) { + VirusVariant virus = static_cast(v); + ScalarType local_indiv_trans_prob_v = + (std::min(local_parameters.get(), + daily_transmissions_by_contacts(local_contact_exposure, cell_index, virus, age_receiver, + local_parameters)) + + daily_transmissions_by_air(local_air_exposure, cell_index, virus, global_parameters)) * + dt.days() * (1 - mask_protection) * (1 - person.get_protection_factor(t, virus, global_parameters)); + + local_indiv_trans_prob[v] = std::make_pair(virus, local_indiv_trans_prob_v); + } + VirusVariant virus = + random_transition(personal_rng, VirusVariant::Count, dt, + local_indiv_trans_prob); // use VirusVariant::Count for no virus submission + if (virus != VirusVariant::Count) { + person.add_new_infection(Infection(personal_rng, virus, age_receiver, global_parameters, t + dt / 2, + mio::abm::InfectionState::Exposed, person.get_latest_protection(), + false)); // Starting time in first approximation + } + } + } + person.add_time_at_location(dt); +} + +void add_exposure_contribution(AirExposureRates& local_air_exposure, ContactExposureRates& local_contact_exposure, + const Person& person, const Location& location, 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(), + location.get_id().get()); + } + + if (person.is_infected(t)) { + auto& infection = person.get_infection(); + auto virus = infection.get_virus_variant(); + auto age = person.get_age(); + // average infectivity over the time step to second order accuracy using midpoint rule + for (CellIndex cell : person.get_cells()) { + 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(); + } + else { + local_air_exposure[{cell, virus}] += infection.get_infectivity(t + dt / 2); + } + local_contact_exposure[{cell, virus, age}] += infection.get_infectivity(t + dt / 2); + } + } +} + +bool migrate(Person& person, const Location& destination, const TransportMode mode, const std::vector& cells) +{ + assert(std::all_of(cells.begin(), cells.end(), [&](const auto& cell) { + return cell < destination.get_cells().size(); + })); // make sure cell indices are valid + + if (person.get_location() != destination.get_id()) { + person.set_location(destination.get_type(), destination.get_id()); + person.get_cells() = cells; + person.set_last_transport_mode(mode); + + return true; + } + else { + mio::log_debug("In migrate: Person {} already is at Location {}", person.get_id().get(), + destination.get_id().get()); + return false; + } +} + +} // namespace abm +} // namespace mio diff --git a/cpp/models/abm/model_functions.h b/cpp/models/abm/model_functions.h new file mode 100644 index 0000000000..e0bc00a3a2 --- /dev/null +++ b/cpp/models/abm/model_functions.h @@ -0,0 +1,99 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele, Elisabeth Kluth, Khoa Nguyen, David Kerkmann, Rene Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef MIO_ABM_MODEL_FUNCTIONS_H +#define MIO_ABM_MODEL_FUNCTIONS_H + +#include "abm/location.h" +#include "abm/person.h" + +#include + +namespace mio +{ +namespace abm +{ + +/** + * @brief Compute the number of daily transmissions for contact transmission of a virus in a cell. + * @param[in] rates The local exposure rates. + * @param[in] cell_index Cell index of the Cell. + * @param[in] virus VirusVariant of interest. + * @param[in] age_receiver AgeGroup of the receiving Person. + * @param[in] params The local infection parameters. + * @return Average amount of Infection%s with the virus from the AgeGroup of the transmitter per day. + */ +ScalarType daily_transmissions_by_contacts(const ContactExposureRates& rates, const CellIndex cell_index, + const VirusVariant virus, const AgeGroup age_receiver, + const LocalInfectionParameters& params); + +/** + * @brief Compute the number of daily transmissions for aerosol transmission of a virus in a cell. + * @param[in] rates The local exposure rates. + * @param[in] cell_index Cell index of the Cell. + * @param[in] virus VirusVariant of interest. + * @param[in] global_params The parameter set of the World. + * @return Average amount of Infection%s with the virus per day. + */ +ScalarType daily_transmissions_by_air(const AirExposureRates& rates, const CellIndex cell_index, + const VirusVariant virus, const Parameters& global_params); + +/** + * @brief Add the contribution of a person to the local exposure rates. + * @param[in, out] local_air_exposure Exposure rates by aerosols for the local population. + * @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] 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); + +/** + * @brief Let a Person interact with the population at its current Location, possibly getting infected. + * @param[in, out] rng PersonalRandomNumberGenerator for this Person. + * @param[in, out] person The person to interact with the local population. + * @param[in] location The person's current location. + * @param[in] local_air_exposure Precomputed exposure rates by aerosols for the local population. + * @param[in] local_contact_exposure Precomputed exposure by rates contacts for the local population. + * @param[in] t Current Simulation time. + * @param[in] dt Length of the current Simulation time step. + * @param[in] global_parameters Parameters of the World. + */ +void interact(PersonalRandomNumberGenerator& personal_rng, Person& person, const Location& location, + const AirExposureRates& local_air_exposure, const ContactExposureRates& local_contact_exposure, + const TimePoint t, const TimeSpan dt, const Parameters& global_parameters); +/** + * @brief Move a person to another location. + * If the person already is at the destination, neither mode nor cells are set. + * @param[in, out] person The person to be moved. + * @param[in] destination The destination to move to. + * @param[in] mode The transport mode the person uses to move. + * @param[in] cells The cells within the destination the person should be in. + * @return Returns false if the person already is at the given destination, true otherwise. + */ +bool migrate(Person& person, const Location& destination, const TransportMode mode = TransportMode::Unknown, + const std::vector& cells = {0}); + +} // namespace abm +} // namespace mio + +#endif // MIO_ABM_MODEL_FUNCTIONS_H diff --git a/cpp/models/abm/movement_data.h b/cpp/models/abm/movement_data.h index 10ba131909..de8a590a34 100644 --- a/cpp/models/abm/movement_data.h +++ b/cpp/models/abm/movement_data.h @@ -21,14 +21,13 @@ #ifndef ABM_MOVEMENT_DATA_H #define ABM_MOVEMENT_DATA_H -#include "abm/time.h" +#include namespace mio { namespace abm { - /** * @brief Mode of Transport. */ @@ -43,7 +42,6 @@ enum class TransportMode : uint32_t Unknown }; - /** * @brief Type of the activity. */ diff --git a/cpp/models/abm/parameters.h b/cpp/models/abm/parameters.h index 9cd277f0c1..7ca6dd4578 100644 --- a/cpp/models/abm/parameters.h +++ b/cpp/models/abm/parameters.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_PARAMETERS_H -#define EPI_ABM_PARAMETERS_H +#ifndef MIO_ABM_PARAMETERS_H +#define MIO_ABM_PARAMETERS_H #include "abm/mask_type.h" #include "abm/time.h" @@ -26,13 +26,11 @@ #include "abm/vaccine.h" #include "memilio/utils/custom_index_array.h" #include "memilio/utils/uncertain_value.h" -#include "memilio/math/eigen.h" #include "memilio/utils/parameter_set.h" #include "memilio/epidemiology/age_group.h" #include "memilio/epidemiology/damping.h" #include "memilio/epidemiology/contact_matrix.h" #include -#include namespace mio { @@ -578,10 +576,23 @@ struct ContactRates { } }; +// If true, consider the capacity of the Cell%s of this Location for the computation of relative transmission risk. +struct UseLocationCapacityForTransmissions { + using Type = bool; + static Type get_default(AgeGroup) + { + return false; + } + static std::string name() + { + return "UseLocationCapacityForTransmissions"; + } +}; + /** * @brief Parameters of the Infection that depend on the Location. */ -using LocalInfectionParameters = ParameterSet; +using LocalInfectionParameters = ParameterSet; /** * @brief Parameters of the simulation that are the same everywhere within the World. diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index e2dcd5f848..6e5938cff3 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -21,10 +21,9 @@ #include "abm/location_type.h" #include "abm/mask_type.h" #include "abm/parameters.h" -#include "abm/world.h" +#include "abm/infection.h" #include "abm/location.h" #include "memilio/utils/random_number_generator.h" -#include "abm/time.h" #include namespace mio @@ -32,9 +31,11 @@ namespace mio namespace abm { -Person::Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age, uint32_t person_id) - : m_location(&location) - , m_assigned_locations((uint32_t)LocationType::Count, INVALID_LOCATION_INDEX) +Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, LocationId location_id, AgeGroup age, + PersonId person_id) + : m_location(location_id) + , m_location_type(location_type) + , m_assigned_locations((uint32_t)LocationType::Count, LocationId::invalid_id()) , m_quarantine_start(TimePoint(-(std::numeric_limits::max() / 2))) , m_age(age) , m_time_at_location(0) @@ -52,32 +53,10 @@ Person::Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age m_random_goto_school_hour = UniformDistribution::get_instance()(rng); } -Person Person::copy_person(Location& location) +Person::Person(const Person& other, PersonId id) + : Person(other) { - Person copied_person = Person(*this); - copied_person.m_location = &location; - location.add_person(*this); - return copied_person; -} - -void Person::interact(RandomNumberGenerator& rng, TimePoint t, TimeSpan dt, const Parameters& params) -{ - if (get_infection_state(t) == InfectionState::Susceptible) { // Susceptible - m_location->interact(rng, *this, t, dt, params); - } - m_time_at_location += dt; -} - -void Person::migrate_to(Location& loc_new, mio::abm::TransportMode transport_mode, const std::vector& cells) -{ - if (*m_location != loc_new) { - m_location->remove_person(*this); - m_location = &loc_new; - m_cells = cells; - loc_new.add_person(*this, cells); - m_time_at_location = TimeSpan(0); - m_last_transport_mode = transport_mode; - } + m_person_id = id; } bool Person::is_infected(TimePoint t) const @@ -108,14 +87,16 @@ void Person::add_new_infection(Infection&& inf) m_infections.push_back(std::move(inf)); } -Location& Person::get_location() +LocationId Person::get_location() const { - return *m_location; + return m_location; } -const Location& Person::get_location() const +void Person::set_location(LocationType type, LocationId id) { - return *m_location; + m_location = id; + m_location_type = type; + m_time_at_location = TimeSpan(0); } const Infection& Person::get_infection() const @@ -128,23 +109,14 @@ Infection& Person::get_infection() return m_infections.back(); } -void Person::set_assigned_location(Location& location) -{ - /* TODO: This is not safe if the location is not the same as added in the world, e.g. the index is wrong. We need to check this. - * For now only use it like this: auto home_id = world.add_location(mio::abm::LocationType::Home); - * person.set_assigned_location(home); - */ - m_assigned_locations[(uint32_t)location.get_type()] = location.get_index(); -} - -void Person::set_assigned_location(LocationId id) +void Person::set_assigned_location(LocationType type, LocationId id) { - m_assigned_locations[(uint32_t)id.type] = id.index; + m_assigned_locations[static_cast(type)] = id; } -uint32_t Person::get_assigned_location_index(LocationType type) const +LocationId Person::get_assigned_location(LocationType type) const { - return m_assigned_locations[(uint32_t)type]; + return m_assigned_locations[static_cast(type)]; } bool Person::goes_to_work(TimePoint t, const Parameters& params) const @@ -180,7 +152,7 @@ void Person::remove_quarantine() m_quarantine_start = TimePoint(-(std::numeric_limits::max() / 2)); } -bool Person::get_tested(RandomNumberGenerator& rng, TimePoint t, const TestParameters& params) +bool Person::get_tested(PersonalRandomNumberGenerator& rng, TimePoint t, const TestParameters& params) { ScalarType random = UniformDistribution::get_instance()(rng); m_time_of_last_test = t; @@ -209,7 +181,7 @@ bool Person::get_tested(RandomNumberGenerator& rng, TimePoint t, const TestParam } } -uint32_t Person::get_person_id() +PersonId Person::get_id() const { return m_person_id; } @@ -234,7 +206,7 @@ ScalarType Person::get_mask_protective_factor(const Parameters& params) const } } -bool Person::apply_mask_intervention(RandomNumberGenerator& rng, const Location& target) +bool Person::apply_mask_intervention(PersonalRandomNumberGenerator& rng, const Location& target) { if (target.get_npi_active() == false) { m_wears_mask = false; diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index b80430a1b0..7810eede7f 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -17,110 +17,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_PERSON_H -#define EPI_ABM_PERSON_H +#ifndef MIO_ABM_PERSON_H +#define MIO_ABM_PERSON_H -#include "abm/location_type.h" +#include "abm/infection.h" #include "abm/infection_state.h" +#include "abm/location_id.h" +#include "abm/location.h" +#include "abm/location_type.h" #include "abm/parameters.h" +#include "abm/person_id.h" +#include "abm/personal_rng.h" #include "abm/time.h" #include "abm/vaccine.h" -#include "abm/mask_type.h" #include "abm/mask.h" +#include "abm/movement_data.h" #include "memilio/epidemiology/age_group.h" #include "memilio/utils/random_number_generator.h" -#include "memilio/utils/memory.h" -#include "abm/movement_data.h" -#include namespace mio { namespace abm { -struct LocationId; -class Location; -class Infection; - -static constexpr uint32_t INVALID_PERSON_ID = std::numeric_limits::max(); - /** * @brief Agents in the simulated World that can carry and spread the Infection. */ class Person { public: - /** - * Random number generator of individual persons. - * Increments the random number generator counter of the person when used. - * Does not store its own key or counter. - * Instead the key needs to be provided from the outside, so that the RNG - * for all persons share the same key. - * The counter is taken from the person. - * Person::RandomNumberGenerator is cheap to construct and transparent - * for the compiler to optimize, so we don't store the RNG persistently, only the - * counter, so we don't need to store the key in each person. This increases - * consistency (if the key is changed after the person is created) and - * reduces the memory required per person. - * @see mio::RandomNumberGeneratorBase - */ - class RandomNumberGenerator : public RandomNumberGeneratorBase - { - public: - /** - * Creates a RandomNumberGenerator for a person. - * @param key Key to be used by the generator. - * @param id Id of the Person. - * @param counter Reference to the Person's RNG Counter. - */ - RandomNumberGenerator(Key key, uint32_t id, Counter& counter) - : m_key(key) - , m_person_id(id) - , m_counter(counter) - { - } - - /** - * Creates a RandomNumberGenerator for a person. - * Uses the same key as another RandomNumberGenerator. - * @param rng RandomNumberGenerator who's key will be used. - * @param person Reference to the Person who's counter will be used. - */ - RandomNumberGenerator(const mio::RandomNumberGenerator& rng, Person& person) - : RandomNumberGenerator(rng.get_key(), person.get_person_id(), person.get_rng_counter()) - { - } - - /** - * @return Get the key. - */ - Key get_key() const - { - return m_key; - } - - /** - * @return Get the current counter. - */ - Counter get_counter() const - { - return rng_totalsequence_counter(m_person_id, m_counter); - } - - /** - * Increment the counter. - */ - void increment_counter() - { - ++m_counter; - } - - private: - Key m_key; ///< Global RNG Key - uint32_t m_person_id; ///< Id of the Person - Counter& m_counter; ///< Reference to the Person's rng counter - }; - /** * @brief Create a Person. * @param[in, out] rng RandomNumberGenerator. @@ -128,14 +53,10 @@ class Person * @param[in] age The AgeGroup of the Person. * @param[in] person_id Index of the Person. */ - explicit Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age, - uint32_t person_id = INVALID_PERSON_ID); + explicit Person(mio::RandomNumberGenerator& rng, LocationType location_type, LocationId location_id, AgeGroup age, + PersonId person_id = PersonId::invalid_id()); - /** - * @brief Create a copy of this #Person object with a new Location. - * @param[in, out] location The new #Location of the Person. - */ - Person copy_person(Location& location); + explicit Person(const Person& other, PersonId id); /** * @brief Compare two Person%s. @@ -145,34 +66,6 @@ class Person return (m_person_id == other.m_person_id); } - /** - * @brief Time passes and the Person interacts with the population at its current Location. - * The Person might become infected. - * @param[in] t Current time. - * @param[in] dt Length of the current Simulation TimeStep. - * @param[in, out] params Infection parameters that are the same in all Location%s. - */ - void interact(RandomNumberGenerator& rng, TimePoint t, TimeSpan dt, const Parameters& params); - - /** - * @brief Migrate to a different Location. - * @param[in, out] loc_new The new Location of the Person. - * @param[in] cells_new The Cell%s that the Person visits at the new Location. - * */ - void migrate_to(Location& loc_new, const std::vector& cells_new = {0}) - { - migrate_to(loc_new, TransportMode::Unknown, cells_new); - } - - /** - * @brief Migrate to a different Location. - * @param[in] loc_new The new Location of the Person. - * @param[in] transport_mode The TransportMode the Person used to get to the new Location. - * @param[in] cells_new The Cell%s that the Person visits at the new Location. - * */ - void migrate_to(Location& loc_new, mio::abm::TransportMode transport_mode, - const std::vector& cells = {0}); - /** * @brief Get the latest #Infection of the Person. * @return The latest #Infection of the Person. @@ -180,9 +73,10 @@ class Person Infection& get_infection(); const Infection& get_infection() const; - /** + /** * @brief Get all Vaccination%s of the Person. * @return A vector with all Vaccination%s. + * @{ */ std::vector& get_vaccinations() { @@ -193,6 +87,7 @@ class Person { return m_vaccinations; } + /** @} */ /** * @brief Returns if the Person is infected at the TimePoint. @@ -227,9 +122,18 @@ class Person * @brief Get the current Location of the Person. * @return Current Location of the Person. */ - Location& get_location(); + LocationId get_location() const; + + LocationType get_location_type() const + { + return m_location_type; + } - const Location& get_location() const; + /** + * @brief Change the location of the person. + * @param[in] id The new location. + */ + void set_location(LocationType type, LocationId id); /** * @brief Get the time the Person has been at its current Location. @@ -240,6 +144,15 @@ class Person return m_time_at_location; } + /** + * @brief Add to the time the Person has been at its current Location. + * @param[in] dt TimeSpan the Person has spent at the Location. + */ + void add_time_at_location(const TimeSpan dt) + { + m_time_at_location += dt; + } + /** * @brief Get the TimePoint of the last negative test. * @return TimePoint since the last test. @@ -248,21 +161,19 @@ class Person { return m_time_of_last_test; } - /** - * @brief Set an assigned Location of the Person. - * The assigned Location is saved by its index of its LocationId. Assume that a Person has at most one assigned - * Location per #LocationType. - * @param[in] location The new assigned Location. - */ - void set_assigned_location(Location& location); /** - * @brief Set an assigned Location of the Person. + * @brief Set an assigned Location of the Person. + * + * Important: Setting incorrect values will cause issues during simulation. It is preferable to use + * World::assign_location with a valid LocationId, obtained e.g. through World::add_location. + * * The assigned Location is saved by the index of its LocationId. Assume that a Person has at most one assigned * Location of a certain #LocationType. + * @param[in] type The LocationType of the Location. * @param[in] id The LocationId of the Location. */ - void set_assigned_location(LocationId id); + void set_assigned_location(LocationType type, LocationId id); /** * @brief Returns the index of an assigned Location of the Person. @@ -270,13 +181,13 @@ class Person * @param[in] type #LocationType of the assigned Location. * @return The index in the LocationId of the assigned Location. */ - uint32_t get_assigned_location_index(LocationType type) const; + LocationId get_assigned_location(LocationType type) const; /** * @brief Get the assigned Location%s of the Person. * @return A vector with the indices of the assigned Location%s of the Person. */ - const std::vector& get_assigned_locations() const + const std::vector& get_assigned_locations() const { return m_assigned_locations; } @@ -329,7 +240,7 @@ class Person { return t < m_quarantine_start + params.get(); } - + /** * @brief Removes the quarantine status of the Person. */ @@ -344,14 +255,14 @@ class Person * @param[in] params Sensitivity and specificity of the test method. * @return True if the test result of the Person is positive. */ - bool get_tested(RandomNumberGenerator& rng, TimePoint t, const TestParameters& params); + bool get_tested(PersonalRandomNumberGenerator& rng, TimePoint t, const TestParameters& params); /** - * @brief Get the PersonID of the Person. - * The PersonID should correspond to the index in m_persons in world. - * @return The PersonID. + * @brief Get the PersonId of the Person. + * The PersonId should correspond to the index in m_persons in world. + * @return The PersonId. */ - uint32_t get_person_id(); + PersonId get_id() const; /** * @brief Get index of Cell%s of the Person. @@ -411,7 +322,7 @@ class Person * @param[in] target The target Location. * @return Whether a Person wears a Mask at the Location. */ - bool apply_mask_intervention(RandomNumberGenerator& rng, const Location& target); + bool apply_mask_intervention(PersonalRandomNumberGenerator& rng, const Location& target); /** * @brief Decide if a Person is currently wearing a Mask. @@ -442,9 +353,9 @@ class Person /** * @brief Add a new #Vaccination - * @param[in] v ExposureType (i. e. vaccine) the person takes. + * @param[in] v ExposureType (i. e. vaccine) the person takes. * @param[in] t TimePoint of the Vaccination. - */ + */ void add_new_vaccination(ExposureType v, TimePoint t) { m_vaccinations.push_back(Vaccination(v, t)); @@ -453,35 +364,44 @@ class Person /** * @brief Get the transport mode the Person used to get to its current Location. * @return TransportMode the Person used to get to its current Location. - */ + */ mio::abm::TransportMode get_last_transport_mode() const { return m_last_transport_mode; } /** - * Get this persons RandomNumberGenerator counter. - * @see mio::abm::Person::RandomNumberGenerator. - */ + * @brief Set the transport mode the Person used to get to its current Location. + * @param[in] mode TransportMode the Person used to get to its current Location. + */ + void set_last_transport_mode(const mio::abm::TransportMode mode) + { + m_last_transport_mode = mode; + } + + /** + * @brief Get this persons RandomNumberGenerator counter. + * @see mio::abm::PersonalRandomNumberGenerator. + */ Counter& get_rng_counter() { return m_rng_counter; } /** - * @brief Get the latest #Infection or #Vaccination and its initial TimePoint of the Person. - */ + * @brief Get the latest #Infection or #Vaccination and its initial TimePoint of the Person. + */ std::pair get_latest_protection() const; /** - * serialize this. + * serialize this. * @see mio::serialize */ template void serialize(IOContext& io) const { auto obj = io.create_object("Person"); - obj.add_element("Location", *m_location); + obj.add_element("Location", m_location); obj.add_element("age", m_age); obj.add_element("id", m_person_id); } @@ -494,9 +414,9 @@ class Person static IOResult deserialize(IOContext& io) { auto obj = io.expect_object("Person"); - auto loc = obj.expect_element("Location", mio::Tag{}); + auto loc = obj.expect_element("Location", mio::Tag{}); auto age = obj.expect_element("age", Tag{}); - auto id = obj.expect_element("id", Tag{}); + auto id = obj.expect_element("id", Tag{}); return apply( io, [](auto&& loc_, auto&& age_, auto&& id_) { @@ -506,8 +426,9 @@ class Person } 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 + LocationId m_location; ///< Current Location of the Person. + LocationType m_location_type; ///< Type of the current Location. + 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. */ std::vector m_vaccinations; ///< Vector with all Vaccination%s the Person has received. std::vector m_infections; ///< Vector with all Infection%s the Person had. @@ -522,7 +443,7 @@ class Person 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. - uint32_t m_person_id; ///< Id of the Person. + 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 diff --git a/cpp/models/abm/person_id.h b/cpp/models/abm/person_id.h new file mode 100644 index 0000000000..20184b7ff9 --- /dev/null +++ b/cpp/models/abm/person_id.h @@ -0,0 +1,56 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Rene Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef MIO_ABM_PERSON_ID_H +#define MIO_ABM_PERSON_ID_H + +#include "memilio/utils/type_safe.h" +#include + +namespace mio +{ +namespace abm +{ + +/// Unique identifier for a Person within a World. +struct MEMILIO_ENABLE_EBO PersonId : public mio::TypeSafe, public OperatorComparison { + /// @brief Create an ID. + PersonId(uint32_t id) + : mio::TypeSafe(id) + { + } + + /// @brief Create an invalid ID. + PersonId() + : mio::TypeSafe(std::numeric_limits::max()) + { + } + + /// @brief Value for invalid IDs. + const static PersonId invalid_id() + { + return PersonId(); + } +}; + +} // namespace abm +} // namespace mio + +#endif // MIO_ABM_PERSON_ID_H diff --git a/cpp/models/abm/abm.h b/cpp/models/abm/personal_rng.cpp similarity index 50% rename from cpp/models/abm/abm.h rename to cpp/models/abm/personal_rng.cpp index aa24659a1f..b6eb54f675 100644 --- a/cpp/models/abm/abm.h +++ b/cpp/models/abm/personal_rng.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2024 MEmilio * -* Authors: Daniel Abele, Majid Abedi, Elisabeth Kluth +* Authors: Daniel Abele, Elisabeth Kluth, David Kerkmann, Khoa Nguyen, Rene Schmieding * * Contact: Martin J. Kuehn * @@ -17,26 +17,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** single include header for ABM */ -#ifndef EPI_ABM_H -#define EPI_ABM_H - -#include "abm/parameters.h" -#include "abm/simulation.h" -#include "abm/world.h" +#include "abm/personal_rng.h" #include "abm/person.h" -#include "abm/location.h" -#include "abm/location_type.h" -#include "memilio/math/interpolation.h" -#include "memilio/utils/random_number_generator.h" -#include "abm/migration_rules.h" -#include "abm/testing_strategy.h" -#include "abm/infection.h" -#include "abm/infection_state.h" -#include "abm/virus_variant.h" -#include "abm/vaccine.h" -#include "abm/household.h" -#include "abm/lockdown_rules.h" -#endif +namespace mio +{ +namespace abm +{ +PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(mio::Key key, PersonId id, + mio::Counter& counter) + : m_key(key) + , m_person_id(id) + , m_counter(counter) +{ +} + +PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(const mio::RandomNumberGenerator& rng, Person& person) + : PersonalRandomNumberGenerator(rng.get_key(), person.get_id(), person.get_rng_counter()) +{ +} + +} // namespace abm +} // namespace mio diff --git a/cpp/models/abm/personal_rng.h b/cpp/models/abm/personal_rng.h new file mode 100644 index 0000000000..654e8dea5a --- /dev/null +++ b/cpp/models/abm/personal_rng.h @@ -0,0 +1,100 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Daniel Abele, Elisabeth Kluth, David Kerkmann, Khoa Nguyen, Rene Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef MIO_ABM_PERSONAL_RNG_H +#define MIO_ABM_PERSONAL_RNG_H + +#include "memilio/utils/random_number_generator.h" +#include "abm/person_id.h" + +namespace mio +{ +namespace abm +{ + +class Person; + +/** + * Random number generator of individual persons. + * Increments the random number generator counter of the person when used. + * Does not store its own key or counter. + * Instead the key needs to be provided from the outside, so that the RNG + * for all persons share the same key. + * The counter is taken from the person. + * PersonalRandomNumberGenerator is cheap to construct and transparent + * for the compiler to optimize, so we don't store the RNG persistently, only the + * counter, so we don't need to store the key in each person. This increases + * consistency (if the key is changed after the person is created) and + * reduces the memory required per person. + * @see mio::RandomNumberGeneratorBase + */ +class PersonalRandomNumberGenerator : public mio::RandomNumberGeneratorBase +{ +public: + /** + * Creates a RandomNumberGenerator for a person. + * @param key Key to be used by the generator. + * @param id Id of the Person. + * @param counter Reference to the Person's RNG Counter. + */ + PersonalRandomNumberGenerator(mio::Key key, PersonId id, mio::Counter& counter); + + /** + * Creates a RandomNumberGenerator for a person. + * Uses the same key as another RandomNumberGenerator. + * @param rng RandomNumberGenerator who's key will be used. + * @param person Reference to the Person who's counter will be used. + */ + PersonalRandomNumberGenerator(const mio::RandomNumberGenerator& rng, Person& person); + + /** + * @return Get the key. + */ + mio::Key get_key() const + { + return m_key; + } + + /** + * @return Get the current counter. + */ + mio::Counter get_counter() const + { + return mio::rng_totalsequence_counter(m_person_id.get(), m_counter); + } + + /** + * Increment the counter. + */ + void increment_counter() + { + ++m_counter; + } + +private: + mio::Key m_key; ///< Global RNG Key + PersonId m_person_id; ///< Id of the Person + mio::Counter& m_counter; ///< Reference to the Person's rng counter +}; + +} // namespace abm +} // namespace mio + +#endif // MIO_ABM_PERSONAL_RNG_H diff --git a/cpp/models/abm/simulation.cpp b/cpp/models/abm/simulation.cpp index 54e9024ce7..d716e4e23e 100644 --- a/cpp/models/abm/simulation.cpp +++ b/cpp/models/abm/simulation.cpp @@ -18,9 +18,6 @@ * limitations under the License. */ #include "abm/simulation.h" -#include "memilio/utils/logging.h" -#include "memilio/utils/mioomp.h" -#include namespace mio { diff --git a/cpp/models/abm/simulation.h b/cpp/models/abm/simulation.h index 7247a69819..ecc8cd8c74 100644 --- a/cpp/models/abm/simulation.h +++ b/cpp/models/abm/simulation.h @@ -17,14 +17,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_SIMULATOR_H -#define EPI_ABM_SIMULATOR_H +#ifndef MIO_ABM_SIMULATION_H +#define MIO_ABM_SIMULATION_H #include "abm/world.h" #include "abm/time.h" -#include "memilio/utils/time_series.h" -#include "memilio/compartments/compartmentalmodel.h" -#include "memilio/epidemiology/populations.h" #include "memilio/io/history.h" namespace mio diff --git a/cpp/models/abm/testing_strategy.cpp b/cpp/models/abm/testing_strategy.cpp index 9e9dff039d..3a4278dee6 100644 --- a/cpp/models/abm/testing_strategy.cpp +++ b/cpp/models/abm/testing_strategy.cpp @@ -19,7 +19,9 @@ */ #include "abm/testing_strategy.h" +#include "abm/location_id.h" #include "memilio/utils/random_number_generator.h" +#include namespace mio { @@ -100,7 +102,7 @@ void TestingScheme::update_activity_status(TimePoint t) m_is_active = (m_start_date <= t && t <= m_end_date); } -bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& person, TimePoint t) const +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)) { @@ -113,40 +115,41 @@ bool TestingScheme::run_scheme(Person::RandomNumberGenerator& rng, Person& perso return true; } -TestingStrategy::TestingStrategy( - const std::unordered_map>& location_to_schemes_map) +TestingStrategy::TestingStrategy(const std::vector& location_to_schemes_map) : m_location_to_schemes_map(location_to_schemes_map.begin(), location_to_schemes_map.end()) { } -void TestingStrategy::add_testing_scheme(const LocationId& loc_id, const TestingScheme& scheme) +void TestingStrategy::add_testing_scheme(const LocationType& loc_type, const LocationId& loc_id, + const TestingScheme& scheme) { auto iter_schemes = - std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [loc_id](auto& p) { - return p.first == loc_id; + std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [&](const auto& p) { + return p.type == loc_type && p.id == loc_id; }); if (iter_schemes == m_location_to_schemes_map.end()) { //no schemes for this location yet, add a new list with one scheme - m_location_to_schemes_map.emplace_back(loc_id, std::vector(1, scheme)); + m_location_to_schemes_map.push_back({loc_type, loc_id, std::vector(1, scheme)}); } else { //add scheme to existing vector if the scheme doesn't exist yet - auto& schemes = iter_schemes->second; + auto& schemes = iter_schemes->schemes; if (std::find(schemes.begin(), schemes.end(), scheme) == schemes.end()) { schemes.push_back(scheme); } } } -void TestingStrategy::remove_testing_scheme(const LocationId& loc_id, const TestingScheme& scheme) +void TestingStrategy::remove_testing_scheme(const LocationType& loc_type, const LocationId& loc_id, + const TestingScheme& scheme) { auto iter_schemes = - std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [loc_id](auto& p) { - return p.first == loc_id; + std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [&](const auto& p) { + return p.type == loc_type && p.id == loc_id; }); if (iter_schemes != m_location_to_schemes_map.end()) { //remove the scheme from the list - auto& schemes_vector = iter_schemes->second; + auto& schemes_vector = iter_schemes->schemes; auto last = std::remove(schemes_vector.begin(), schemes_vector.end(), scheme); schemes_vector.erase(last, schemes_vector.end()); //delete the list of schemes for this location if no schemes left @@ -158,14 +161,14 @@ void TestingStrategy::remove_testing_scheme(const LocationId& loc_id, const Test void TestingStrategy::update_activity_status(TimePoint t) { - for (auto& [_, testing_schemes] : m_location_to_schemes_map) { + for (auto& [_type, _id, testing_schemes] : m_location_to_schemes_map) { for (auto& scheme : testing_schemes) { scheme.update_activity_status(t); } } } -bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& person, const Location& location, +bool TestingStrategy::run_strategy(PersonalRandomNumberGenerator& rng, Person& person, const Location& location, TimePoint t) { // A Person is always allowed to go home and this is never called if a person is not discharged from a hospital or ICU. @@ -175,15 +178,15 @@ bool TestingStrategy::run_strategy(Person::RandomNumberGenerator& rng, Person& p //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()}, - LocationId{INVALID_LOCATION_INDEX, location.get_type()}}) { + for (auto key : {std::make_pair(location.get_type(), location.get_id()), + std::make_pair(location.get_type(), LocationId::invalid_id())}) { auto iter_schemes = - std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [loc_key](auto& p) { - return p.first == loc_key; + std::find_if(m_location_to_schemes_map.begin(), m_location_to_schemes_map.end(), [&](const auto& p) { + return p.type == key.first && p.id == key.second; }); if (iter_schemes != m_location_to_schemes_map.end()) { //apply all testing schemes that are found - auto& schemes = iter_schemes->second; + auto& schemes = iter_schemes->schemes; if (!std::all_of(schemes.begin(), schemes.end(), [&rng, &person, t](TestingScheme& ts) { return !ts.is_active() || ts.run_scheme(rng, person, t); })) { diff --git a/cpp/models/abm/testing_strategy.h b/cpp/models/abm/testing_strategy.h index e947ec30c7..ee0c4a3ba0 100644 --- a/cpp/models/abm/testing_strategy.h +++ b/cpp/models/abm/testing_strategy.h @@ -17,17 +17,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_TESTING_SCHEME_H -#define EPI_ABM_TESTING_SCHEME_H +#ifndef MIO_ABM_TESTING_SCHEME_H +#define MIO_ABM_TESTING_SCHEME_H #include "abm/config.h" +#include "abm/location_id.h" +#include "abm/location_type.h" #include "abm/parameters.h" #include "abm/person.h" #include "abm/location.h" #include "abm/time.h" -#include "memilio/utils/random_number_generator.h" #include -#include +#include namespace mio { @@ -136,12 +137,12 @@ class TestingScheme /** * @brief Runs the TestingScheme and potentially tests a Person. - * @param[inout] rng Person::RandomNumberGenerator for the Person being tested. + * @param[inout] rng PersonalRandomNumberGenerator of the Person being tested. * @param[in] person Person to check. * @param[in] t TimePoint when to run the scheme. * @return If the person is allowed to enter the Location by the scheme. */ - bool run_scheme(Person::RandomNumberGenerator& rng, Person& person, TimePoint t) const; + bool run_scheme(PersonalRandomNumberGenerator& rng, Person& person, TimePoint t) const; private: TestingCriteria m_testing_criteria; ///< TestingCriteria of the scheme. @@ -159,49 +160,60 @@ class TestingScheme class TestingStrategy { public: + /** + * @brief List of testing schemes for a given LocationType and LocationId. + * A LocalStrategy with id of value LocationId::invalid_id() is used for all Locations with LocationType type. + */ + struct LocalStrategy { + LocationType type; + LocationId id; + std::vector schemes; + }; + /** * @brief Create a TestingStrategy. * @param[in] testing_schemes Vector of TestingSchemes that are checked for testing. */ TestingStrategy() = default; - explicit TestingStrategy(const std::unordered_map>& location_to_schemes_map); + explicit TestingStrategy(const std::vector& location_to_schemes_map); /** * @brief Add a TestingScheme to the set of schemes that are checked for testing at a certain Location. + * A TestingScheme with loc_id of value LocationId::invalid_id() is used for all Locations of the given type. + * @param[in] loc_type LocationType key for TestingScheme to be remove. * @param[in] loc_id LocationId key for TestingScheme to be added. * @param[in] scheme TestingScheme to be added. */ - void add_testing_scheme(const LocationId& loc_id, const TestingScheme& scheme); + void add_testing_scheme(const LocationType& loc_type, const LocationId& loc_id, const TestingScheme& scheme); /** * @brief Add a TestingScheme to the set of schemes that are checked for testing at a certain LocationType. - * A TestingScheme applies to all Location of the same type is store in - * LocationId{INVALID_LOCATION_INDEX, location_type} of m_location_to_schemes_map. + * A TestingScheme with loc_id of value LocationId::invalid_id() is used for all Locations of the given type. * @param[in] loc_type LocationId key for TestingScheme to be added. * @param[in] scheme TestingScheme to be added. */ void add_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme) { - add_testing_scheme(LocationId{INVALID_LOCATION_INDEX, loc_type}, scheme); + add_testing_scheme(loc_type, LocationId::invalid_id(), scheme); } /** * @brief Remove a TestingScheme from the set of schemes that are checked for testing at a certain Location. + * @param[in] loc_type LocationType key for TestingScheme to be remove. * @param[in] loc_id LocationId key for TestingScheme to be remove. * @param[in] scheme TestingScheme to be removed. */ - void remove_testing_scheme(const LocationId& loc_id, const TestingScheme& scheme); + void remove_testing_scheme(const LocationType& loc_type, const LocationId& loc_id, const TestingScheme& scheme); /** * @brief Remove a TestingScheme from the set of schemes that are checked for testing at a certain Location. - * A TestingScheme applies to all Location of the same type is store in - * LocationId{INVALID_LOCATION_INDEX, location_type} of m_location_to_schemes_map. + * A TestingScheme with loc_id of value LocationId::invalid_id() is used for all Locations of the given type. * @param[in] loc_type LocationType key for TestingScheme to be remove. * @param[in] scheme TestingScheme to be removed. */ void remove_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme) { - remove_testing_scheme(LocationId{INVALID_LOCATION_INDEX, loc_type}, scheme); + remove_testing_scheme(loc_type, LocationId::invalid_id(), scheme); } /** @@ -213,17 +225,16 @@ class TestingStrategy /** * @brief Runs the TestingStrategy and potentially tests a Person. - * @param[inout] rng Person::RandomNumberGenerator for the Person being tested. + * @param[inout] rng PersonalRandomNumberGenerator of the Person being tested. * @param[in] person Person to check. * @param[in] location Location to check. * @param[in] t TimePoint when to run the strategy. * @return If the Person is allowed to enter the Location. */ - bool run_strategy(Person::RandomNumberGenerator& rng, Person& person, const Location& location, TimePoint t); + bool run_strategy(PersonalRandomNumberGenerator& rng, Person& person, const Location& location, TimePoint t); private: - std::vector>> - m_location_to_schemes_map; ///< Set of schemes that are checked for testing. + std::vector m_location_to_schemes_map; ///< Set of schemes that are checked for testing. }; } // namespace abm diff --git a/cpp/models/abm/time.h b/cpp/models/abm/time.h index 7b5f0b8e1b..45ad5495d4 100644 --- a/cpp/models/abm/time.h +++ b/cpp/models/abm/time.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_TIME_H -#define EPI_ABM_TIME_H +#ifndef MIO_ABM_TIME_H +#define MIO_ABM_TIME_H namespace mio { diff --git a/cpp/models/abm/trip_list.cpp b/cpp/models/abm/trip_list.cpp index 88ef819de4..c9991bdb1f 100644 --- a/cpp/models/abm/trip_list.cpp +++ b/cpp/models/abm/trip_list.cpp @@ -18,10 +18,8 @@ * limitations under the License. */ #include "abm/trip_list.h" -#include "abm/location.h" #include "abm/random_events.h" - -#include +#include "memilio/utils/stl_util.h" namespace mio { diff --git a/cpp/models/abm/trip_list.h b/cpp/models/abm/trip_list.h index 51ce065667..627afef7f1 100644 --- a/cpp/models/abm/trip_list.h +++ b/cpp/models/abm/trip_list.h @@ -17,17 +17,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_TRIP_LIST_H -#define EPI_ABM_TRIP_LIST_H +#ifndef MIO_ABM_TRIP_LIST_H +#define MIO_ABM_TRIP_LIST_H -#include "abm/parameters.h" -#include "abm/location.h" -#include "abm/infection.h" -#include "abm/location_type.h" - -#include "memilio/math/eigen.h" -#include -#include +#include "abm/location_id.h" +#include "abm/movement_data.h" +#include "abm/person_id.h" +#include "abm/time.h" namespace mio { @@ -38,7 +34,7 @@ namespace abm * @brief A trip describes a migration from one Location to another Location. */ struct Trip { - uint32_t person_id; /**< Person that makes the trip and corresponds to the index into the structure m_persons from + PersonId person_id; /**< Person that makes the trip and corresponds to the index into the structure m_persons from World, where all Person%s are saved.*/ TimePoint time; ///< Time at which a Person changes the Location. LocationId migration_destination; ///< Location where the Person migrates to. @@ -58,7 +54,7 @@ struct Trip { * @param[in] origin Location where the person starts the Trip. * @param[in] input_cells The index of the Cell%s the Person migrates to. */ - Trip(uint32_t id, TimePoint time_new, LocationId destination, LocationId origin, TransportMode mode_of_transport, + Trip(PersonId id, TimePoint time_new, LocationId destination, LocationId origin, TransportMode mode_of_transport, ActivityType type_of_activity, const std::vector& input_cells = {}) : person_id(id) , time(mio::abm::TimePoint(time_new.time_since_midnight().seconds())) @@ -70,13 +66,13 @@ struct Trip { { } - Trip(uint32_t id, TimePoint time_new, LocationId destination, const std::vector& input_cells = {}) + Trip(PersonId id, TimePoint time_new, LocationId destination, const std::vector& input_cells = {}) : Trip(id, time_new, destination, destination, mio::abm::TransportMode::Unknown, mio::abm::ActivityType::UnknownActivity, input_cells) { } - Trip(uint32_t id, TimePoint time_new, LocationId destination, LocationId origin, + Trip(PersonId id, TimePoint time_new, LocationId destination, LocationId origin, const std::vector& input_cells = {}) : Trip(id, time_new, destination, origin, mio::abm::TransportMode::Unknown, mio::abm::ActivityType::UnknownActivity, input_cells) @@ -102,10 +98,8 @@ struct Trip { auto obj = io.create_object("Trip"); obj.add_element("person_id", person_id); obj.add_element("time", time.seconds()); - obj.add_element("destination_index", migration_destination.index); - obj.add_element("destination_type", migration_destination.type); - obj.add_element("origin_index", migration_origin.index); - obj.add_element("origin_type", migration_origin.type); + obj.add_element("destination", migration_destination); + obj.add_element("origin", migration_origin); } /** @@ -115,22 +109,17 @@ struct Trip { template static IOResult deserialize(IOContext& io) { - auto obj = io.expect_object("Trip"); - auto person_id = obj.expect_element("person_id", Tag{}); - auto time = obj.expect_element("time", Tag{}); - auto destination_index = obj.expect_element("destination_index", Tag{}); - auto destination_type = obj.expect_element("destination_type", Tag{}); - auto origin_index = obj.expect_element("origin_index", Tag{}); - auto origin_type = obj.expect_element("origin_type", Tag{}); + auto obj = io.expect_object("Trip"); + auto person_id = obj.expect_element("person_id", Tag{}); + auto time = obj.expect_element("time", Tag{}); + auto destination_id = obj.expect_element("destination", Tag{}); + auto origin_id = obj.expect_element("origin", Tag{}); return apply( io, - [](auto&& person_id_, auto&& time_, auto&& destination_index_, auto&& destination_type_, - auto&& origin_index_, auto&& origin_type_) { - return Trip(person_id_, TimePoint(time_), - LocationId{destination_index_, LocationType(destination_type_)}, - LocationId{origin_index_, LocationType(origin_type_)}); + [](auto&& person_id_, auto&& time_, auto&& destination_id_, auto&& origin_id_) { + return Trip(person_id_, TimePoint(time_), destination_id_, origin_id_); }, - person_id, time, destination_index, destination_type, origin_index, origin_type); + person_id, time, destination_id, origin_id); } }; diff --git a/cpp/models/abm/vaccine.h b/cpp/models/abm/vaccine.h index 29199abb51..72da133516 100644 --- a/cpp/models/abm/vaccine.h +++ b/cpp/models/abm/vaccine.h @@ -17,8 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_VACCINE_H -#define EPI_ABM_VACCINE_H +#ifndef MIO_ABM_VACCINE_H +#define MIO_ABM_VACCINE_H #include "abm/time.h" diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index e534b8cec0..e3654b69b2 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -18,17 +18,16 @@ * limitations under the License. */ #include "abm/world.h" +#include "abm/location_id.h" #include "abm/location_type.h" -#include "abm/mask_type.h" #include "abm/person.h" #include "abm/location.h" #include "abm/migration_rules.h" -#include "abm/infection.h" -#include "abm/vaccine.h" +#include "memilio/epidemiology/age_group.h" #include "memilio/utils/logging.h" #include "memilio/utils/mioomp.h" -#include "memilio/utils/random_number_generator.h" #include "memilio/utils/stl_util.h" +#include namespace mio { @@ -37,21 +36,39 @@ namespace abm LocationId World::add_location(LocationType type, uint32_t num_cells) { - LocationId id = {static_cast(m_locations.size()), type}; - m_locations.emplace_back(std::make_unique(id, parameters.get_num_groups(), num_cells)); + LocationId id{static_cast(m_locations.size())}; + m_locations.emplace_back(type, id, parameters.get_num_groups(), num_cells); m_has_locations[size_t(type)] = true; + + // mark caches for rebuild + m_is_local_population_cache_valid = false; + m_are_exposure_caches_valid = false; + m_exposure_caches_need_rebuild = true; + return id; } -Person& World::add_person(const LocationId id, AgeGroup age) +PersonId World::add_person(const LocationId id, AgeGroup age) +{ + return add_person(Person(m_rng, get_location(id).get_type(), id, age)); +} + +PersonId World::add_person(Person&& person) { - assert(age.get() < parameters.get_num_groups()); - uint32_t person_id = static_cast(m_persons.size()); - m_persons.push_back(std::make_unique(m_rng, get_individualized_location(id), age, person_id)); - auto& person = *m_persons.back(); - person.set_assigned_location(m_cemetery_id); - get_individualized_location(id).add_person(person); - return person; + assert(person.get_location() != LocationId::invalid_id() && "Added Person's location must be valid."); + assert(person.get_location() < LocationId((uint32_t)m_locations.size()) && + "Added Person's location is not in World."); + assert(person.get_age() < (AgeGroup)parameters.get_num_groups() && "Added Person's AgeGroup is too large."); + + PersonId new_id{static_cast(m_persons.size())}; + m_persons.emplace_back(person, new_id); + auto& new_person = m_persons.back(); + new_person.set_assigned_location(LocationType::Cemetery, m_cemetery_id); + + if (m_is_local_population_cache_valid) { + ++m_local_population_cache[new_person.get_location().get()]; + } + return new_id; } void World::evolve(TimePoint t, TimeSpan dt) @@ -65,32 +82,32 @@ void World::evolve(TimePoint t, TimeSpan dt) void World::interaction(TimePoint t, TimeSpan dt) { + const uint32_t num_persons = static_cast(m_persons.size()); 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); - person->interact(personal_rng, t, dt, parameters); + for (uint32_t person_id = 0; person_id < num_persons; ++person_id) { + interact(person_id, t, dt); } } void World::migration(TimePoint t, TimeSpan dt) { + const uint32_t num_persons = static_cast(m_persons.size()); 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); + for (uint32_t person_id = 0; person_id < num_persons; ++person_id) { + Person& person = m_persons[person_id]; + auto personal_rng = PersonalRandomNumberGenerator(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(); - 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); + auto target_type = rule(personal_rng, person, t, dt, parameters); + const Location& target_location = get_location(find_location(target_type, person_id)); + const LocationId current_location = person.get_location(); + if (m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + if (target_location.get_id() != current_location && + get_number_persons(target_location.get_id()) < target_location.get_capacity().persons) { + bool wears_mask = person.apply_mask_intervention(personal_rng, target_location); if (wears_mask) { - person->migrate_to(target_location); + migrate(person_id, target_location.get_id()); } return true; } @@ -129,13 +146,13 @@ void World::migration(TimePoint t, TimeSpan dt) 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 personal_rng = Person::RandomNumberGenerator(m_rng, *person); - if (!person->is_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)) { - person->apply_mask_intervention(personal_rng, target_location); - person->migrate_to(target_location, trip.trip_mode); + auto& person = get_person(trip.person_id); + auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); + if (!person.is_in_quarantine(t, parameters) && person.get_infection_state(t) != InfectionState::Dead) { + auto& target_location = get_location(trip.migration_destination); + if (m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + person.apply_mask_intervention(personal_rng, target_location); + migrate(person.get_id(), target_location.get_id(), trip.trip_mode); } } m_trip_list.increase_index(); @@ -146,65 +163,134 @@ void World::migration(TimePoint t, TimeSpan dt) } } -void World::begin_step(TimePoint t, TimeSpan dt) +void World::build_compute_local_population_cache() const { - m_testing_strategy.update_activity_status(t); - PRAGMA_OMP(parallel for) - for (auto i = size_t(0); i < m_locations.size(); ++i) { - auto&& location = m_locations[i]; - location->cache_exposure_rates(t, dt, parameters.get_num_groups()); - } + PRAGMA_OMP(single) + { + const size_t num_locations = m_locations.size(); + const size_t num_persons = m_persons.size(); + m_local_population_cache.resize(num_locations); + PRAGMA_OMP(taskloop) + for (size_t i = 0; i < num_locations; i++) { + m_local_population_cache[i] = 0; + } // implicit taskloop barrier + PRAGMA_OMP(taskloop) + for (size_t i = 0; i < num_persons; i++) { + ++m_local_population_cache[m_persons[i].get_location().get()]; + } // implicit taskloop barrier + } // implicit single barrier } -auto World::get_locations() const -> Range> +void World::build_exposure_caches() { - return std::make_pair(ConstLocationIterator(m_locations.begin()), ConstLocationIterator(m_locations.end())); + PRAGMA_OMP(single) + { + const size_t num_locations = m_locations.size(); + m_air_exposure_rates_cache.resize(num_locations); + m_contact_exposure_rates_cache.resize(num_locations); + PRAGMA_OMP(taskloop) + for (size_t i = 0; i < num_locations; i++) { + m_air_exposure_rates_cache[i].resize({CellIndex(m_locations[i].get_cells().size()), VirusVariant::Count}); + m_contact_exposure_rates_cache[i].resize({CellIndex(m_locations[i].get_cells().size()), VirusVariant::Count, + AgeGroup(parameters.get_num_groups())}); + } // implicit taskloop barrier + m_are_exposure_caches_valid = false; + m_exposure_caches_need_rebuild = false; + } // implicit single barrier } -auto World::get_persons() const -> Range> +void World::compute_exposure_caches(TimePoint t, TimeSpan dt) +{ + PRAGMA_OMP(single) + { + // if cache shape was changed (e.g. by add_location), rebuild it + if (m_exposure_caches_need_rebuild) { + build_exposure_caches(); + } + // use these const values to help omp recognize that the for loops are bounded + const auto num_locations = m_locations.size(); + const auto num_persons = m_persons.size(); + + // 1) reset all cached values + // Note: we cannot easily reuse values, as they are time dependant (get_infection_state) + PRAGMA_OMP(taskloop) + for (size_t i = 0; i < num_locations; ++i) { + const auto index = i; + auto& local_air_exposure = m_air_exposure_rates_cache[index]; + std::for_each(local_air_exposure.begin(), local_air_exposure.end(), [](auto& r) { + r = 0.0; + }); + auto& local_contact_exposure = m_contact_exposure_rates_cache[index]; + std::for_each(local_contact_exposure.begin(), local_contact_exposure.end(), [](auto& r) { + r = 0.0; + }); + } // implicit taskloop barrier + // here is an implicit (and needed) barrier from parallel for + + // 2) add all contributions from each person + PRAGMA_OMP(taskloop) + for (size_t i = 0; i < num_persons; ++i) { + const Person& person = m_persons[i]; + const auto location = person.get_location().get(); + mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location], + m_contact_exposure_rates_cache[location], person, + get_location(person.get_id()), t, dt); + } // implicit taskloop barrier + } // implicit single barrier +} + +void World::begin_step(TimePoint t, TimeSpan dt) { - return std::make_pair(ConstPersonIterator(m_persons.begin()), ConstPersonIterator(m_persons.end())); + m_testing_strategy.update_activity_status(t); + + if (!m_is_local_population_cache_valid) { + build_compute_local_population_cache(); + m_is_local_population_cache_valid = true; + } + compute_exposure_caches(t, dt); + m_are_exposure_caches_valid = true; } -const Location& World::get_individualized_location(LocationId id) const +auto World::get_locations() const -> Range> +{ + return std::make_pair(m_locations.cbegin(), m_locations.cend()); +} +auto World::get_locations() -> Range> { - return *m_locations[id.index]; + return std::make_pair(m_locations.begin(), m_locations.end()); } -Location& World::get_individualized_location(LocationId id) +auto World::get_persons() const -> Range> { - return *m_locations[id.index]; + return std::make_pair(m_persons.cbegin(), m_persons.cend()); } -const Location& World::find_location(LocationType type, const Person& person) const +auto World::get_persons() -> Range> { - auto index = person.get_assigned_location_index(type); - assert(index != INVALID_LOCATION_INDEX && "unexpected error."); - return get_individualized_location({index, type}); + return std::make_pair(m_persons.begin(), m_persons.end()); } -Location& World::find_location(LocationType type, const Person& person) +LocationId World::find_location(LocationType type, const PersonId person) const { - auto index = person.get_assigned_location_index(type); - assert(index != INVALID_LOCATION_INDEX && "unexpected error."); - return get_individualized_location({index, type}); + auto location_id = get_person(person).get_assigned_location(type); + assert(location_id != LocationId::invalid_id() && "The person has no assigned location of that type."); + return location_id; } size_t World::get_subpopulation_combined(TimePoint t, InfectionState s) const { return std::accumulate(m_locations.begin(), m_locations.end(), (size_t)0, - [t, s](size_t running_sum, const std::unique_ptr& loc) { - return running_sum + loc->get_subpopulation(t, s); + [t, s, this](size_t running_sum, const Location& loc) { + return running_sum + get_subpopulation(loc.get_id(), t, s); }); } size_t World::get_subpopulation_combined_per_location_type(TimePoint t, InfectionState s, LocationType type) const { - return std::accumulate(m_locations.begin(), m_locations.end(), (size_t)0, - [t, s, type](size_t running_sum, const std::unique_ptr& loc) { - return loc->get_type() == type ? running_sum + loc->get_subpopulation(t, s) - : running_sum; - }); + return std::accumulate( + m_locations.begin(), m_locations.end(), (size_t)0, [t, s, type, this](size_t running_sum, const Location& loc) { + return loc.get_type() == type ? running_sum + get_subpopulation(loc.get_id(), t, s) : running_sum; + }); } TripList& World::get_trip_list() diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 8aa65ac5de..853dff4960 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -17,25 +17,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef EPI_ABM_WORLD_H -#define EPI_ABM_WORLD_H +#ifndef MIO_ABM_WORLD_H +#define MIO_ABM_WORLD_H -#include "abm/config.h" +#include "abm/model_functions.h" #include "abm/location_type.h" +#include "abm/movement_data.h" #include "abm/parameters.h" #include "abm/location.h" #include "abm/person.h" -#include "abm/lockdown_rules.h" +#include "abm/person_id.h" +#include "abm/time.h" #include "abm/trip_list.h" +#include "abm/random_events.h" #include "abm/testing_strategy.h" -#include "memilio/utils/pointer_dereferencing_iterator.h" +#include "memilio/epidemiology/age_group.h" #include "memilio/utils/random_number_generator.h" #include "memilio/utils/stl_util.h" #include -#include #include -#include namespace mio { @@ -49,10 +50,10 @@ namespace abm class World { public: - using LocationIterator = PointerDereferencingIterator>::iterator>; - using ConstLocationIterator = PointerDereferencingIterator>::const_iterator>; - using PersonIterator = PointerDereferencingIterator>::iterator>; - using ConstPersonIterator = PointerDereferencingIterator>::const_iterator>; + using LocationIterator = std::vector::iterator; + using ConstLocationIterator = std::vector::const_iterator; + using PersonIterator = std::vector::iterator; + using ConstPersonIterator = std::vector::const_iterator; /** * @brief Create a World. @@ -68,41 +69,43 @@ class World } /** - * @brief Create a copied World. - * @param[in] other The World that needs to be copied. + * @brief Create a World. + * @param[in] params Initial simulation parameters. */ + World(const Parameters& params) + : parameters(params.get_num_groups()) + , m_trip_list() + , m_use_migration_rules(true) + , m_cemetery_id(add_location(LocationType::Cemetery)) + { + parameters = params; + } + World(const World& other) : parameters(other.parameters) - , m_persons() - , m_locations() + , m_local_population_cache() + , m_air_exposure_rates_cache() + , m_contact_exposure_rates_cache() + , m_is_local_population_cache_valid(false) + , m_are_exposure_caches_valid(false) + , m_exposure_caches_need_rebuild(true) + , m_persons(other.m_persons) + , m_locations(other.m_locations) + , m_has_locations(other.m_has_locations) + , m_testing_strategy(other.m_testing_strategy) , m_trip_list(other.m_trip_list) - , m_cemetery_id(add_location(LocationType::Cemetery)) + , m_use_migration_rules(other.m_use_migration_rules) + , m_migration_rules(other.m_migration_rules) + , m_cemetery_id(other.m_cemetery_id) + , m_rng(other.m_rng) { - for (auto& origin_loc : other.get_locations()) { - if (origin_loc.get_type() != LocationType::Cemetery) { - // Copy a location - m_locations.emplace_back( - std::make_unique(origin_loc.copy_location_without_persons(parameters.get_num_groups()))); - } - for (auto& person : other.get_persons()) { - // If a person is in this location, copy this person and add it to this location. - if (person.get_location() == origin_loc) { - LocationId origin_id = {origin_loc.get_index(), origin_loc.get_type()}; - m_persons.push_back( - std::make_unique(person.copy_person(get_individualized_location(origin_id)))); - } - } - } - use_migration_rules(other.m_use_migration_rules); } - - //type is move-only for stable references of persons/locations - World(World&& other) = default; - World& operator=(World&& other) = default; - World& operator=(const World&) = delete; + World& operator=(const World&) = default; + World(World&&) = default; + World& operator=(World&&) = default; /** - * serialize this. + * serialize this. * @see mio::serialize */ template @@ -148,75 +151,89 @@ class World size, locations, trip_list, persons, use_migration_rules); } - /** + /** * @brief Prepare the World for the next Simulation step. * @param[in] t Current time. * @param[in] dt Length of the time step. */ void begin_step(TimePoint t, TimeSpan dt); - /** + /** * @brief Evolve the world one time step. * @param[in] t Current time. * @param[in] dt Length of the time step. */ void evolve(TimePoint t, TimeSpan dt); - /** + /** * @brief Add a Location to the World. * @param[in] type Type of Location to add. * @param[in] num_cells [Default: 1] Number of Cell%s that the Location is divided into. - * @return Index and type of the newly created Location. + * @return ID of the newly created Location. */ LocationId add_location(LocationType type, uint32_t num_cells = 1); - /** + /** * @brief Add a Person to the World. - * @param[in] id Index and type of the initial Location of the Person. + * @param[in] id The LocationId of the initial Location of the Person. * @param[in] age AgeGroup of the person. - * @return Reference to the newly created Person. + * @return ID of the newly created Person. */ - Person& add_person(const LocationId id, AgeGroup age); + PersonId add_person(const LocationId id, AgeGroup age); + + /** + * @brief Adds a copy of a given Person to the World. + * @param[in] person The Person to copy from. + * @return ID of the newly created Person. + */ + PersonId add_person(Person&& person); /** * @brief Get a range of all Location%s in the World. * @return A range of all Location%s. + * @{ */ Range> get_locations() const; + Range> get_locations(); + /** @} */ /** * @brief Get a range of all Person%s in the World. * @return A range of all Person%s. + * @{ */ Range> get_persons() const; - - /** - * @brief Get an individualized Location. - * @param[in] id LocationId of the Location. - * @return Reference to the Location. - */ - const Location& get_individualized_location(LocationId id) const; - - Location& get_individualized_location(LocationId id); + Range> get_persons(); + /** @} */ /** * @brief Find an assigned Location of a Person. * @param[in] type The #LocationType that specifies the assigned Location. - * @param[in] person The Person. - * @return Reference to the assigned Location. + * @param[in] person PersonId of the Person. + * @return ID of the Location of LocationType type assigend to person. */ - const Location& find_location(LocationType type, const Person& person) const; + LocationId find_location(LocationType type, const PersonId person) const; - Location& find_location(LocationType type, const Person& person); + /** + * @brief Assign a Location to a Person. + * A Person can have at most one assigned Location of a certain LocationType. + * Assigning another Location of an already assigned LocationType will replace the prior assignment. + * @param[in] person The PersonId of the person this location will be assigned to. + * @param[in] location The LocationId of the Location. + */ + void assign_location(PersonId person, LocationId location) + { + get_person(person).set_assigned_location(get_location(location).get_type(), location); + } - /** + /** * @brief Get the number of Persons in one #InfectionState at all Location%s. * @param[in] t Specified #TimePoint. * @param[in] s Specified #InfectionState. */ size_t get_subpopulation_combined(TimePoint t, InfectionState s) const; - /** + /** * @brief Get the number of Persons in one #InfectionState at all Location%s of a type. * @param[in] t Specified #TimePoint. * @param[in] s Specified #InfectionState. @@ -232,10 +249,10 @@ class World const TripList& get_trip_list() const; - /** + /** * @brief Decide if migration rules (like go to school/work) are used or not; * The migration rules regarding hospitalization/ICU/quarantine are always used. - * @param[in] param If true uses the migration rules for migration to school/work etc., else only the rules + * @param[in] param If true uses the migration rules for migration to school/work etc., else only the rules * regarding hospitalization/ICU/quarantine. */ void use_migration_rules(bool param); @@ -264,7 +281,7 @@ class World }); } - /** + /** * @brief Get the TestingStrategy. * @return Reference to the list of TestingScheme%s that are checked for testing. */ @@ -272,14 +289,14 @@ class World const TestingStrategy& get_testing_strategy() const; - /** + /** * @brief The simulation parameters of the world. */ Parameters parameters; /** * Get the RandomNumberGenerator used by this world for random events. - * Persons use their own generators with the same key as the global one. + * Persons use their own generators with the same key as the global one. * @return The random number generator. */ RandomNumberGenerator& get_rng() @@ -288,7 +305,7 @@ class World } /** - * @brief Add a TestingScheme to the set of schemes that are checked for testing at all Locations that have + * @brief Add a TestingScheme to the set of schemes that are checked for testing at all Locations that have * the LocationType. * @param[in] loc_type LocationId key for TestingScheme to be added. * @param[in] scheme TestingScheme to be added. @@ -296,13 +313,140 @@ class World void add_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme); /** - * @brief Remove a TestingScheme from the set of schemes that are checked for testing at all Locations that have + * @brief Remove a TestingScheme from the set of schemes that are checked for testing at all Locations that have * the LocationType. * @param[in] loc_type LocationId key for TestingScheme to be added. * @param[in] scheme TestingScheme to be added. */ void remove_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme); + /** + * @brief Get a reference to a Person from this World. + * @param[in] id A person's PersonId. + * @return A reference to the Person. + * @{ + */ + Person& get_person(PersonId id) + { + assert(id.get() < m_persons.size() && "Given PersonId is not in this World."); + return m_persons[id.get()]; + } + + const Person& get_person(PersonId id) const + { + assert(id.get() < m_persons.size() && "Given PersonId is not in this World."); + return m_persons[id.get()]; + } + /** @} */ + + /** + * @brief Get the number of Person%s of a particular #InfectionState for all Cell%s. + * @param[in] location A LocationId from the world. + * @param[in] t TimePoint of querry. + * @param[in] state #InfectionState of interest. + * @return Amount of Person%s of the #InfectionState in all Cell%s of the Location. + */ + size_t get_subpopulation(LocationId location, TimePoint t, InfectionState state) const + { + return std::count_if(m_persons.begin(), m_persons.end(), [&](auto&& p) { + return p.get_location() == location && p.get_infection_state(t) == state; + }); + } + + /** + * @brief Get the total number of Person%s at the Location. + * @param[in] location A LocationId from the world. + * @return Number of Person%s in the location. + */ + size_t get_number_persons(LocationId location) const + { + if (!m_is_local_population_cache_valid) { + build_compute_local_population_cache(); + } + return m_local_population_cache[location.get()]; + } + + // move a person to another location. this requires that location is part of this world. + /** + * @brief Let a person move to another location. + * @param[in] person PersonId of a person from this world. + * @param[in] destination LocationId of the location in this world, which the person should move to. + * @param[in] mode The transport mode the person uses to move. + * @param[in] cells The cells within the destination the person should be in. + */ + inline void migrate(PersonId person, LocationId destination, TransportMode mode = TransportMode::Unknown, + const std::vector& cells = {0}) + { + LocationId origin = get_location(person).get_id(); + const bool has_moved = mio::abm::migrate(get_person(person), get_location(destination), mode, cells); + // if the person has moved, invalidate exposure caches but keep population caches valid + if (has_moved) { + m_are_exposure_caches_valid = false; + if (m_is_local_population_cache_valid) { + --m_local_population_cache[origin.get()]; + ++m_local_population_cache[destination.get()]; + } + } + } + + /** + * @brief Let a person interact with the population at its current location. + * @param[in] person PersonId of a person from this world. + * @param[in] t Time step of the simulation. + * @param[in] dt Step size of the simulation. + */ + inline void interact(PersonId person, TimePoint t, TimeSpan dt) + { + if (!m_are_exposure_caches_valid) { + // checking caches is only needed for external calls + // during simulation (i.e. in evolve()), the caches are computed in begin_step + compute_exposure_caches(t, dt); + m_are_exposure_caches_valid = true; + } + auto personal_rng = PersonalRandomNumberGenerator(m_rng, get_person(person)); + mio::abm::interact(personal_rng, get_person(person), get_location(person), + m_air_exposure_rates_cache[get_location(person).get_id().get()], + m_contact_exposure_rates_cache[get_location(person).get_id().get()], t, dt, parameters); + } + + /** + * @brief Get a reference to a location in this World. + * @param[in] id LocationId of the Location. + * @return Reference to the Location. + * @{ + */ + const Location& get_location(LocationId id) const + { + assert(id != LocationId::invalid_id() && "Given LocationId must be valid."); + assert(id < LocationId((uint32_t)m_locations.size()) && "Given LocationId is not in this World."); + return m_locations[id.get()]; + } + + Location& get_location(LocationId id) + { + assert(id != LocationId::invalid_id() && "Given LocationId must be valid."); + assert(id < LocationId((uint32_t)m_locations.size()) && "Given LocationId is not in this World."); + return m_locations[id.get()]; + } + /** @} */ + + /** + * @brief Get a reference to the location of a person. + * @param[in] id PersonId of a person. + * @return Reference to the Location. + * @{ + */ + inline Location& get_location(PersonId id) + { + return get_location(get_person(id).get_location()); + } + + inline const Location& get_location(PersonId id) const + { + return get_location(get_person(id).get_location()); + } + /** @} */ + private: /** * @brief Person%s interact at their Location and may become infected. @@ -317,14 +461,37 @@ class World */ void migration(TimePoint t, TimeSpan dt); - std::vector> m_persons; ///< Vector with pointers to every Person. - std::vector> m_locations; ///< Vector with pointers to every Location. + /// @brief Shape the cache and store how many Person%s are at any Location. Use from single thread! + void build_compute_local_population_cache() const; + + /// @brief Shape the air and contact exposure cache according to the current Location%s. + void build_exposure_caches(); + + /** + * @brief Store all air/contact exposures for the current simulation step. + * @param[in] t Current TimePoint of the simulation. + * @param[in] dt The duration of the simulation step. + */ + void compute_exposure_caches(TimePoint t, TimeSpan dt); + + mutable Eigen::Matrix + m_local_population_cache; ///< Current number of Persons in a given location. + Eigen::Matrix + m_air_exposure_rates_cache; ///< Cache for local exposure through droplets in #transmissions/day. + Eigen::Matrix + m_contact_exposure_rates_cache; ///< Cache for local exposure through contacts in #transmissions/day. + bool m_is_local_population_cache_valid = false; + bool m_are_exposure_caches_valid = false; + bool m_exposure_caches_need_rebuild = true; + + std::vector m_persons; ///< Vector of every Person. + std::vector m_locations; ///< Vector of every Location. std::bitset m_has_locations; ///< Flags for each LocationType, set if a Location of that type exists. TestingStrategy m_testing_strategy; ///< List of TestingScheme%s that are checked for testing. TripList m_trip_list; ///< List of all Trip%s the Person%s do. bool m_use_migration_rules; ///< Whether migration rules are considered. - std::vector>> m_migration_rules; ///< Rules that govern the migration between Location%s. diff --git a/cpp/simulations/abm.cpp b/cpp/simulations/abm.cpp index f35eb1bef4..78ce0f2a40 100644 --- a/cpp/simulations/abm.cpp +++ b/cpp/simulations/abm.cpp @@ -17,13 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "abm/abm.h" #include "abm/analyze_result.h" +#include "abm/common_abm_loggers.h" +#include "abm/household.h" +#include "abm/lockdown_rules.h" #include "memilio/io/result_io.h" +#include "memilio/math/interpolation.h" #include "memilio/utils/random_number_generator.h" #include "memilio/utils/uncertain_value.h" -#include "boost/filesystem.hpp" -#include "abm/common_abm_loggers.h" namespace fs = boost::filesystem; @@ -54,7 +55,7 @@ void assign_uniform_distribution(mio::UncertainValue<>& p, ScalarType min, Scala * The infection states are chosen randomly. They are distributed according to the probabilites set in the example. * @return random infection state */ -mio::abm::InfectionState determine_infection_state(mio::abm::Person::RandomNumberGenerator& rng, ScalarType exposed, +mio::abm::InfectionState determine_infection_state(mio::abm::PersonalRandomNumberGenerator& rng, ScalarType exposed, ScalarType infected_no_symptoms, ScalarType infected_symptoms, ScalarType recovered) { @@ -319,15 +320,15 @@ void create_assign_locations(mio::abm::World& world) // For the capacity we assume an area of 1.25 m^2 per person (https://doi.org/10.1371/journal.pone.0259037) and a // room height of 3 m auto event = world.add_location(mio::abm::LocationType::SocialEvent); - world.get_individualized_location(event).get_infection_parameters().set(100); - world.get_individualized_location(event).set_capacity(100, 375); + world.get_location(event).get_infection_parameters().set(100); + 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<>(); + auto probability = mio::UncertainValue<>(); assign_uniform_distribution(probability, 0.5, 1.0); auto test_type = mio::abm::AntigenTest(); @@ -342,11 +343,11 @@ void create_assign_locations(mio::abm::World& world) // (https://doi.org/10.1016/j.buildenv.2021.107926)) // For the ICUs we assume a capacity of 30 agents and the same volume. auto hospital = world.add_location(mio::abm::LocationType::Hospital); - world.get_individualized_location(hospital).get_infection_parameters().set(5); - world.get_individualized_location(hospital).set_capacity(584, 26242); + world.get_location(hospital).get_infection_parameters().set(5); + world.get_location(hospital).set_capacity(584, 26242); auto icu = world.add_location(mio::abm::LocationType::ICU); - world.get_individualized_location(icu).get_infection_parameters().set(5); - world.get_individualized_location(icu).set_capacity(30, 1350); + world.get_location(icu).get_infection_parameters().set(5); + world.get_location(icu).set_capacity(30, 1350); // Add schools, workplaces and shops. // At every school there are 600 students. The maximum contacs are 40. @@ -360,16 +361,16 @@ void create_assign_locations(mio::abm::World& world) // and a volume of 7200 cubic meters (10 m^2 per person (https://doi.org/10.1371/journal.pone.0259037) and 3 m // room height). auto shop = world.add_location(mio::abm::LocationType::BasicsShop); - world.get_individualized_location(shop).get_infection_parameters().set(20); - world.get_individualized_location(shop).set_capacity(240, 7200); + world.get_location(shop).get_infection_parameters().set(20); + world.get_location(shop).set_capacity(240, 7200); auto school = world.add_location(mio::abm::LocationType::School); - world.get_individualized_location(school).get_infection_parameters().set(40); - world.get_individualized_location(school).set_capacity(600, 3600); + world.get_location(school).get_infection_parameters().set(40); + world.get_location(school).set_capacity(600, 3600); auto work = world.add_location(mio::abm::LocationType::Work); - world.get_individualized_location(work).get_infection_parameters().set(40); - world.get_individualized_location(work).set_capacity(100, 3000); + world.get_location(work).get_infection_parameters().set(40); + world.get_location(work).set_capacity(100, 3000); int counter_event = 0; int counter_school = 0; @@ -378,47 +379,48 @@ void create_assign_locations(mio::abm::World& world) //Assign locations to the people auto persons = world.get_persons(); for (auto& person : persons) { + const auto id = person.get_id(); //assign shop and event - person.set_assigned_location(event); + world.assign_location(id, event); counter_event++; - person.set_assigned_location(shop); + world.assign_location(id, shop); counter_shop++; //assign hospital and ICU - person.set_assigned_location(hospital); - person.set_assigned_location(icu); + world.assign_location(id, hospital); + world.assign_location(id, icu); //assign work/school to people depending on their age if (person.get_age() == age_group_5_to_14) { - person.set_assigned_location(school); + world.assign_location(id, school); counter_school++; } if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { - person.set_assigned_location(work); + world.assign_location(id, work); counter_work++; } //add new school/work/shop if needed if (counter_event == 1000) { counter_event = 0; event = world.add_location(mio::abm::LocationType::SocialEvent); - world.get_individualized_location(event).set_capacity(100, 375); - world.get_individualized_location(event).get_infection_parameters().set(100); + world.get_location(event).set_capacity(100, 375); + world.get_location(event).get_infection_parameters().set(100); } if (counter_school == 600) { counter_school = 0; school = world.add_location(mio::abm::LocationType::School); - world.get_individualized_location(school).get_infection_parameters().set(40); - world.get_individualized_location(school).set_capacity(600, 3600); + world.get_location(school).get_infection_parameters().set(40); + world.get_location(school).set_capacity(600, 3600); } if (counter_work == 100) { counter_work = 0; work = world.add_location(mio::abm::LocationType::Work); - world.get_individualized_location(work).get_infection_parameters().set(40); - world.get_individualized_location(work).set_capacity(100, 3000); + world.get_location(work).get_infection_parameters().set(40); + world.get_location(work).set_capacity(100, 3000); } if (counter_shop == 15000) { counter_shop = 0; shop = world.add_location(mio::abm::LocationType::BasicsShop); - world.get_individualized_location(shop).get_infection_parameters().set(20); - world.get_individualized_location(shop).set_capacity(240, 7200); + world.get_location(shop).get_infection_parameters().set(20); + world.get_location(shop).set_capacity(240, 7200); } } @@ -448,7 +450,7 @@ void assign_infection_state(mio::abm::World& world, mio::abm::TimePoint t, doubl { auto persons = world.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::Person::RandomNumberGenerator(world.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(world.get_rng(), person); auto infection_state = determine_infection_state(rng, exposed_prob, infected_no_symptoms_prob, infected_symptoms_prob, recovered_prob); if (infection_state != mio::abm::InfectionState::Susceptible) { diff --git a/cpp/simulations/abm_braunschweig.cpp b/cpp/simulations/abm_braunschweig.cpp index 25f044acb7..7d6630f931 100644 --- a/cpp/simulations/abm_braunschweig.cpp +++ b/cpp/simulations/abm_braunschweig.cpp @@ -17,18 +17,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#include -#include -#include -#include "abm/abm.h" +#include "abm/common_abm_loggers.h" +#include "abm/location_id.h" +#include "abm/lockdown_rules.h" +#include "abm/person.h" +#include "abm/simulation.h" +#include "abm/world.h" +#include "memilio/epidemiology/age_group.h" +#include "memilio/io/io.h" #include "memilio/io/result_io.h" +#include "memilio/math/interpolation.h" #include "memilio/utils/uncertain_value.h" -#include "boost/filesystem.hpp" #include "boost/algorithm/string/split.hpp" #include "boost/algorithm/string/classification.hpp" -#include "abm/vaccine.h" -#include "abm/common_abm_loggers.h" + +#include +#include +#include namespace fs = boost::filesystem; @@ -59,7 +64,7 @@ void assign_uniform_distribution(mio::UncertainValue<>& p, ScalarType min, Scala * The infection states are chosen randomly. They are distributed according to the probabilites set in the example. * @return random infection state */ -mio::abm::InfectionState determine_infection_state(mio::abm::Person::RandomNumberGenerator& rng, ScalarType exposed, +mio::abm::InfectionState determine_infection_state(mio::abm::PersonalRandomNumberGenerator& rng, ScalarType exposed, ScalarType infected_no_symptoms, ScalarType infected_symptoms, ScalarType recovered) { @@ -82,7 +87,7 @@ void assign_infection_state(mio::abm::World& world, mio::abm::TimePoint t, doubl { auto persons = world.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::Person::RandomNumberGenerator(world.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(world.get_rng(), person); auto infection_state = determine_infection_state(rng, exposed_prob, infected_no_symptoms_prob, infected_symptoms_prob, recovered_prob); if (infection_state != mio::abm::InfectionState::Susceptible) @@ -215,9 +220,9 @@ void create_world_from_data(mio::abm::World& world, const std::string& filename, count_of_titles++; } - std::map locations = {}; - std::map persons = {}; - std::map person_ids = {}; + std::map locations = {}; + std::map pids_data_to_world = {}; + std::map person_ids = {}; std::map> locations_before; std::map> locations_after; @@ -225,13 +230,12 @@ void create_world_from_data(mio::abm::World& world, const std::string& filename, // We assume that no person goes to an hospital, altough e.g. "Sonstiges" could be a hospital auto hospital = world.add_location(mio::abm::LocationType::Hospital); - world.get_individualized_location(hospital).get_infection_parameters().set(5); - world.get_individualized_location(hospital).set_capacity(std::numeric_limits::max(), - std::numeric_limits::max()); + world.get_location(hospital).get_infection_parameters().set(5); + world.get_location(hospital).set_capacity(std::numeric_limits::max(), + std::numeric_limits::max()); auto icu = world.add_location(mio::abm::LocationType::ICU); - world.get_individualized_location(icu).get_infection_parameters().set(5); - world.get_individualized_location(icu).set_capacity(std::numeric_limits::max(), - std::numeric_limits::max()); + world.get_location(icu).get_infection_parameters().set(5); + world.get_location(icu).set_capacity(std::numeric_limits::max(), std::numeric_limits::max()); // First we determine the persons number and their starting locations int number_of_persons = 0; @@ -310,7 +314,7 @@ void create_world_from_data(mio::abm::World& world, const std::string& filename, locations.insert({home_id, home}); mio::abm::GeographicalLocation location_long_lat_home = {(double)row[index["lon_start"]] / 1e+5, (double)row[index["lat_start"]] / 1e+5}; - world.get_individualized_location(home).set_geographical_location(location_long_lat_home); + world.get_location(home).set_geographical_location(location_long_lat_home); } else { home = it_home->second; @@ -324,7 +328,7 @@ void create_world_from_data(mio::abm::World& world, const std::string& filename, get_location_type(activity_end), 1); // Assume one place has one activity, this may be untrue but not important for now(?) locations.insert({target_location_id, location}); - world.get_individualized_location(location).set_geographical_location(location_long_lat); + world.get_location(location).set_geographical_location(location_long_lat); } } fin.clear(); @@ -339,8 +343,8 @@ void create_world_from_data(mio::abm::World& world, const std::string& filename, split_line(line, &row); line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); - uint32_t person_id = row[index["puid"]]; - if (person_ids.find(person_id) == person_ids.end()) + uint32_t person_data_id = row[index["puid"]]; + if (person_ids.find(person_data_id) == person_ids.end()) break; uint32_t age = row[index["age"]]; @@ -355,34 +359,33 @@ void create_world_from_data(mio::abm::World& world, const std::string& filename, auto target_location = locations.find(target_location_id)->second; auto start_location = locations.find(start_location_id)->second; - auto it_person = persons.find(person_id); + auto pid_itr = pids_data_to_world.find(person_data_id); - if (it_person == persons.end()) { - auto it_first_location_id = locations_before.find(person_id); + if (pid_itr == pids_data_to_world.end()) { // person has not been added to world yet + auto it_first_location_id = locations_before.find(person_data_id); if (it_first_location_id == locations_before.end()) { - it_first_location_id = locations_after.find(person_id); + it_first_location_id = locations_after.find(person_data_id); } auto first_location_id = it_first_location_id->second.first; auto first_location = locations.find(first_location_id)->second; - auto& person = world.add_person(first_location, determine_age_group(age)); + auto person_world_id = world.add_person(first_location, determine_age_group(age)); auto home = locations.find(home_id)->second; - person.set_assigned_location(home); - person.set_assigned_location(hospital); - person.set_assigned_location(icu); - persons.insert({person_id, person}); - it_person = persons.find(person_id); + world.assign_location(person_world_id, home); + world.assign_location(person_world_id, hospital); + world.assign_location(person_world_id, icu); + pid_itr = pids_data_to_world.insert_or_assign(person_data_id, person_world_id).first; } - it_person->second.set_assigned_location( + world.assign_location( + pid_itr->second, target_location); //This assumes that we only have in each tripchain only one location type for each person if (locations.find(start_location_id) == locations.end()) { // For trips where the start location is not known use Home instead - start_location = {it_person->second.get_assigned_location_index(mio::abm::LocationType::Home), - mio::abm::LocationType::Home}; + start_location = world.get_person(pid_itr->second).get_assigned_location(mio::abm::LocationType::Home); } world.get_trip_list().add_trip(mio::abm::Trip( - it_person->second.get_person_id(), mio::abm::TimePoint(0) + mio::abm::minutes(trip_start), target_location, - start_location, mio::abm::TransportMode(transport_mode), mio::abm::ActivityType(acticity_end))); + pid_itr->second, mio::abm::TimePoint(0) + mio::abm::minutes(trip_start), target_location, start_location, + mio::abm::TransportMode(transport_mode), mio::abm::ActivityType(acticity_end))); } world.get_trip_list().use_weekday_trips_on_weekend(); } @@ -953,11 +956,11 @@ void write_log_to_file_trip_data(const T& history) for (uint32_t movement_data_index = 2; movement_data_index < movement_data.size(); ++movement_data_index) { myfile3 << "timestep Nr.: " << movement_data_index - 1 << "\n"; for (uint32_t trip_index = 0; trip_index < movement_data[movement_data_index].size(); trip_index++) { - auto agent_id = (int)std::get<0>(movement_data[movement_data_index][trip_index]); + auto agent_id = std::get<0>(movement_data[movement_data_index][trip_index]); int start_index = movement_data_index - 1; - using Type = std::tuple; + using Type = std::tuple; while (!std::binary_search(std::begin(movement_data[start_index]), std::end(movement_data[start_index]), movement_data[movement_data_index][trip_index], [](const Type& v1, const Type& v2) { @@ -965,14 +968,14 @@ void write_log_to_file_trip_data(const T& history) })) { start_index--; } - auto start_location_pointer = + auto start_location_iterator = std::lower_bound(std::begin(movement_data[start_index]), std::end(movement_data[start_index]), movement_data[movement_data_index][trip_index], [](const Type& v1, const Type& v2) { return std::get<0>(v1) < std::get<0>(v2); }); - int start_location = (int)std::get<1>(*start_location_pointer); + auto start_location = (int)std::get<1>(*start_location_iterator).get(); - auto end_location = (int)std::get<1>(movement_data[movement_data_index][trip_index]); + auto end_location = (int)std::get<1>(movement_data[movement_data_index][trip_index]).get(); auto start_time = (int)std::get<2>(movement_data[movement_data_index][trip_index]).seconds(); auto end_time = (int)std::get<2>(movement_data[movement_data_index][trip_index]).seconds(); @@ -1016,7 +1019,7 @@ mio::IOResult run(const std::string& input_file, const fs::path& result_di // Collect the id of location in world. std::vector loc_ids; for (auto& location : sim.get_world().get_locations()) { - loc_ids.push_back(location.get_index()); + loc_ids.push_back(location.get_id().get()); } // Advance the world to tmax sim.advance(tmax, historyPersonInf, historyTimeSeries, historyPersonInfDelta); diff --git a/cpp/tests/abm_helpers.cpp b/cpp/tests/abm_helpers.cpp index 5c10c4828b..85f9edf765 100644 --- a/cpp/tests/abm_helpers.cpp +++ b/cpp/tests/abm_helpers.cpp @@ -26,25 +26,45 @@ mio::abm::Person make_test_person(mio::abm::Location& location, mio::AgeGroup ag mio::abm::Parameters params) { assert(age.get() < params.get_num_groups()); - auto rng = mio::RandomNumberGenerator(); - mio::abm::Person p = mio::abm::Person(rng, location, age); + auto rng = mio::RandomNumberGenerator(); + mio::abm::Person p(rng, location.get_type(), location.get_id(), age); if (infection_state != mio::abm::InfectionState::Susceptible) { - auto rng_p = mio::abm::Person::RandomNumberGenerator(rng, p); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); p.add_new_infection( mio::abm::Infection(rng_p, static_cast(0), age, params, t, infection_state)); } return p; } -mio::abm::Person& add_test_person(mio::abm::World& world, mio::abm::LocationId loc_id, mio::AgeGroup age, - mio::abm::InfectionState infection_state, mio::abm::TimePoint t) +mio::abm::PersonId add_test_person(mio::abm::World& world, mio::abm::LocationId loc_id, mio::AgeGroup age, + mio::abm::InfectionState infection_state, mio::abm::TimePoint t) { - assert(age.get() < world.parameters.get_num_groups()); - mio::abm::Person& p = world.add_person(loc_id, age); - if (infection_state != mio::abm::InfectionState::Susceptible) { - auto rng_p = mio::abm::Person::RandomNumberGenerator(world.get_rng(), p); - p.add_new_infection(mio::abm::Infection(rng_p, static_cast(0), age, world.parameters, t, - infection_state)); + return world.add_person(make_test_person(world.get_location(loc_id), age, infection_state, t, world.parameters)); +} + +void interact_testing(mio::abm::PersonalRandomNumberGenerator& personal_rng, mio::abm::Person& person, + const mio::abm::Location& location, const std::vector& local_population, + const mio::abm::TimePoint t, const mio::abm::TimeSpan dt, + const mio::abm::Parameters& global_parameters) +{ + // allocate and initialize air exposures with 0 + mio::abm::AirExposureRates local_air_exposure; + local_air_exposure.resize({mio::abm::CellIndex(location.get_cells().size()), mio::abm::VirusVariant::Count}); + std::for_each(local_air_exposure.begin(), local_air_exposure.end(), [](auto& r) { + r = 0.0; + }); + // allocate and initialize contact exposures with 0 + mio::abm::ContactExposureRates local_contact_exposure; + local_contact_exposure.resize({mio::abm::CellIndex(location.get_cells().size()), mio::abm::VirusVariant::Count, + mio::AgeGroup(global_parameters.get_num_groups())}); + std::for_each(local_contact_exposure.begin(), local_contact_exposure.end(), [](auto& r) { + r = 0.0; + }); + // caclculate current exposures + for (const mio::abm::Person& p : local_population) { + add_exposure_contribution(local_air_exposure, local_contact_exposure, p, location, t, dt); } - return p; + // run interaction + mio::abm::interact(personal_rng, person, location, local_air_exposure, local_contact_exposure, t, dt, + global_parameters); } diff --git a/cpp/tests/abm_helpers.h b/cpp/tests/abm_helpers.h index 787b4cadde..84bb208f8f 100644 --- a/cpp/tests/abm_helpers.h +++ b/cpp/tests/abm_helpers.h @@ -20,14 +20,9 @@ #ifndef ABM_HELPERS_H #define ABM_HELPERS_H -#include "abm/abm.h" -#include "abm/virus_variant.h" -#include "memilio/math/eigen_util.h" -#include "memilio/epidemiology/age_group.h" -#include "matchers.h" -#include "gtest/gtest.h" +#include "abm/world.h" + #include "gmock/gmock.h" -#include // Assign the name to general age group. const size_t num_age_groups = 6; @@ -96,7 +91,7 @@ struct ScopedMockDistribution { /** * @brief Create a Person without a World object. Intended for simple use in tests. -*/ + */ mio::abm::Person make_test_person(mio::abm::Location& location, mio::AgeGroup age = age_group_15_to_34, mio::abm::InfectionState infection_state = mio::abm::InfectionState::Susceptible, mio::abm::TimePoint t = mio::abm::TimePoint(0), @@ -104,10 +99,16 @@ mio::abm::Person make_test_person(mio::abm::Location& location, mio::AgeGroup ag /** * @brief Add a Person to the World. Intended for simple use in tests. -*/ -mio::abm::Person& add_test_person(mio::abm::World& world, mio::abm::LocationId loc_id, - mio::AgeGroup age = age_group_15_to_34, - mio::abm::InfectionState infection_state = mio::abm::InfectionState::Susceptible, - mio::abm::TimePoint t = mio::abm::TimePoint(0)); + */ +mio::abm::PersonId add_test_person(mio::abm::World& world, mio::abm::LocationId loc_id, + mio::AgeGroup age = age_group_15_to_34, + mio::abm::InfectionState infection_state = mio::abm::InfectionState::Susceptible, + mio::abm::TimePoint t = mio::abm::TimePoint(0)); + +/// @brief Calls mio::abm::interact, but it computes the correct exposures for you. +void interact_testing(mio::abm::PersonalRandomNumberGenerator& personal_rng, mio::abm::Person& person, + const mio::abm::Location& location, const std::vector& local_population, + const mio::abm::TimePoint t, const mio::abm::TimeSpan dt, + const mio::abm::Parameters& global_parameters); #endif //ABM_HELPERS_H diff --git a/cpp/tests/test_abm_household.cpp b/cpp/tests/test_abm_household.cpp index a04b78e126..4b3ef64c91 100644 --- a/cpp/tests/test_abm_household.cpp +++ b/cpp/tests/test_abm_household.cpp @@ -18,7 +18,6 @@ * limitations under the License. */ #include "abm/household.h" -#include "abm/abm.h" #include "abm_helpers.h" #include @@ -49,8 +48,8 @@ TEST(TestHouseholds, test_add_household_to_world) EXPECT_EQ(persons[3].get_age(), age_group_5_to_14); // Test location - EXPECT_EQ(persons[0].get_location().get_index(), persons[1].get_location().get_index()); - EXPECT_EQ(persons[2].get_location().get_index(), persons[3].get_location().get_index()); + EXPECT_EQ(persons[0].get_location(), persons[1].get_location()); + EXPECT_EQ(persons[2].get_location(), persons[3].get_location()); } TEST(TestHouseholds, test_add_household_group_to_world) @@ -97,11 +96,11 @@ TEST(TestHouseholds, test_add_household_group_to_world) EXPECT_EQ(number_of_age35to59_year_olds, 70); // Test location for some people - EXPECT_EQ(persons[0].get_location().get_index(), persons[1].get_location().get_index()); - EXPECT_EQ(persons[1].get_location().get_index(), persons[5].get_location().get_index()); - EXPECT_EQ(persons[5].get_location().get_index(), persons[10].get_location().get_index()); + EXPECT_EQ(persons[0].get_location(), persons[1].get_location()); + EXPECT_EQ(persons[1].get_location(), persons[5].get_location()); + EXPECT_EQ(persons[5].get_location(), persons[10].get_location()); - EXPECT_EQ(persons[60].get_location().get_index(), persons[61].get_location().get_index()); - EXPECT_EQ(persons[61].get_location().get_index(), persons[62].get_location().get_index()); - EXPECT_EQ(persons[62].get_location().get_index(), persons[63].get_location().get_index()); + EXPECT_EQ(persons[60].get_location(), persons[61].get_location()); + EXPECT_EQ(persons[61].get_location(), persons[62].get_location()); + EXPECT_EQ(persons[62].get_location(), persons[63].get_location()); } diff --git a/cpp/tests/test_abm_infection.cpp b/cpp/tests/test_abm_infection.cpp index bb6df0c064..58d8425e2a 100644 --- a/cpp/tests/test_abm_infection.cpp +++ b/cpp/tests/test_abm_infection.cpp @@ -21,6 +21,7 @@ #include "abm/location_type.h" #include "abm/person.h" #include "abm_helpers.h" +#include "memilio/math/interpolation.h" #include "memilio/utils/random_number_generator.h" #include "abm_helpers.h" @@ -34,7 +35,7 @@ TEST(TestInfection, init) //uses uniformdistribution but result doesn't matter, so init before the mock mio::abm::Location loc(mio::abm::LocationType::Hospital, 0); auto counter = mio::Counter(0); - auto rng = mio::abm::Person::RandomNumberGenerator(mio::Key{0}, 0, counter); + auto rng = mio::abm::PersonalRandomNumberGenerator(mio::Key{0}, mio::abm::PersonId(0), counter); ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) @@ -100,7 +101,7 @@ TEST(TestInfection, init) TEST(TestInfection, getInfectionState) { auto counter = mio::Counter(0); - auto rng = mio::abm::Person::RandomNumberGenerator(mio::Key{0}, 0, counter); + auto rng = mio::abm::PersonalRandomNumberGenerator(mio::Key{0}, mio::abm::PersonId(0), counter); auto params = mio::abm::Parameters(num_age_groups); auto t = mio::abm::TimePoint(0); auto infection = mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, age_group_15_to_34, params, t, @@ -112,7 +113,7 @@ TEST(TestInfection, getInfectionState) TEST(TestInfection, drawInfectionCourseBackward) { auto counter = mio::Counter(0); - auto rng = mio::abm::Person::RandomNumberGenerator(mio::Key{0}, 0, counter); + auto rng = mio::abm::PersonalRandomNumberGenerator(mio::Key{0}, mio::abm::PersonId(0), counter); auto t = mio::abm::TimePoint(1); auto dt = mio::abm::days(1); @@ -166,7 +167,7 @@ TEST(TestInfection, getPersonalProtectiveFactor) auto rng = mio::RandomNumberGenerator(); auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(rng, location, age_group_15_to_34); + auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_15_to_34); person.add_new_vaccination(mio::abm::ExposureType::GenericVaccine, mio::abm::TimePoint(0)); auto latest_protection = person.get_latest_protection(); diff --git a/cpp/tests/test_abm_location.cpp b/cpp/tests/test_abm_location.cpp index 474a92e893..2e95746ca9 100644 --- a/cpp/tests/test_abm_location.cpp +++ b/cpp/tests/test_abm_location.cpp @@ -18,36 +18,12 @@ * limitations under the License. */ -#include "abm/infection.h" +#include "abm/location_id.h" +#include "abm/parameters.h" #include "abm/person.h" +#include "abm/world.h" #include "abm_helpers.h" #include "memilio/utils/random_number_generator.h" -#include - -TEST(TestLocation, init) -{ - mio::abm::Location location(mio::abm::LocationType::School, 0, num_age_groups); - for (mio::abm::InfectionState i = mio::abm::InfectionState(0); i < mio::abm::InfectionState::Count; - i = mio::abm::InfectionState(size_t(i) + 1)) { - ASSERT_EQ(location.get_subpopulation(mio::abm::TimePoint(0), i), 0); - } - EXPECT_EQ(location.get_number_persons(), 0); -} - -TEST(TestLocation, copyLocation) -{ - auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = make_test_person(location, age_group_5_to_14, mio::abm::InfectionState::InfectedSymptoms); - EXPECT_EQ(location.get_number_persons(), 0); - location.add_person(person); - EXPECT_EQ(location.get_number_persons(), 1); - - auto copied_location = location.copy_location_without_persons(num_age_groups); - ASSERT_EQ(copied_location.get_type(), mio::abm::LocationType::School); - ASSERT_EQ(copied_location.get_index(), location.get_index()); - ASSERT_EQ(copied_location.get_cells().size(), location.get_cells().size()); - EXPECT_EQ(copied_location.get_number_persons(), 0); -} TEST(TestLocation, initCell) { @@ -55,98 +31,10 @@ TEST(TestLocation, initCell) ASSERT_EQ(location.get_cells().size(), 2); } -TEST(TestLocation, getIndex) +TEST(TestLocation, getId) { mio::abm::Location location(mio::abm::LocationType::Home, 0, num_age_groups); - ASSERT_EQ((int)location.get_index(), 0); -} - -TEST(TestLocation, addRemovePerson) -{ - mio::abm::Location home(mio::abm::LocationType::Home, 0, 6, 1); - mio::abm::Location location(mio::abm::LocationType::PublicTransport, 0, 6, 3); - - auto person1 = make_test_person(home, age_group_5_to_14, mio::abm::InfectionState::InfectedSymptoms); - auto person2 = make_test_person(home, age_group_5_to_14, mio::abm::InfectionState::InfectedSymptoms); - auto person3 = make_test_person(home, age_group_35_to_59, mio::abm::InfectionState::Exposed); - - home.add_person(person1, {0}); - home.add_person(person2, {0}); - home.add_person(person3, {0}); - - person1.migrate_to(location, {0, 1}); - person2.migrate_to(location, {0}); - person3.migrate_to(location, {0, 1}); - - auto t = mio::abm::TimePoint(0); - ASSERT_EQ(home.get_number_persons(), 0u); - ASSERT_EQ(location.get_subpopulation(t, mio::abm::InfectionState::InfectedSymptoms), 2); - ASSERT_EQ(location.get_subpopulation(t, mio::abm::InfectionState::Exposed), 1); - ASSERT_EQ(location.get_cells()[0].m_persons.size(), 3u); - ASSERT_EQ(location.get_cells()[1].m_persons.size(), 2u); - ASSERT_EQ(location.get_cells()[2].m_persons.size(), 0u); - - location.remove_person(person2); - - EXPECT_EQ(location.get_number_persons(), 2u); - ASSERT_EQ(location.get_subpopulation(t, mio::abm::InfectionState::InfectedSymptoms), 1); - ASSERT_EQ(location.get_subpopulation(t, mio::abm::InfectionState::Exposed), 1); - ASSERT_EQ(location.get_cells()[0].m_persons.size(), 2u); - ASSERT_EQ(location.get_cells()[1].m_persons.size(), 2u); - ASSERT_EQ(location.get_cells()[2].m_persons.size(), 0u); -} - -TEST(TestLocation, CacheExposureRate) -{ - using testing::Return; - - auto rng = mio::RandomNumberGenerator(); - - mio::AgeGroup age = - mio::AgeGroup(mio::UniformIntDistribution::get_instance()(rng, 0, int(num_age_groups - 1))); - mio::abm::VirusVariant variant = mio::abm::VirusVariant( - mio::UniformIntDistribution::get_instance()(rng, 0, int(mio::abm::VirusVariant::Count) - 1)); - - auto t = mio::abm::TimePoint(0); - auto dt = mio::abm::seconds(10000); - - mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); - - // setup a location with some chance of exposure - mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups, 1); - mio::abm::Location location(mio::abm::LocationType::PublicTransport, 0, num_age_groups, 3); - auto infected1 = mio::abm::Person(rng, home, age); - auto rng_infected1 = mio::abm::Person::RandomNumberGenerator(rng, infected1); - infected1.add_new_infection( - mio::abm::Infection(rng_infected1, variant, age, params, t, mio::abm::InfectionState::InfectedNoSymptoms)); - infected1.migrate_to(location, {0}); - auto infected2 = mio::abm::Person(rng, home, age); - auto rng_infected2 = mio::abm::Person::RandomNumberGenerator(rng, infected2); - infected2.add_new_infection( - mio::abm::Infection(rng_infected2, variant, age, params, t, mio::abm::InfectionState::InfectedNoSymptoms)); - infected2.migrate_to(location, {0, 1}); - - //cache precomputed results - location.cache_exposure_rates(t, dt, num_age_groups); - - EXPECT_NEAR((location.get_cells()[0].m_cached_exposure_rate_contacts[{variant, age}]), 0.015015859523894731, 1e-14); - EXPECT_NEAR((location.get_cells()[0].m_cached_exposure_rate_air[{variant}]), 0.015015859523894731, 1e-14); - EXPECT_NEAR((location.get_cells()[1].m_cached_exposure_rate_contacts[{variant, age}]), 0.0075079297619473654, - 1e-14); - EXPECT_NEAR((location.get_cells()[1].m_cached_exposure_rate_air[{variant}]), 0.0075079297619473654, 1e-14); - EXPECT_NEAR((location.get_cells()[2].m_cached_exposure_rate_contacts[{variant, age}]), 0, 1e-14); - EXPECT_NEAR((location.get_cells()[2].m_cached_exposure_rate_air[{variant}]), 0, 1e-14); - - // should also work with capacities - location.set_capacity_adapted_transmission_risk_flag(true); - location.set_capacity(2, 22, 0); // Capacity for Cell 1 - location.set_capacity(2, 22, 1); // Capacity for Cell 2 - location.set_capacity(2, 22, 2); // Capacity for Cell 3 - location.cache_exposure_rates(t, dt, num_age_groups); - - EXPECT_NEAR((location.get_cells()[0].m_cached_exposure_rate_air[{variant}]), 0.045047578571684191, 1e-14); - EXPECT_NEAR((location.get_cells()[1].m_cached_exposure_rate_air[{variant}]), 0.022523789285842095, 1e-14); - EXPECT_NEAR((location.get_cells()[2].m_cached_exposure_rate_air[{variant}]), 0, 1e-14); + ASSERT_EQ(location.get_id(), mio::abm::LocationId(0)); } TEST(TestLocation, reachCapacity) @@ -183,18 +71,15 @@ TEST(TestLocation, reachCapacity) .WillOnce(testing::Return(0.8)) // draw random school hour .WillRepeatedly(testing::Return(1.0)); - auto& p1 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::InfectedNoSymptoms); - auto& p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible); + auto p1 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::InfectedNoSymptoms); + auto p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible); - auto& home = world.get_individualized_location(home_id); - auto& school = world.get_individualized_location(school_id); + world.get_person(p1).set_assigned_location(mio::abm::LocationType::School, school_id); + world.get_person(p2).set_assigned_location(mio::abm::LocationType::School, school_id); + world.get_person(p1).set_assigned_location(mio::abm::LocationType::Home, home_id); + world.get_person(p2).set_assigned_location(mio::abm::LocationType::Home, home_id); - p1.set_assigned_location(school_id); - p2.set_assigned_location(school_id); - p1.set_assigned_location(home_id); - p2.set_assigned_location(home_id); - - school.set_capacity(1, 66); + world.get_location(school_id).set_capacity(1, 66); ScopedMockDistribution>>> mock_exponential_dist; @@ -202,10 +87,10 @@ TEST(TestLocation, reachCapacity) world.evolve(t, dt); - ASSERT_EQ(p1.get_location(), school); - ASSERT_EQ(p2.get_location(), home); // p2 should not be able to enter the school - ASSERT_EQ(school.get_number_persons(), 1); - ASSERT_EQ(home.get_number_persons(), 1); + ASSERT_EQ(world.get_person(p1).get_location(), school_id); + ASSERT_EQ(world.get_person(p2).get_location(), home_id); // p2 should not be able to enter the school + ASSERT_EQ(world.get_number_persons(school_id), 1); + ASSERT_EQ(world.get_number_persons(home_id), 1); } TEST(TestLocation, computeSpacePerPersonRelative) @@ -255,13 +140,7 @@ TEST(TestLocation, interact) make_test_person(location, age_group_80_plus, mio::abm::InfectionState::InfectedSymptoms, t, params); auto infected3 = make_test_person(location, age_group_5_to_14, mio::abm::InfectionState::InfectedSymptoms, t, params); - - location.add_person(infected1, {0}); - location.add_person(infected2, {0}); - location.add_person(infected3, {0}); - - //cache precomputed results - location.cache_exposure_rates(t, dt, num_age_groups); + std::vector local_population{infected1, infected2, infected3}; ScopedMockDistribution>>> mock_exponential_dist; @@ -269,13 +148,13 @@ TEST(TestLocation, interact) auto susceptible = make_test_person(location, age, mio::abm::InfectionState::Susceptible, t, params); EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(Return(0.5)); - auto person_rng = mio::abm::Person::RandomNumberGenerator(rng, susceptible); - location.interact(person_rng, susceptible, t, dt, params); + auto person_rng = mio::abm::PersonalRandomNumberGenerator(rng, susceptible); + interact_testing(person_rng, susceptible, location, local_population, t, dt, params); EXPECT_EQ(susceptible.get_infection_state(t + dt), mio::abm::InfectionState::Susceptible); EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(Return(0.05)); EXPECT_CALL(mock_discrete_dist.get_mock(), invoke).Times(1).WillOnce(Return(0)); - location.interact(person_rng, susceptible, t, dt, params); + interact_testing(person_rng, susceptible, location, local_population, t, dt, params); EXPECT_EQ(susceptible.get_infection_state(t + dt), mio::abm::InfectionState::Exposed); } diff --git a/cpp/tests/test_abm_lockdown_rules.cpp b/cpp/tests/test_abm_lockdown_rules.cpp index 2124f5ac6d..fff125d1e4 100644 --- a/cpp/tests/test_abm_lockdown_rules.cpp +++ b/cpp/tests/test_abm_lockdown_rules.cpp @@ -17,6 +17,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/lockdown_rules.h" +#include "abm/migration_rules.h" #include "abm/person.h" #include "abm_helpers.h" #include "memilio/utils/random_number_generator.h" @@ -29,7 +31,7 @@ TEST(TestLockdownRules, school_closure) auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(6); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); + mio::abm::Location school(mio::abm::LocationType::School, 1, num_age_groups); //setup rng mock so one person is home schooled and the other goes to school ScopedMockDistribution>>> mock_uniform_dist; @@ -45,25 +47,25 @@ TEST(TestLockdownRules, school_closure) .WillOnce(testing::Return(0.2)) .WillRepeatedly(testing::Return(1.0)); - auto p1 = mio::abm::Person(rng, home, age_group_5_to_14); - p1.set_assigned_location(home); - p1.set_assigned_location(school); - auto p2 = mio::abm::Person(rng, home, age_group_5_to_14); - p2.set_assigned_location(home); - p2.set_assigned_location(school); + auto p1 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + p1.set_assigned_location(home.get_type(), home.get_id()); + p1.set_assigned_location(school.get_type(), school.get_id()); + auto p2 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + p2.set_assigned_location(home.get_type(), home.get_id()); + p2.set_assigned_location(school.get_type(), school.get_id()); 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; mio::abm::set_school_closure(t, 0.7, params); - auto p1_rng = mio::abm::Person::RandomNumberGenerator(rng, p1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(rng, p1); ASSERT_EQ(mio::abm::go_to_school(p1_rng, p1, t_morning, dt, params), mio::abm::LocationType::Home); - auto p2_rng = mio::abm::Person::RandomNumberGenerator(rng, p2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(rng, p2); ASSERT_EQ(mio::abm::go_to_school(p2_rng, p2, t_morning, dt, params), mio::abm::LocationType::School); } @@ -76,7 +78,7 @@ TEST(TestLockdownRules, school_opening) auto t_morning = mio::abm::TimePoint(0) + mio::abm::days(1) + mio::abm::hours(7); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); + mio::abm::Location school(mio::abm::LocationType::School, 1, num_age_groups); //setup rng mock so the person is homeschooled in case of lockdown ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) @@ -86,21 +88,21 @@ TEST(TestLockdownRules, school_opening) .WillOnce(testing::Return(0.6)) .WillOnce(testing::Return(0.6)) .WillRepeatedly(testing::Return(1.0)); - auto p = mio::abm::Person(rng, home, age_group_5_to_14); - p.set_assigned_location(home); - p.set_assigned_location(school); + auto p = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + p.set_assigned_location(home.get_type(), home.get_id()); + p.set_assigned_location(school.get_type(), school.get_id()); 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; mio::abm::set_school_closure(t_closing, 1., params); mio::abm::set_school_closure(t_opening, 0., params); - auto p_rng = mio::abm::Person::RandomNumberGenerator(rng, p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, p); ASSERT_EQ(mio::abm::go_to_school(p_rng, p, t_morning, dt, params), mio::abm::LocationType::School); } @@ -112,14 +114,14 @@ TEST(TestLockdownRules, home_office) auto dt = mio::abm::hours(1); mio::abm::Location home(mio::abm::LocationType::Home, 0); - mio::abm::Location work(mio::abm::LocationType::Work, 0); + mio::abm::Location work(mio::abm::LocationType::Work, 1); mio::abm::Parameters params(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; @@ -135,16 +137,16 @@ TEST(TestLockdownRules, home_office) .WillOnce(testing::Return(0.7)) .WillRepeatedly(testing::Return(1.0)); - auto person1 = mio::abm::Person(rng, home, age_group_15_to_34); - auto person2 = mio::abm::Person(rng, home, age_group_15_to_34); - person1.set_assigned_location(home); - person1.set_assigned_location(work); - person2.set_assigned_location(home); - person2.set_assigned_location(work); + auto person1 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto person2 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + person1.set_assigned_location(home.get_type(), home.get_id()); + person1.set_assigned_location(work.get_type(), work.get_id()); + person2.set_assigned_location(home.get_type(), home.get_id()); + person2.set_assigned_location(work.get_type(), work.get_id()); - auto p1_rng = mio::abm::Person::RandomNumberGenerator(rng, person1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(rng, person1); ASSERT_EQ(mio::abm::go_to_work(p1_rng, person1, t_morning, dt, params), mio::abm::LocationType::Work); - auto p2_rng = mio::abm::Person::RandomNumberGenerator(rng, person2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(rng, person2); ASSERT_EQ(mio::abm::go_to_work(p2_rng, person2, t_morning, dt, params), mio::abm::LocationType::Home); } @@ -168,22 +170,22 @@ TEST(TestLockdownRules, no_home_office) .WillOnce(testing::Return(0.7)) .WillRepeatedly(testing::Return(1.0)); - auto p = mio::abm::Person(rng, home, age_group_15_to_34); - p.set_assigned_location(home); - p.set_assigned_location(work); + auto p = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + p.set_assigned_location(home.get_type(), home.get_id()); + p.set_assigned_location(work.get_type(), work.get_id()); 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; mio::abm::set_home_office(t_closing, 0.5, params); mio::abm::set_home_office(t_opening, 0., params); - auto p_rng = mio::abm::Person::RandomNumberGenerator(rng, p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, p); ASSERT_EQ(mio::abm::go_to_work(p_rng, p, t_morning, dt, params), mio::abm::LocationType::Work); } @@ -195,15 +197,15 @@ TEST(TestLockdownRules, social_event_closure) auto t_evening = mio::abm::TimePoint(0) + mio::abm::hours(19); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location event(mio::abm::LocationType::SocialEvent, 0, num_age_groups); - auto p = mio::abm::Person(rng, home, age_group_5_to_14); - p.set_assigned_location(home); - p.set_assigned_location(event); + mio::abm::Location event(mio::abm::LocationType::SocialEvent, 1, num_age_groups); + auto p = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + p.set_assigned_location(home.get_type(), home.get_id()); + p.set_assigned_location(event.get_type(), event.get_id()); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); mio::abm::close_social_events(t, 1, params); - auto p_rng = mio::abm::Person::RandomNumberGenerator(rng, p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, p); ASSERT_EQ(mio::abm::go_to_event(p_rng, p, t_evening, dt, params), mio::abm::LocationType::Home); } @@ -216,10 +218,10 @@ TEST(TestLockdownRules, social_events_opening) auto t_evening = mio::abm::TimePoint(0) + mio::abm::days(1) + mio::abm::hours(19); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location event(mio::abm::LocationType::SocialEvent, 0, num_age_groups); - auto p = mio::abm::Person(rng, home, age_group_5_to_14); - p.set_assigned_location(event); - p.set_assigned_location(home); + mio::abm::Location event(mio::abm::LocationType::SocialEvent, 1, num_age_groups); + auto p = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + p.set_assigned_location(event.get_type(), event.get_id()); + p.set_assigned_location(home.get_type(), home.get_id()); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); mio::abm::close_social_events(t_closing, 1, params); @@ -228,6 +230,6 @@ TEST(TestLockdownRules, social_events_opening) ScopedMockDistribution>>> mock_exponential_dist; EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(testing::Return(0.01)); - auto p_rng = mio::abm::Person::RandomNumberGenerator(rng, p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, p); ASSERT_EQ(mio::abm::go_to_event(p_rng, p, t_evening, dt, params), mio::abm::LocationType::SocialEvent); } diff --git a/cpp/tests/test_abm_masks.cpp b/cpp/tests/test_abm_masks.cpp index dd0fa484af..5266d34f7a 100644 --- a/cpp/tests/test_abm_masks.cpp +++ b/cpp/tests/test_abm_masks.cpp @@ -65,16 +65,15 @@ TEST(TestMasks, maskProtection) //setup location with some chance of exposure auto t = mio::abm::TimePoint(0); mio::abm::Location infection_location(mio::abm::LocationType::School, 0, num_age_groups); - auto susc_person1 = mio::abm::Person(rng, infection_location, age_group_15_to_34); - auto susc_person2 = mio::abm::Person(rng, infection_location, age_group_15_to_34); - auto infected1 = make_test_person(infection_location, age_group_15_to_34, - mio::abm::InfectionState::InfectedSymptoms, t, params); // infected 7 days prior - - infection_location.add_person(infected1); + auto susc_person1 = + mio::abm::Person(rng, infection_location.get_type(), infection_location.get_id(), age_group_15_to_34); + auto susc_person2 = + mio::abm::Person(rng, infection_location.get_type(), infection_location.get_id(), age_group_15_to_34); + auto infected1 = make_test_person(infection_location, age_group_15_to_34, + mio::abm::InfectionState::InfectedSymptoms, t, params); // infected 7 days prior //cache precomputed results auto dt = mio::abm::days(1); - infection_location.cache_exposure_rates(t, dt, num_age_groups); // susc_person1 wears a mask, default protection is 1 susc_person1.set_wear_mask(true); // susc_person2 does not wear a mask @@ -84,11 +83,11 @@ TEST(TestMasks, maskProtection) ScopedMockDistribution>>> mock_exponential_dist; - auto p1_rng = mio::abm::Person::RandomNumberGenerator(rng, susc_person1); - infection_location.interact(p1_rng, susc_person1, t, dt, params); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(rng, susc_person1); + interact_testing(p1_rng, susc_person1, infection_location, {susc_person1, susc_person2, infected1}, t, dt, params); EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillOnce(testing::Return(0.5)); - auto p2_rng = mio::abm::Person::RandomNumberGenerator(rng, susc_person2); - infection_location.interact(p2_rng, susc_person2, t, dt, params); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(rng, susc_person2); + interact_testing(p2_rng, susc_person2, infection_location, {susc_person1, susc_person2, infected1}, t, dt, params); // The person susc_person1 should have full protection against an infection, susc_person2 not ASSERT_EQ(susc_person1.get_infection_state(t + dt), mio::abm::InfectionState::Susceptible); diff --git a/cpp/tests/test_abm_migration_rules.cpp b/cpp/tests/test_abm_migration_rules.cpp index 474e12d205..592897359d 100644 --- a/cpp/tests/test_abm_migration_rules.cpp +++ b/cpp/tests/test_abm_migration_rules.cpp @@ -17,10 +17,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/location_type.h" +#include "abm/migration_rules.h" #include "abm/person.h" #include "abm_helpers.h" #include "memilio/utils/random_number_generator.h" +TEST(TestMigrationRules, random_migration) +{ + int t = 0, dt = 1; + auto rng = mio::RandomNumberGenerator(); + auto default_type = mio::abm::LocationType::Cemetery; + auto person = mio::abm::Person(rng, default_type, 0, age_group_15_to_34); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, person); + auto params = mio::abm::Parameters(num_age_groups); + + ScopedMockDistribution>>> mock_exp_dist; + EXPECT_CALL(mock_exp_dist.get_mock(), invoke) + .Times(testing::Exactly(2)) + // values for v in mio::abm::random_transition + .WillOnce(testing::Return((t + dt) / 2.)) + .WillOnce(testing::Return(t + 2. * dt)); + + ScopedMockDistribution>>> mock_disc_dist; + EXPECT_CALL(mock_disc_dist.get_mock(), invoke) + .Times(testing::Exactly(1)) + // arbitrary value for random_idx in mio::abm::random_transition + .WillOnce(testing::Return(2)); + + const auto random_migration = [&]() { + return mio::abm::random_migration(p_rng, person, mio::abm::TimePoint{t}, mio::abm::days(dt), params); + }; + + params.set(mio::abm::TimePoint{t + 2 * dt}); + + const auto dest0 = random_migration(); + EXPECT_NE(dest0, default_type) << "should return a new location type (via random_transition)"; + + const auto dest1 = random_migration(); + EXPECT_EQ(dest1, default_type) << "should return current location type (via random_transition)"; + + params.set(mio::abm::TimePoint{t}); + + const auto dest2 = random_migration(); + EXPECT_EQ(dest2, default_type) << "should return current location type"; +} + TEST(TestMigrationRules, student_goes_to_school) { auto rng = mio::RandomNumberGenerator(); @@ -34,26 +76,26 @@ TEST(TestMigrationRules, student_goes_to_school) .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_child = mio::abm::Person(rng, home, age_group_5_to_14); - auto p_adult = mio::abm::Person(rng, home, age_group_15_to_34); + auto p_child = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(7); auto t_weekend = mio::abm::TimePoint(0) + mio::abm::days(5) + mio::abm::hours(7); auto dt = mio::abm::hours(1); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); - auto child_rng = mio::abm::Person::RandomNumberGenerator(rng, p_child); - auto adult_rng = mio::abm::Person::RandomNumberGenerator(rng, p_child); + auto child_rng = mio::abm::PersonalRandomNumberGenerator(rng, p_child); + auto adult_rng = mio::abm::PersonalRandomNumberGenerator(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; - ASSERT_EQ(mio::abm::go_to_school(child_rng, p_child, t_morning, dt, params), mio::abm::LocationType::School); - ASSERT_EQ(mio::abm::go_to_school(adult_rng, p_adult, t_morning, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_school(child_rng, p_child, t_weekend, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_school(child_rng, p_child, t_morning, dt, params), mio::abm::LocationType::School); + EXPECT_EQ(mio::abm::go_to_school(adult_rng, p_adult, t_morning, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_school(child_rng, p_child, t_weekend, dt, params), mio::abm::LocationType::Home); } TEST(TestMigrationRules, students_go_to_school_in_different_times) @@ -77,10 +119,10 @@ TEST(TestMigrationRules, students_go_to_school_in_different_times) .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_child_goes_to_school_at_6 = mio::abm::Person(rng, home, age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = mio::abm::Person::RandomNumberGenerator(rng, p_child_goes_to_school_at_6); - auto p_child_goes_to_school_at_8 = mio::abm::Person(rng, home, age_group_5_to_14); - auto rng_child_goes_to_school_at_8 = mio::abm::Person::RandomNumberGenerator(rng, p_child_goes_to_school_at_8); + auto p_child_goes_to_school_at_6 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_6); + auto p_child_goes_to_school_at_8 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_8 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_8); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); auto t_morning_8 = mio::abm::TimePoint(0) + mio::abm::hours(8); @@ -88,28 +130,24 @@ 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; - ASSERT_EQ( + EXPECT_EQ( mio::abm::go_to_school(rng_child_goes_to_school_at_6, p_child_goes_to_school_at_6, t_morning_6, dt, params), - mio::abm::LocationType::School); - ASSERT_EQ( + EXPECT_EQ( mio::abm::go_to_school(rng_child_goes_to_school_at_6, p_child_goes_to_school_at_6, t_morning_8, dt, params), - mio::abm::LocationType::Home); - ASSERT_EQ( + EXPECT_EQ( mio::abm::go_to_school(rng_child_goes_to_school_at_8, p_child_goes_to_school_at_8, t_morning_6, dt, params), - mio::abm::LocationType::Home); - ASSERT_EQ( + EXPECT_EQ( mio::abm::go_to_school(rng_child_goes_to_school_at_8, p_child_goes_to_school_at_8, t_morning_8, dt, params), - mio::abm::LocationType::School); } @@ -137,35 +175,34 @@ TEST(TestMigrationRules, students_go_to_school_in_different_times_with_smaller_t .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_child_goes_to_school_at_6 = mio::abm::Person(rng, home, age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = mio::abm::Person::RandomNumberGenerator(rng, p_child_goes_to_school_at_6); - auto p_child_goes_to_school_at_8_30 = mio::abm::Person(rng, home, age_group_5_to_14); + auto p_child_goes_to_school_at_6 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_6); + auto p_child_goes_to_school_at_8_30 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); auto rng_child_goes_to_school_at_8_30 = - mio::abm::Person::RandomNumberGenerator(rng, p_child_goes_to_school_at_8_30); + mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_8_30); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); auto t_morning_8_30 = mio::abm::TimePoint(0) + mio::abm::hours(8) + mio::abm::seconds(1800); 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; - ASSERT_EQ( + EXPECT_EQ( mio::abm::go_to_school(rng_child_goes_to_school_at_6, p_child_goes_to_school_at_6, t_morning_6, dt, params), - mio::abm::LocationType::School); - ASSERT_EQ( + EXPECT_EQ( mio::abm::go_to_school(rng_child_goes_to_school_at_6, p_child_goes_to_school_at_6, t_morning_8_30, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_school(rng_child_goes_to_school_at_8_30, p_child_goes_to_school_at_8_30, t_morning_6, dt, + EXPECT_EQ(mio::abm::go_to_school(rng_child_goes_to_school_at_8_30, p_child_goes_to_school_at_8_30, t_morning_6, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_school(rng_child_goes_to_school_at_8_30, p_child_goes_to_school_at_8_30, t_morning_8_30, + EXPECT_EQ(mio::abm::go_to_school(rng_child_goes_to_school_at_8_30, p_child_goes_to_school_at_8_30, t_morning_8_30, dt, params), mio::abm::LocationType::School); } @@ -174,13 +211,13 @@ TEST(TestMigrationRules, school_return) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); - auto p_child = mio::abm::Person(rng, school, age_group_5_to_14); - auto rng_child = mio::abm::Person::RandomNumberGenerator(rng, p_child); + auto p_child = mio::abm::Person(rng, school.get_type(), school.get_id(), age_group_5_to_14); + auto rng_child = mio::abm::PersonalRandomNumberGenerator(rng, p_child); auto t = mio::abm::TimePoint(0) + mio::abm::hours(15); auto dt = mio::abm::hours(1); - ASSERT_EQ(mio::abm::go_to_school(rng_child, p_child, t, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_school(rng_child, p_child, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); } @@ -201,10 +238,10 @@ TEST(TestMigrationRules, worker_goes_to_work) .WillOnce(testing::Return(0.)) .WillRepeatedly(testing::Return(1.0)); - auto p_retiree = mio::abm::Person(rng, home, age_group_60_to_79); - auto rng_retiree = mio::abm::Person::RandomNumberGenerator(rng, p_retiree); - auto p_adult = mio::abm::Person(rng, home, age_group_15_to_34); - auto rng_adult = mio::abm::Person::RandomNumberGenerator(rng, p_adult); + auto p_retiree = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(rng, p_retiree); + auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(rng, p_adult); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(8); auto t_night = mio::abm::TimePoint(0) + mio::abm::days(1) + mio::abm::hours(4); @@ -212,16 +249,16 @@ 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; - ASSERT_EQ(mio::abm::go_to_work(rng_retiree, p_retiree, t_morning, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_morning, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_night, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_work(rng_retiree, p_retiree, t_morning, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_morning, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_night, dt, params), mio::abm::LocationType::Home); } TEST(TestMigrationRules, worker_goes_to_work_with_non_dividable_timespan) @@ -241,10 +278,10 @@ TEST(TestMigrationRules, worker_goes_to_work_with_non_dividable_timespan) .WillOnce(testing::Return(0.)) .WillRepeatedly(testing::Return(1.0)); - auto p_retiree = mio::abm::Person(rng, home, age_group_60_to_79); - auto rng_retiree = mio::abm::Person::RandomNumberGenerator(rng, p_retiree); - auto p_adult = mio::abm::Person(rng, home, age_group_15_to_34); - auto rng_adult = mio::abm::Person::RandomNumberGenerator(rng, p_adult); + auto p_retiree = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(rng, p_retiree); + auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(rng, p_adult); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(8); auto t_night = mio::abm::TimePoint(0) + mio::abm::days(1) + mio::abm::hours(4); @@ -252,16 +289,16 @@ 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; - ASSERT_EQ(mio::abm::go_to_work(rng_retiree, p_retiree, t_morning, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_morning, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_night, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_work(rng_retiree, p_retiree, t_morning, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_morning, dt, params), mio::abm::LocationType::Home); + EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t_night, dt, params), mio::abm::LocationType::Home); } TEST(TestMigrationRules, workers_go_to_work_in_different_times) @@ -282,10 +319,10 @@ TEST(TestMigrationRules, workers_go_to_work_in_different_times) .WillRepeatedly(testing::Return(1.0)); - auto p_adult_goes_to_work_at_6 = mio::abm::Person(rng, home, age_group_15_to_34); - auto rng_adult_goes_to_work_at_6 = mio::abm::Person::RandomNumberGenerator(rng, p_adult_goes_to_work_at_6); - auto p_adult_goes_to_work_at_8 = mio::abm::Person(rng, home, age_group_15_to_34); - auto rng_adult_goes_to_work_at_8 = mio::abm::Person::RandomNumberGenerator(rng, p_adult_goes_to_work_at_8); + auto p_adult_goes_to_work_at_6 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto rng_adult_goes_to_work_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_adult_goes_to_work_at_6); + auto p_adult_goes_to_work_at_8 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto rng_adult_goes_to_work_at_8 = mio::abm::PersonalRandomNumberGenerator(rng, p_adult_goes_to_work_at_8); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); auto t_morning_8 = mio::abm::TimePoint(0) + mio::abm::hours(8); @@ -293,24 +330,24 @@ 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; - ASSERT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_6, p_adult_goes_to_work_at_6, t_morning_6, dt, params), + EXPECT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_6, p_adult_goes_to_work_at_6, t_morning_6, dt, params), mio::abm::LocationType::Work); - ASSERT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_6, p_adult_goes_to_work_at_6, t_morning_8, dt, params), + EXPECT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_6, p_adult_goes_to_work_at_6, t_morning_8, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_6, p_adult_goes_to_work_at_6, t_night, dt, params), + EXPECT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_6, p_adult_goes_to_work_at_6, t_night, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_8, p_adult_goes_to_work_at_8, t_morning_6, dt, params), + EXPECT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_8, p_adult_goes_to_work_at_8, t_morning_6, dt, params), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_8, p_adult_goes_to_work_at_8, t_morning_8, dt, params), + EXPECT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_8, p_adult_goes_to_work_at_8, t_morning_8, dt, params), mio::abm::LocationType::Work); - ASSERT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_8, p_adult_goes_to_work_at_8, t_night, dt, params), + EXPECT_EQ(mio::abm::go_to_work(rng_adult_goes_to_work_at_8, p_adult_goes_to_work_at_8, t_night, dt, params), mio::abm::LocationType::Home); } @@ -318,40 +355,40 @@ TEST(TestMigrationRules, work_return) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); - auto p_adult = mio::abm::Person(rng, work, age_group_35_to_59); - auto rng_adult = mio::abm::Person::RandomNumberGenerator(rng, p_adult); + auto p_adult = mio::abm::Person(rng, work.get_type(), work.get_id(), age_group_35_to_59); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(rng, p_adult); auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); auto dt = mio::abm::hours(1); - ASSERT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); } 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::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); auto p_inf1 = make_test_person(work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_inf1 = mio::abm::Person::RandomNumberGenerator(rng, p_inf1); + auto rng_inf1 = mio::abm::PersonalRandomNumberGenerator(rng, p_inf1); p_inf1.get_tested(rng_inf1, t, test_params); - ASSERT_EQ(mio::abm::go_to_quarantine(rng_inf1, p_inf1, t, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf1, p_inf1, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); //detected infected person quarantines at home auto p_inf2 = make_test_person(work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_inf2 = mio::abm::Person::RandomNumberGenerator(rng, p_inf2); - ASSERT_EQ(mio::abm::go_to_quarantine(rng_inf2, p_inf2, t, dt, mio::abm::Parameters(num_age_groups)), + auto rng_inf2 = mio::abm::PersonalRandomNumberGenerator(rng, p_inf2); + EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf2, p_inf2, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); //undetected infected person does not quaratine auto p_inf3 = make_test_person(hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf3 = mio::abm::Person::RandomNumberGenerator(rng, p_inf3); + auto rng_inf3 = mio::abm::PersonalRandomNumberGenerator(rng, p_inf3); p_inf1.get_tested(rng_inf3, t, test_params); - ASSERT_EQ(mio::abm::go_to_quarantine(rng_inf3, p_inf3, t, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf3, p_inf3, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Hospital); //detected infected person does not leave hospital to quarantine } @@ -362,14 +399,14 @@ TEST(TestMigrationRules, hospital) auto t = mio::abm::TimePoint(12346); auto dt = mio::abm::hours(1); auto p_inf = make_test_person(home, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf = mio::abm::Person::RandomNumberGenerator(rng, p_inf); + auto rng_inf = mio::abm::PersonalRandomNumberGenerator(rng, p_inf); - ASSERT_EQ(mio::abm::go_to_hospital(rng_inf, p_inf, t, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_hospital(rng_inf, p_inf, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Hospital); auto p_car = make_test_person(home, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - auto rng_car = mio::abm::Person::RandomNumberGenerator(rng, p_car); - ASSERT_EQ(mio::abm::go_to_hospital(rng_car, p_car, t, dt, mio::abm::Parameters(num_age_groups)), + auto rng_car = mio::abm::PersonalRandomNumberGenerator(rng, p_car); + EXPECT_EQ(mio::abm::go_to_hospital(rng_car, p_car, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); } @@ -385,21 +422,21 @@ TEST(TestMigrationRules, go_shopping) auto dt = mio::abm::hours(1); auto p_hosp = make_test_person(hospital, age_group_0_to_4, mio::abm::InfectionState::InfectedSymptoms, t_weekday); - auto rng_hosp = mio::abm::Person::RandomNumberGenerator(rng, p_hosp); - auto p_home = mio::abm::Person(rng, home, age_group_60_to_79); - auto rng_home = mio::abm::Person::RandomNumberGenerator(rng, p_home); + auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(rng, p_hosp); + auto p_home = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto rng_home = mio::abm::PersonalRandomNumberGenerator(rng, p_home); - ASSERT_EQ(mio::abm::go_to_shop(rng_hosp, p_hosp, t_weekday, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_shop(rng_hosp, p_hosp, t_weekday, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Hospital); - ASSERT_EQ(mio::abm::go_to_shop(rng_home, p_home, t_sunday, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_shop(rng_home, p_home, t_sunday, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::go_to_shop(rng_home, p_home, t_night, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_shop(rng_home, p_home, t_night, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); ScopedMockDistribution>>> mock_exponential_dist; EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(testing::Return(0.01)); - ASSERT_EQ(mio::abm::go_to_shop(rng_home, p_home, t_weekday, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_shop(rng_home, p_home, t_weekday, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::BasicsShop); } @@ -410,15 +447,13 @@ TEST(TestMigrationRules, shop_return) auto t = mio::abm::TimePoint(0) + mio::abm::days(4) + mio::abm::hours(9); auto dt = mio::abm::hours(1); - mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location shop(mio::abm::LocationType::BasicsShop, 0, num_age_groups); - auto p = make_test_person(home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); - auto rng_p = mio::abm::Person::RandomNumberGenerator(rng, p); - home.add_person(p); - p.migrate_to(shop); - p.interact(rng_p, t, dt, params); //person only returns home after some time passed + auto p = make_test_person(shop, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); - ASSERT_EQ(mio::abm::go_to_shop(rng_p, p, t, dt, mio::abm::Parameters(num_age_groups)), + p.add_time_at_location(dt); + + EXPECT_EQ(mio::abm::go_to_shop(rng_p, p, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); } @@ -426,30 +461,30 @@ TEST(TestMigrationRules, go_event) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); - auto p_work = mio::abm::Person(rng, work, age_group_35_to_59); - auto rng_work = mio::abm::Person::RandomNumberGenerator(rng, p_work); - mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_home = mio::abm::Person(rng, home, age_group_60_to_79); - auto rng_home = mio::abm::Person::RandomNumberGenerator(rng, p_home); + auto p_work = mio::abm::Person(rng, work.get_type(), work.get_id(), age_group_35_to_59); + auto rng_work = mio::abm::PersonalRandomNumberGenerator(rng, p_work); + mio::abm::Location home(mio::abm::LocationType::Home, 1, num_age_groups); + auto p_home = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto rng_home = mio::abm::PersonalRandomNumberGenerator(rng, p_home); auto t_weekday = mio::abm::TimePoint(0) + mio::abm::days(4) + mio::abm::hours(20); auto t_saturday = mio::abm::TimePoint(0) + mio::abm::days(5) + mio::abm::hours(10); auto t_night = mio::abm::TimePoint(0) + mio::abm::days(5) + mio::abm::hours(1); auto dt = mio::abm::hours(1); - ASSERT_EQ(mio::abm::go_to_event(rng_work, p_work, t_weekday, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_event(rng_work, p_work, t_weekday, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); - ASSERT_EQ(mio::abm::go_to_event(rng_home, p_home, t_night, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_event(rng_home, p_home, t_night, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); ScopedMockDistribution>>> mock_exponential_dist; EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(testing::Return(0.01)); - ASSERT_EQ(mio::abm::go_to_event(rng_home, p_home, t_weekday, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_event(rng_home, p_home, t_weekday, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::SocialEvent); EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(testing::Return(0.01)); - ASSERT_EQ(mio::abm::go_to_event(rng_home, p_home, t_saturday, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_event(rng_home, p_home, t_saturday, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::SocialEvent); } @@ -461,15 +496,13 @@ TEST(TestMigrationRules, event_return) auto dt = mio::abm::hours(3); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location social_event(mio::abm::LocationType::SocialEvent, 0, num_age_groups); - auto p = mio::abm::Person(rng, home, age_group_15_to_34); - auto rng_p = mio::abm::Person::RandomNumberGenerator(rng, p); - home.add_person(p); - p.migrate_to(social_event); - p.interact(rng_p, t, dt, params); - - ASSERT_EQ(mio::abm::go_to_event(rng_p, p, t, dt, mio::abm::Parameters(num_age_groups)), - mio::abm::LocationType::Home); + mio::abm::Location social_event(mio::abm::LocationType::SocialEvent, 1, num_age_groups); + auto p = mio::abm::Person(rng, social_event.get_type(), social_event.get_id(), age_group_15_to_34); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); + + p.add_time_at_location(dt); + + EXPECT_EQ(mio::abm::go_to_event(rng_p, p, t, dt, params), mio::abm::LocationType::Home); } TEST(TestMigrationRules, icu) @@ -479,15 +512,15 @@ TEST(TestMigrationRules, icu) auto t = mio::abm::TimePoint(12346); auto dt = mio::abm::hours(1); auto p_hosp = make_test_person(hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedCritical, t); - auto rng_hosp = mio::abm::Person::RandomNumberGenerator(rng, p_hosp); + auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(rng, p_hosp); - ASSERT_EQ(mio::abm::go_to_icu(rng_hosp, p_hosp, t, dt, mio::abm::Parameters(num_age_groups)), + EXPECT_EQ(mio::abm::go_to_icu(rng_hosp, p_hosp, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::ICU); - mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); + mio::abm::Location work(mio::abm::LocationType::Work, 1, num_age_groups); auto p_work = make_test_person(work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_work = mio::abm::Person::RandomNumberGenerator(rng, p_work); - ASSERT_EQ(mio::abm::go_to_icu(rng_work, p_work, t, dt, mio::abm::Parameters(num_age_groups)), + auto rng_work = mio::abm::PersonalRandomNumberGenerator(rng, p_work); + EXPECT_EQ(mio::abm::go_to_icu(rng_work, p_work, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); } @@ -498,12 +531,12 @@ TEST(TestMigrationRules, recover) auto t = mio::abm::TimePoint(12346); auto dt = mio::abm::hours(1); auto p_rec = make_test_person(hospital, age_group_60_to_79, mio::abm::InfectionState::Recovered, t); - auto rng_rec = mio::abm::Person::RandomNumberGenerator(rng, p_rec); + auto rng_rec = mio::abm::PersonalRandomNumberGenerator(rng, p_rec); auto p_inf = make_test_person(hospital, age_group_60_to_79, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf = mio::abm::Person::RandomNumberGenerator(rng, p_inf); - ASSERT_EQ(mio::abm::return_home_when_recovered(rng_rec, p_rec, t, dt, {num_age_groups}), + auto rng_inf = mio::abm::PersonalRandomNumberGenerator(rng, p_inf); + EXPECT_EQ(mio::abm::return_home_when_recovered(rng_rec, p_rec, t, dt, {num_age_groups}), mio::abm::LocationType::Home); - ASSERT_EQ(mio::abm::return_home_when_recovered(rng_inf, p_inf, t, dt, {num_age_groups}), + EXPECT_EQ(mio::abm::return_home_when_recovered(rng_inf, p_inf, t, dt, {num_age_groups}), mio::abm::LocationType::Hospital); } @@ -515,7 +548,7 @@ TEST(TestMigrationRules, dead) auto t = mio::abm::TimePoint(12346); auto dt = mio::abm::hours(1); auto p_dead = make_test_person(icu, age_group_60_to_79, mio::abm::InfectionState::Dead, t); - auto p_rng = mio::abm::Person::RandomNumberGenerator(rng, p_dead); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, p_dead); - ASSERT_EQ(mio::abm::get_buried(p_rng, p_dead, t, dt, {num_age_groups}), mio::abm::LocationType::Cemetery); + EXPECT_EQ(mio::abm::get_buried(p_rng, p_dead, t, dt, {num_age_groups}), mio::abm::LocationType::Cemetery); } diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 028c940b6e..6cea44e1d4 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -17,86 +17,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/location_id.h" +#include "abm/model_functions.h" #include "abm/location_type.h" +#include "abm/migration_rules.h" #include "abm/person.h" - +#include "abm/time.h" #include "abm_helpers.h" #include "memilio/utils/random_number_generator.h" + #include TEST(TestPerson, init) { auto rng = mio::RandomNumberGenerator(); - mio::abm::Location location(mio::abm::LocationType::Work, 0, num_age_groups); + mio::abm::Location location(mio::abm::LocationType::Work, 7, num_age_groups); auto t = mio::abm::TimePoint(0); - auto person = mio::abm::Person(rng, location, age_group_60_to_79); - - ASSERT_EQ(person.get_infection_state(t), mio::abm::InfectionState::Susceptible); - ASSERT_EQ(person.get_location(), location); - ASSERT_EQ(person.get_person_id(), mio::abm::INVALID_PERSON_ID); -} + auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_60_to_79); -TEST(TestPerson, copyPerson) -{ - auto rng = mio::RandomNumberGenerator(); - auto location = mio::abm::Location(mio::abm::LocationType::Work, 0, num_age_groups); - auto t = mio::abm::TimePoint(0); - auto person = mio::abm::Person(rng, location, age_group_60_to_79); - auto copied_location = location.copy_location_without_persons(num_age_groups); - auto copied_person = person.copy_person(copied_location); - - ASSERT_EQ(copied_person.get_infection_state(t), mio::abm::InfectionState::Susceptible); - ASSERT_EQ(copied_person.get_location(), copied_location); - ASSERT_EQ(copied_person.get_person_id(), mio::abm::INVALID_PERSON_ID); + EXPECT_EQ(person.get_infection_state(t), mio::abm::InfectionState::Susceptible); + EXPECT_EQ(person.get_location(), location.get_id()); + EXPECT_EQ(person.get_id(), mio::abm::PersonId::invalid_id()); } TEST(TestPerson, migrate) { auto rng = mio::RandomNumberGenerator(); - auto t = mio::abm::TimePoint(0); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location loc1(mio::abm::LocationType::PublicTransport, 0, 6, 1); - mio::abm::Location loc2(mio::abm::LocationType::School, 0, num_age_groups); - mio::abm::Location loc3(mio::abm::LocationType::PublicTransport, 0, 6, 2); + mio::abm::Location loc1(mio::abm::LocationType::PublicTransport, 1, 6, 1); + mio::abm::Location loc2(mio::abm::LocationType::School, 2, num_age_groups); + mio::abm::Location loc3(mio::abm::LocationType::PublicTransport, 3, 6, 2); auto person = make_test_person(home, age_group_0_to_4, mio::abm::InfectionState::Recovered); - person.migrate_to(loc1, {0}); - ASSERT_EQ(person.get_location(), loc1); - ASSERT_EQ(loc1.get_subpopulation(t, mio::abm::InfectionState::Recovered), 1); - ASSERT_EQ(home.get_subpopulation(t, mio::abm::InfectionState::Recovered), 0); - ASSERT_EQ(loc1.get_cells()[0].m_persons.size(), 1u); - ASSERT_EQ(person.get_last_transport_mode(), mio::abm::TransportMode::Unknown); - - person.migrate_to(loc2, mio::abm::TransportMode::Walking); - - ASSERT_EQ(person.get_location(), loc2); - ASSERT_EQ(loc2.get_subpopulation(t, mio::abm::InfectionState::Recovered), 1); - ASSERT_EQ(loc1.get_subpopulation(t, mio::abm::InfectionState::Recovered), 0); - ASSERT_EQ(loc1.get_cells()[0].m_persons.size(), 0u); - ASSERT_EQ(person.get_last_transport_mode(), mio::abm::TransportMode::Walking); - - person.migrate_to(loc3, mio::abm::TransportMode::Bike, {0, 1}); - - ASSERT_EQ(loc3.get_cells()[0].m_persons.size(), 1u); - ASSERT_EQ(loc3.get_cells()[1].m_persons.size(), 1u); + // check that a person does not move to its current location + person.add_time_at_location(mio::abm::hours(1)); + EXPECT_FALSE(mio::abm::migrate(person, home)); + EXPECT_EQ(person.get_time_at_location(), mio::abm::hours(1)); + EXPECT_EQ(person.get_location(), home.get_id()); + + // move the person around a bit + EXPECT_TRUE(mio::abm::migrate(person, loc1, mio::abm::TransportMode::Unknown, {0})); + EXPECT_EQ(person.get_time_at_location(), mio::abm::TimeSpan(0)); + EXPECT_EQ(person.get_location(), loc1.get_id()); + EXPECT_EQ(person.get_last_transport_mode(), mio::abm::TransportMode::Unknown); + + EXPECT_TRUE(mio::abm::migrate(person, loc2, mio::abm::TransportMode::Walking, {0})); + EXPECT_EQ(person.get_time_at_location(), mio::abm::TimeSpan(0)); + EXPECT_EQ(person.get_location(), loc2.get_id()); + EXPECT_EQ(person.get_last_transport_mode(), mio::abm::TransportMode::Walking); + + EXPECT_TRUE(mio::abm::migrate(person, loc3, mio::abm::TransportMode::Bike, {0, 1})); + EXPECT_EQ(person.get_time_at_location(), mio::abm::TimeSpan(0)); + EXPECT_EQ(person.get_location(), loc3.get_id()); + EXPECT_EQ(person.get_last_transport_mode(), mio::abm::TransportMode::Bike); ASSERT_EQ(person.get_cells().size(), 2); - ASSERT_EQ(person.get_cells()[0], 0u); - ASSERT_EQ(person.get_cells()[1], 1u); - ASSERT_EQ(person.get_last_transport_mode(), mio::abm::TransportMode::Bike); + EXPECT_EQ(person.get_cells()[0], 0u); + EXPECT_EQ(person.get_cells()[1], 1u); } TEST(TestPerson, setGetAssignedLocation) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location location(mio::abm::LocationType::Work, 2, num_age_groups); - auto person = mio::abm::Person(rng, location, age_group_35_to_59); - person.set_assigned_location(location); - ASSERT_EQ((int)person.get_assigned_location_index(mio::abm::LocationType::Work), 2); + auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_35_to_59); + person.set_assigned_location(location.get_type(), location.get_id()); + EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(2)); - person.set_assigned_location({4, mio::abm::LocationType::Work}); - ASSERT_EQ((int)person.get_assigned_location_index(mio::abm::LocationType::Work), 4); + person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(4)); + EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(4)); } TEST(TestPerson, quarantine) @@ -107,7 +97,7 @@ TEST(TestPerson, quarantine) auto infection_parameters = mio::abm::Parameters(num_age_groups); 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 work(mio::abm::LocationType::Work, 1, num_age_groups); //setup rng mock so the person has a state transition to Recovered ScopedMockDistribution>>> mock_uniform_dist; @@ -129,16 +119,16 @@ 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); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(rng, person); person.get_tested(rng_person, t_morning, test_params); - ASSERT_EQ(person.get_infection_state(t_morning), mio::abm::InfectionState::InfectedSymptoms); - ASSERT_EQ(mio::abm::go_to_work(rng_person, person, t_morning, dt, infection_parameters), + EXPECT_EQ(person.get_infection_state(t_morning), mio::abm::InfectionState::InfectedSymptoms); + EXPECT_EQ(mio::abm::go_to_work(rng_person, person, t_morning, dt, infection_parameters), mio::abm::LocationType::Home); - ASSERT_EQ(person.get_infection_state(t_morning + dt), mio::abm::InfectionState::Recovered); + EXPECT_EQ(person.get_infection_state(t_morning + dt), mio::abm::InfectionState::Recovered); person.remove_quarantine(); - ASSERT_EQ(mio::abm::go_to_work(rng_person, person, t_morning, dt, infection_parameters), + EXPECT_EQ(mio::abm::go_to_work(rng_person, person, t_morning, dt, infection_parameters), mio::abm::LocationType::Work); } @@ -151,9 +141,9 @@ TEST(TestPerson, get_tested) mio::abm::TimePoint t(0); mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); auto infected = make_test_person(loc, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - auto rng_infected = mio::abm::Person::RandomNumberGenerator(rng, infected); - auto susceptible = mio::abm::Person(rng, loc, age_group_15_to_34); - auto rng_suscetible = mio::abm::Person::RandomNumberGenerator(rng, susceptible); + auto rng_infected = mio::abm::PersonalRandomNumberGenerator(rng, infected); + auto susceptible = mio::abm::Person(rng, loc.get_type(), loc.get_id(), age_group_15_to_34); + auto rng_suscetible = mio::abm::PersonalRandomNumberGenerator(rng, susceptible); auto pcr_test = mio::abm::PCRTest(); auto antigen_test = mio::abm::AntigenTest(); @@ -167,16 +157,16 @@ TEST(TestPerson, get_tested) .WillOnce(Return(0.95)) .WillOnce(Return(0.6)) .WillOnce(Return(0.999)); - ASSERT_EQ(infected.get_tested(rng_infected, t, pcr_test.get_default()), true); - ASSERT_EQ(infected.is_in_quarantine(t, params), true); + EXPECT_EQ(infected.get_tested(rng_infected, t, pcr_test.get_default()), true); + EXPECT_EQ(infected.is_in_quarantine(t, params), true); infected.remove_quarantine(); - ASSERT_EQ(infected.get_tested(rng_infected, t, pcr_test.get_default()), false); - ASSERT_EQ(infected.is_in_quarantine(t, params), false); - ASSERT_EQ(susceptible.get_tested(rng_suscetible, t, pcr_test.get_default()), false); - ASSERT_EQ(susceptible.is_in_quarantine(t, params), false); - ASSERT_EQ(susceptible.get_tested(rng_suscetible, t, pcr_test.get_default()), true); - ASSERT_EQ(susceptible.is_in_quarantine(t, params), true); - ASSERT_EQ(susceptible.get_time_of_last_test(), mio::abm::TimePoint(0)); + EXPECT_EQ(infected.get_tested(rng_infected, t, pcr_test.get_default()), false); + EXPECT_EQ(infected.is_in_quarantine(t, params), false); + EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, pcr_test.get_default()), false); + EXPECT_EQ(susceptible.is_in_quarantine(t, params), false); + EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, pcr_test.get_default()), 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>>> @@ -187,21 +177,24 @@ TEST(TestPerson, get_tested) .WillOnce(Return(0.95)) .WillOnce(Return(0.6)) .WillOnce(Return(0.999)); - ASSERT_EQ(infected.get_tested(rng_infected, t, antigen_test.get_default()), true); - ASSERT_EQ(infected.get_tested(rng_infected, t, antigen_test.get_default()), false); - ASSERT_EQ(susceptible.get_tested(rng_suscetible, t, antigen_test.get_default()), false); - ASSERT_EQ(susceptible.get_tested(rng_suscetible, t, antigen_test.get_default()), true); - ASSERT_EQ(susceptible.get_time_of_last_test(), mio::abm::TimePoint(0)); + EXPECT_EQ(infected.get_tested(rng_infected, t, antigen_test.get_default()), true); + EXPECT_EQ(infected.get_tested(rng_infected, t, antigen_test.get_default()), false); + EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, antigen_test.get_default()), false); + EXPECT_EQ(susceptible.get_tested(rng_suscetible, t, antigen_test.get_default()), true); + EXPECT_EQ(susceptible.get_time_of_last_test(), mio::abm::TimePoint(0)); } TEST(TestPerson, getCells) { mio::abm::Location home(mio::abm::LocationType::Home, 0, 6, 1); - mio::abm::Location location(mio::abm::LocationType::PublicTransport, 0, 6, 2); + mio::abm::Location location(mio::abm::LocationType::PublicTransport, 1, 6, 7); auto person = make_test_person(home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); - home.add_person(person); - person.migrate_to(location, {0, 1}); + + EXPECT_TRUE(mio::abm::migrate(person, location, mio::abm::TransportMode::Unknown, {3, 5})); + ASSERT_EQ(person.get_cells().size(), 2); + EXPECT_EQ(person.get_cells()[0], 3u); + EXPECT_EQ(person.get_cells()[1], 5u); } TEST(TestPerson, interact) @@ -212,10 +205,10 @@ TEST(TestPerson, interact) auto infection_parameters = mio::abm::Parameters(num_age_groups); mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::TimePoint t(0); - auto person = mio::abm::Person(rng, loc, age_group_15_to_34); - auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); + auto person = mio::abm::Person(rng, loc.get_type(), loc.get_id(), age_group_15_to_34); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(rng, person); auto dt = mio::abm::seconds(8640); //0.1 days - person.interact(rng_person, t, dt, infection_parameters); + interact_testing(rng_person, person, loc, {person}, t, dt, infection_parameters); EXPECT_EQ(person.get_time_at_location(), dt); } @@ -227,7 +220,7 @@ TEST(TestPerson, applyMaskIntervention) mio::abm::Location target(mio::abm::LocationType::Work, 0, num_age_groups); auto person = make_test_person(home); person.get_mask().change_mask(mio::abm::MaskType::Community); - auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(rng, person); target.set_npi_active(false); person.apply_mask_intervention(rng_person, target); @@ -297,8 +290,8 @@ TEST(TestPerson, getLatestProtection) { auto rng = mio::RandomNumberGenerator(); auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(rng, location, age_group_15_to_34); - auto prng = mio::abm::Person::RandomNumberGenerator(rng, person); + auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_15_to_34); + auto prng = mio::abm::PersonalRandomNumberGenerator(rng, person); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); auto t = mio::abm::TimePoint(0); @@ -318,15 +311,14 @@ TEST(TestPerson, getLatestProtection) TEST(Person, rng) { auto rng = mio::RandomNumberGenerator(); - mio::abm::Location loc(mio::abm::LocationType::Home, 0); - auto p = mio::abm::Person(rng, loc, age_group_35_to_59, 13); + auto p = mio::abm::Person(rng, mio::abm::LocationType::Home, 0, age_group_35_to_59, mio::abm::PersonId(13)); - ASSERT_EQ(p.get_rng_counter(), mio::Counter(0)); + EXPECT_EQ(p.get_rng_counter(), mio::Counter(0)); - auto p_rng = mio::abm::Person::RandomNumberGenerator(rng, p); - ASSERT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{0})); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, p); + EXPECT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{0})); p_rng(); - ASSERT_EQ(p.get_rng_counter(), mio::Counter(1)); - ASSERT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{1})); + EXPECT_EQ(p.get_rng_counter(), mio::Counter(1)); + EXPECT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{1})); } diff --git a/cpp/tests/test_abm_simulation.cpp b/cpp/tests/test_abm_simulation.cpp index fff7311d5d..22034ad7b9 100644 --- a/cpp/tests/test_abm_simulation.cpp +++ b/cpp/tests/test_abm_simulation.cpp @@ -19,6 +19,7 @@ */ #include "abm_helpers.h" #include "abm/common_abm_loggers.h" +#include "matchers.h" #include "memilio/io/history.h" TEST(TestSimulation, advance_random) @@ -26,14 +27,15 @@ TEST(TestSimulation, advance_random) auto world = mio::abm::World(num_age_groups); auto location1 = world.add_location(mio::abm::LocationType::School); auto location2 = world.add_location(mio::abm::LocationType::School); - auto& p1 = world.add_person(location1, age_group_5_to_14); - auto& p2 = world.add_person(location1, age_group_5_to_14); - auto& p3 = world.add_person(location2, age_group_5_to_14); - auto& p4 = world.add_person(location2, age_group_5_to_14); - p1.set_assigned_location(location1); - p2.set_assigned_location(location1); - p3.set_assigned_location(location2); - p4.set_assigned_location(location2); + auto p1 = world.add_person(location1, age_group_5_to_14); + auto p2 = world.add_person(location1, age_group_5_to_14); + auto p3 = world.add_person(location2, age_group_5_to_14); + auto p4 = world.add_person(location2, age_group_5_to_14); + + world.assign_location(p1, location1); + world.assign_location(p2, location1); + world.assign_location(p3, location2); + world.assign_location(p4, location2); auto sim = mio::abm::Simulation(mio::abm::TimePoint(0), std::move(world)); @@ -75,34 +77,35 @@ TEST(TestSimulation, advanceWithCommonHistory) auto basics_id = world.add_location(mio::abm::LocationType::BasicsShop); auto public_id = world.add_location(mio::abm::LocationType::PublicTransport); - auto& person1 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Exposed); - auto& person2 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::Exposed); - auto& person3 = add_test_person(world, home_id, age_group_35_to_59, mio::abm::InfectionState::Dead); - person1.set_assigned_location(home_id); - person2.set_assigned_location(home_id); - person3.set_assigned_location(home_id); - person1.set_assigned_location(school_id); - person2.set_assigned_location(work_id); - person2.set_assigned_location(icu_id); - person2.set_assigned_location(hospital_id); - person1.set_assigned_location(social_id); - person2.set_assigned_location(social_id); - person3.set_assigned_location(social_id); - person1.set_assigned_location(basics_id); - person2.set_assigned_location(basics_id); - person3.set_assigned_location(basics_id); - person2.set_assigned_location(public_id); + auto person1 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Exposed); + auto person2 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::Exposed); + auto person3 = add_test_person(world, home_id, age_group_35_to_59, mio::abm::InfectionState::Dead); + + world.assign_location(person1, home_id); + world.assign_location(person2, home_id); + world.assign_location(person3, home_id); + world.assign_location(person1, school_id); + world.assign_location(person2, work_id); + world.assign_location(person2, icu_id); + world.assign_location(person2, hospital_id); + world.assign_location(person1, social_id); + world.assign_location(person2, social_id); + world.assign_location(person3, social_id); + world.assign_location(person1, basics_id); + world.assign_location(person2, basics_id); + world.assign_location(person3, basics_id); + world.assign_location(person2, public_id); mio::abm::TripList& trip_list = world.get_trip_list(); // We add trips for person two to test the history and if it is working correctly - mio::abm::Trip trip1(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(2), work_id); - mio::abm::Trip trip2(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), icu_id); - mio::abm::Trip trip3(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(4), hospital_id); - mio::abm::Trip trip4(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(5), social_id); - mio::abm::Trip trip5(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(6), basics_id); - mio::abm::Trip trip6(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(7), public_id); - mio::abm::Trip trip7(person2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), home_id); + mio::abm::Trip trip1(person2, mio::abm::TimePoint(0) + mio::abm::hours(2), work_id); + mio::abm::Trip trip2(person2, mio::abm::TimePoint(0) + mio::abm::hours(3), icu_id); + mio::abm::Trip trip3(person2, mio::abm::TimePoint(0) + mio::abm::hours(4), hospital_id); + mio::abm::Trip trip4(person2, mio::abm::TimePoint(0) + mio::abm::hours(5), social_id); + mio::abm::Trip trip5(person2, mio::abm::TimePoint(0) + mio::abm::hours(6), basics_id); + mio::abm::Trip trip6(person2, mio::abm::TimePoint(0) + mio::abm::hours(7), public_id); + mio::abm::Trip trip7(person2, mio::abm::TimePoint(0) + mio::abm::hours(8), home_id); trip_list.add_trip(trip1); trip_list.add_trip(trip2); diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 7442701625..3c53c7c228 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "abm/person.h" +#include "abm/testing_strategy.h" #include "abm_helpers.h" #include "memilio/utils/random_number_generator.h" @@ -91,9 +91,9 @@ TEST(TestTestingScheme, runScheme) 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); auto person1 = make_test_person(loc_home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); - auto rng_person1 = mio::abm::Person::RandomNumberGenerator(rng, person1); + auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(rng, person1); auto person2 = make_test_person(loc_home, age_group_15_to_34, mio::abm::InfectionState::Recovered); - auto rng_person2 = mio::abm::Person::RandomNumberGenerator(rng, person2); + auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(rng, person2); ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) @@ -130,9 +130,9 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) 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); - auto rng_person1 = mio::abm::Person::RandomNumberGenerator(rng, person1); + auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(rng, person1); auto person2 = make_test_person(loc_work, age_group_15_to_34, mio::abm::InfectionState::Recovered); - auto rng_person2 = mio::abm::Person::RandomNumberGenerator(rng, person2); + auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(rng, person2); ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) @@ -141,7 +141,7 @@ TEST(TestTestingScheme, initAndRunTestingStrategy) .WillOnce(testing::Return(0.5)); mio::abm::TestingStrategy test_strategy = - mio::abm::TestingStrategy(std::unordered_map>()); + 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), diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index e01c00170c..99a3eed6fc 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -27,7 +27,7 @@ TEST(TestWorld, init) EXPECT_EQ(world.get_locations().size(), 1); EXPECT_EQ(world.get_locations()[0].get_type(), mio::abm::LocationType::Cemetery); - ASSERT_THAT(world.get_persons(), testing::ElementsAre()); + EXPECT_THAT(world.get_persons(), testing::ElementsAre()); } TEST(TestWorld, addLocation) @@ -38,13 +38,13 @@ TEST(TestWorld, addLocation) auto work_id = world.add_location(mio::abm::LocationType::Work); auto home_id = world.add_location(mio::abm::LocationType::Home); - ASSERT_EQ((int)school_id1.index, 1); - ASSERT_EQ((int)school_id2.index, 2); + ASSERT_EQ(school_id1.get(), 1u); + ASSERT_EQ(school_id2.get(), 2u); - auto& school1 = world.get_individualized_location(school_id1); - auto& school2 = world.get_individualized_location(school_id2); - auto& work = world.get_individualized_location(work_id); - auto& home = world.get_individualized_location(home_id); + auto& school1 = world.get_location(school_id1); + auto& school2 = world.get_location(school_id2); + auto& work = world.get_location(work_id); + auto& home = world.get_location(home_id); size_t count_schools = 0; for (auto& loc : world.get_locations()) { @@ -65,12 +65,12 @@ TEST(TestWorld, addPerson) auto world = mio::abm::World(num_age_groups); auto location = world.add_location(mio::abm::LocationType::School); - auto& p1 = world.add_person(location, age_group_15_to_34); - auto& p2 = world.add_person(location, age_group_35_to_59); + world.add_person(location, age_group_15_to_34); + world.add_person(location, age_group_35_to_59); ASSERT_EQ(world.get_persons().size(), 2); - ASSERT_EQ(&world.get_persons()[0], &p1); - ASSERT_EQ(&world.get_persons()[1], &p2); + ASSERT_EQ(world.get_person(0).get_age(), age_group_15_to_34); + ASSERT_EQ(world.get_person(1).get_age(), age_group_35_to_59); } TEST(TestWorld, getSubpopulationCombined) @@ -103,22 +103,21 @@ TEST(TestWorld, findLocation) 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& home = world.get_individualized_location(home_id); - auto& school = world.get_individualized_location(school_id); - auto& work = world.get_individualized_location(work_id); - auto person = make_test_person(home); - person.set_assigned_location(home); - person.set_assigned_location(work); - person.set_assigned_location(school); - - ASSERT_EQ(world.find_location(mio::abm::LocationType::Work, person), work); - ASSERT_EQ(world.find_location(mio::abm::LocationType::School, person), school); - ASSERT_EQ(world.find_location(mio::abm::LocationType::Home, person), home); + auto person_id = add_test_person(world, home_id); + auto& person = world.get_person(person_id); + + person.set_assigned_location(mio::abm::LocationType::Home, home_id); + person.set_assigned_location(mio::abm::LocationType::Work, work_id); + person.set_assigned_location(mio::abm::LocationType::School, school_id); + + EXPECT_EQ(world.find_location(mio::abm::LocationType::Work, person_id), work_id); + EXPECT_EQ(world.find_location(mio::abm::LocationType::School, person_id), school_id); + EXPECT_EQ(world.find_location(mio::abm::LocationType::Home, person_id), home_id); auto&& world_test = std::as_const(world); - ASSERT_EQ(world_test.find_location(mio::abm::LocationType::Work, person), work); - ASSERT_EQ(world_test.find_location(mio::abm::LocationType::School, person), school); - ASSERT_EQ(world_test.find_location(mio::abm::LocationType::Home, person), home); + EXPECT_EQ(world_test.find_location(mio::abm::LocationType::Work, person_id), work_id); + EXPECT_EQ(world_test.find_location(mio::abm::LocationType::School, person_id), school_id); + EXPECT_EQ(world_test.find_location(mio::abm::LocationType::Home, person_id), home_id); } TEST(TestWorld, evolveStateTransition) @@ -145,13 +144,19 @@ TEST(TestWorld, evolveStateTransition) 2 * dt.days(); auto location1 = world.add_location(mio::abm::LocationType::School); - auto& p1 = add_test_person(world, location1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); - auto& p2 = add_test_person(world, location1, age_group_15_to_34, mio::abm::InfectionState::Susceptible); auto location2 = world.add_location(mio::abm::LocationType::Work); - auto& p3 = add_test_person(world, location2, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - p1.set_assigned_location(location1); - p2.set_assigned_location(location1); - p3.set_assigned_location(location2); + + add_test_person(world, location1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); + add_test_person(world, location1, age_group_15_to_34, mio::abm::InfectionState::Susceptible); + add_test_person(world, location2, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); + + auto& p1 = world.get_persons()[0]; + auto& p2 = world.get_persons()[1]; + auto& p3 = world.get_persons()[2]; + + p1.set_assigned_location(mio::abm::LocationType::School, location1); + p2.set_assigned_location(mio::abm::LocationType::School, location1); + p3.set_assigned_location(mio::abm::LocationType::Work, location2); //setup mock so p2 becomes infected ScopedMockDistribution>>> @@ -201,18 +206,19 @@ TEST(TestWorld, evolveMigration) .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); + auto pid2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); + auto pid1 = + 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& p1 = world.get_person(pid1); + auto& p2 = world.get_person(pid2); - auto& school = world.get_individualized_location(school_id); - auto& work = world.get_individualized_location(work_id); + p1.set_assigned_location(mio::abm::LocationType::School, school_id); + p2.set_assigned_location(mio::abm::LocationType::School, school_id); + p1.set_assigned_location(mio::abm::LocationType::Work, work_id); + p2.set_assigned_location(mio::abm::LocationType::Work, work_id); + p1.set_assigned_location(mio::abm::LocationType::Home, home_id); + p2.set_assigned_location(mio::abm::LocationType::Home, home_id); ScopedMockDistribution>>> mock_exponential_dist; @@ -220,10 +226,10 @@ TEST(TestWorld, evolveMigration) world.evolve(t, dt); - 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); + EXPECT_EQ(p1.get_location(), work_id); + EXPECT_EQ(p2.get_location(), school_id); + EXPECT_EQ(world.get_number_persons(school_id), 1); + EXPECT_EQ(world.get_number_persons(work_id), 1); } { @@ -261,29 +267,37 @@ TEST(TestWorld, evolveMigration) .WillOnce(testing::Return(0.8)) // draw random school hour .WillRepeatedly(testing::Return(1.0)); // this forces p1 and p3 to recover - 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); - auto& p3 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::InfectedSevere, t); - auto& p4 = add_test_person(world, hospital_id, age_group_5_to_14, mio::abm::InfectionState::Recovered, t); - auto& p5 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::Susceptible, t); - p1.set_assigned_location(event_id); - p2.set_assigned_location(event_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); - p3.set_assigned_location(home_id); - p4.set_assigned_location(home_id); - p3.set_assigned_location(hospital_id); - p4.set_assigned_location(hospital_id); - p5.set_assigned_location(event_id); - p5.set_assigned_location(work_id); - p5.set_assigned_location(home_id); + auto pid1 = + add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); + auto pid2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible, t); + auto pid3 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::InfectedSevere, t); + auto pid4 = add_test_person(world, hospital_id, age_group_5_to_14, mio::abm::InfectionState::Recovered, t); + auto pid5 = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::Susceptible, t); + + auto& p1 = world.get_person(pid1); + auto& p2 = world.get_person(pid2); + auto& p3 = world.get_person(pid3); + auto& p4 = world.get_person(pid4); + auto& p5 = world.get_person(pid5); + + p1.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id); + p2.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id); + p1.set_assigned_location(mio::abm::LocationType::Work, work_id); + p2.set_assigned_location(mio::abm::LocationType::Work, work_id); + p1.set_assigned_location(mio::abm::LocationType::Home, home_id); + p2.set_assigned_location(mio::abm::LocationType::Home, home_id); + p3.set_assigned_location(mio::abm::LocationType::Home, home_id); + p4.set_assigned_location(mio::abm::LocationType::Home, home_id); + p3.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id); + p4.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id); + p5.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id); + p5.set_assigned_location(mio::abm::LocationType::Work, work_id); + p5.set_assigned_location(mio::abm::LocationType::Home, home_id); mio::abm::TripList& data = world.get_trip_list(); - mio::abm::Trip trip1(p1.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); - mio::abm::Trip trip2(p2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id); - mio::abm::Trip trip3(p5.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id); + mio::abm::Trip trip1(p1.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); + mio::abm::Trip trip2(p2.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id); + mio::abm::Trip trip3(p5.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id); data.add_trip(trip1); data.add_trip(trip2); data.add_trip(trip3); @@ -296,46 +310,41 @@ TEST(TestWorld, evolveMigration) world.evolve(t, dt); - 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& hospital = world.get_individualized_location(hospital_id); - - EXPECT_EQ(p1.get_location(), work); - EXPECT_EQ(p2.get_location(), event); - EXPECT_EQ(p3.get_location(), hospital); - EXPECT_EQ(p4.get_location(), home); - EXPECT_EQ(p5.get_location(), event); - EXPECT_EQ(event.get_number_persons(), 2); - EXPECT_EQ(work.get_number_persons(), 1); - EXPECT_EQ(home.get_number_persons(), 1); - EXPECT_EQ(hospital.get_number_persons(), 1); - - p1.migrate_to(home); - p2.migrate_to(home); - p5.migrate_to(home); + EXPECT_EQ(p1.get_location(), work_id); + EXPECT_EQ(p2.get_location(), event_id); + EXPECT_EQ(p3.get_location(), hospital_id); + EXPECT_EQ(p4.get_location(), home_id); + EXPECT_EQ(p5.get_location(), event_id); + EXPECT_EQ(world.get_number_persons(event_id), 2); + EXPECT_EQ(world.get_number_persons(work_id), 1); + EXPECT_EQ(world.get_number_persons(home_id), 1); + EXPECT_EQ(world.get_number_persons(hospital_id), 1); + + world.migrate(p1.get_id(), home_id); + world.migrate(p2.get_id(), home_id); + world.migrate(p5.get_id(), home_id); t = mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(8); world.get_trip_list().reset_index(); world.evolve(t, dt); - EXPECT_EQ(p1.get_location(), work); - EXPECT_EQ(p2.get_location(), event); - EXPECT_EQ(p3.get_location(), home); - EXPECT_EQ(p4.get_location(), home); - EXPECT_EQ(p5.get_location(), event); - EXPECT_EQ(event.get_number_persons(), 2); - EXPECT_EQ(work.get_number_persons(), 1); - EXPECT_EQ(home.get_number_persons(), 2); + EXPECT_EQ(p1.get_location(), work_id); + EXPECT_EQ(p2.get_location(), event_id); + EXPECT_EQ(p3.get_location(), home_id); + EXPECT_EQ(p4.get_location(), home_id); + EXPECT_EQ(p5.get_location(), event_id); + EXPECT_EQ(world.get_number_persons(event_id), 2); + EXPECT_EQ(world.get_number_persons(work_id), 1); + EXPECT_EQ(world.get_number_persons(home_id), 2); bool weekend = true; - mio::abm::Trip tripweekend1(p1.get_person_id(), - mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), event_id); - mio::abm::Trip tripweekend2(p2.get_person_id(), - mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), home_id); - mio::abm::Trip tripweekend3(p5.get_person_id(), - mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), work_id); + mio::abm::Trip tripweekend1(p1.get_id(), mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), + event_id); + mio::abm::Trip tripweekend2(p2.get_id(), mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), + home_id); + mio::abm::Trip tripweekend3(p5.get_id(), mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), + work_id); data.add_trip(tripweekend1, weekend); data.add_trip(tripweekend2, weekend); data.add_trip(tripweekend3, weekend); @@ -344,14 +353,14 @@ TEST(TestWorld, evolveMigration) world.evolve(t, dt); - EXPECT_EQ(p1.get_location(), event); - EXPECT_EQ(p2.get_location(), home); - EXPECT_EQ(p3.get_location(), home); - EXPECT_EQ(p4.get_location(), home); - EXPECT_EQ(p5.get_location(), work); - EXPECT_EQ(event.get_number_persons(), 1); - EXPECT_EQ(work.get_number_persons(), 1); - EXPECT_EQ(home.get_number_persons(), 3); + EXPECT_EQ(p1.get_location(), event_id); + EXPECT_EQ(p2.get_location(), home_id); + EXPECT_EQ(p3.get_location(), home_id); + EXPECT_EQ(p4.get_location(), home_id); + EXPECT_EQ(p5.get_location(), work_id); + EXPECT_EQ(world.get_number_persons(event_id), 1); + EXPECT_EQ(world.get_number_persons(work_id), 1); + EXPECT_EQ(world.get_number_persons(home_id), 3); } // Test that a dead person cannot make a movement @@ -371,37 +380,40 @@ TEST(TestWorld, evolveMigration) auto icu_id = world.add_location(mio::abm::LocationType::ICU); auto hospital_id = world.add_location(mio::abm::LocationType::Hospital); // Create a person that is dead at time t - auto& p_dead = add_test_person(world, icu_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t); + add_test_person(world, icu_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t); // Create a person that is severe at hospital and will be dead at time t + dt - auto& p_severe = - add_test_person(world, hospital_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t + dt); - p_dead.set_assigned_location(icu_id); - p_dead.set_assigned_location(work_id); - p_dead.set_assigned_location(home_id); - p_severe.set_assigned_location(hospital_id); - p_severe.set_assigned_location(icu_id); - p_severe.set_assigned_location(home_id); + add_test_person(world, hospital_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t + dt); + + auto& p_dead = world.get_persons()[0]; + auto& p_severe = world.get_persons()[1]; + + p_dead.set_assigned_location(mio::abm::LocationType::ICU, icu_id); + p_dead.set_assigned_location(mio::abm::LocationType::Work, work_id); + p_dead.set_assigned_location(mio::abm::LocationType::Home, home_id); + p_severe.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id); + p_severe.set_assigned_location(mio::abm::LocationType::ICU, icu_id); + p_severe.set_assigned_location(mio::abm::LocationType::Home, home_id); // Add trip to see if a dead person can move outside of cemetery by scheduled mio::abm::TripList& trip_list = world.get_trip_list(); - mio::abm::Trip trip1(p_dead.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(2), work_id, home_id); - mio::abm::Trip trip2(p_dead.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id); - mio::abm::Trip trip3(p_severe.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id); + mio::abm::Trip trip1(p_dead.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(2), work_id, home_id); + mio::abm::Trip trip2(p_dead.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id); + mio::abm::Trip trip3(p_severe.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id); trip_list.add_trip(trip1); trip_list.add_trip(trip2); trip_list.add_trip(trip3); // Check the dead person got burried and the severely infected person starts in Hospital world.evolve(t, dt); - EXPECT_EQ(p_dead.get_location().get_type(), mio::abm::LocationType::Cemetery); + EXPECT_EQ(world.get_location(p_dead.get_id()).get_type(), mio::abm::LocationType::Cemetery); EXPECT_EQ(p_severe.get_infection_state(t), mio::abm::InfectionState::InfectedSevere); - EXPECT_EQ(p_severe.get_location().get_type(), mio::abm::LocationType::Hospital); + EXPECT_EQ(world.get_location(p_severe.get_id()).get_type(), mio::abm::LocationType::Hospital); // Check the dead person is still in Cemetery and the severely infected person dies and got burried world.evolve(t + dt, dt); - EXPECT_EQ(p_dead.get_location().get_type(), mio::abm::LocationType::Cemetery); + EXPECT_EQ(world.get_location(p_dead.get_id()).get_type(), mio::abm::LocationType::Cemetery); EXPECT_EQ(p_severe.get_infection_state(t + dt), mio::abm::InfectionState::Dead); - EXPECT_EQ(p_severe.get_location().get_type(), mio::abm::LocationType::Cemetery); + EXPECT_EQ(world.get_location(p_severe.get_id()).get_type(), mio::abm::LocationType::Cemetery); } } @@ -415,16 +427,17 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) 100; world.parameters.get()[{mio::abm::VirusVariant(0), age_group_15_to_34}] = 100; - auto home_id = world.add_location(mio::abm::LocationType::Home); - auto work_id = world.add_location(mio::abm::LocationType::Work); - auto& home = world.get_individualized_location(home_id); - auto& work = world.get_individualized_location(work_id); + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto& work = world.get_location(work_id); + auto current_time = mio::abm::TimePoint(0); - auto person = + auto pid = add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, current_time); - auto rng_person = mio::abm::Person::RandomNumberGenerator(rng, person); - person.set_assigned_location(home); - person.set_assigned_location(work); + auto& person = world.get_person(pid); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(rng, person); + person.set_assigned_location(mio::abm::LocationType::Home, home_id); + person.set_assigned_location(mio::abm::LocationType::Work, work_id); auto testing_criteria = mio::abm::TestingCriteria(); testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); @@ -545,179 +558,3 @@ TEST(TestWorld, checkParameterConstraints) params.get() = mio::abm::TimePoint(-2); ASSERT_EQ(params.check_constraints(), true); } - -TEST(TestWorld, copyWorld) -{ - auto world = mio::abm::World(num_age_groups); - auto rng = mio::RandomNumberGenerator(); - - world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 4.; - world.use_migration_rules(false); - - auto school_id1 = world.add_location(mio::abm::LocationType::School); - auto school_id2 = world.add_location(mio::abm::LocationType::School); - auto work_id = world.add_location(mio::abm::LocationType::Work); - auto home_id = world.add_location(mio::abm::LocationType::Home); - - auto& school1 = world.get_individualized_location(school_id1); - school1.set_required_mask(mio::abm::MaskType::Surgical); - school1.set_npi_active(true); - auto& school2 = world.get_individualized_location(school_id2); - school2.set_required_mask(mio::abm::MaskType::FFP2); - auto& work = world.get_individualized_location(work_id); - auto& home = world.get_individualized_location(home_id); - - auto& p1 = world.add_person(school_id1, age_group_0_to_4); - auto rng_p1 = mio::abm::Person::RandomNumberGenerator(rng, p1); - p1.add_new_infection(mio::abm::Infection(rng_p1, mio::abm::VirusVariant::Wildtype, p1.get_age(), world.parameters, - mio::abm::TimePoint(0))); - auto& p2 = world.add_person(school_id2, age_group_15_to_34); - p2.set_mask_preferences(std::vector(15, 0.2)); - - mio::abm::TripList& trip_data = world.get_trip_list(); - mio::abm::Trip trip1(p1.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), school_id1, home_id); - mio::abm::Trip trip2(p2.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); - trip_data.add_trip(trip1); - trip_data.add_trip(trip2); - - auto infection_params = - world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] - .value(); - - auto copied_world = mio::abm::World(world); - auto copied_infection_params = - copied_world.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] - .value(); - - // Assert the parameters, trips, locations and persons of copied world are logically equal to that of original world - ASSERT_EQ(copied_infection_params, infection_params); - ASSERT_EQ(copied_world.use_migration_rules(), world.use_migration_rules()); - - mio::abm::TripList& copied_trip_data = copied_world.get_trip_list(); - ASSERT_EQ(copied_trip_data.num_trips(), trip_data.num_trips()); - ASSERT_EQ(copied_trip_data.get_next_trip(false).person_id, trip_data.get_next_trip(false).person_id); - ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_destination, - trip_data.get_next_trip(false).migration_destination); - ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_origin, trip_data.get_next_trip(false).migration_origin); - copied_trip_data.increase_index(); - trip_data.increase_index(); - ASSERT_EQ(copied_trip_data.get_next_trip(false).person_id, trip_data.get_next_trip(false).person_id); - ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_destination, - trip_data.get_next_trip(false).migration_destination); - ASSERT_EQ(copied_trip_data.get_next_trip(false).migration_origin, trip_data.get_next_trip(false).migration_origin); - - ASSERT_EQ(copied_world.get_locations().size(), world.get_locations().size()); - ASSERT_EQ(copied_world.get_locations()[1].get_index(), world.get_locations()[1].get_index()); - ASSERT_EQ(copied_world.get_locations()[2].get_index(), world.get_locations()[2].get_index()); - ASSERT_EQ(copied_world.get_locations()[3].get_index(), world.get_locations()[3].get_index()); - ASSERT_EQ(copied_world.get_locations()[4].get_index(), world.get_locations()[4].get_index()); - ASSERT_EQ(copied_world.get_locations()[1].get_number_persons(), world.get_locations()[1].get_number_persons()); - ASSERT_EQ(copied_world.get_locations()[2].get_number_persons(), world.get_locations()[2].get_number_persons()); - ASSERT_EQ(copied_world.get_locations()[3].get_number_persons(), world.get_locations()[3].get_number_persons()); - ASSERT_EQ(copied_world.get_locations()[4].get_number_persons(), world.get_locations()[4].get_number_persons()); - ASSERT_EQ(copied_world.get_locations()[1].get_npi_active(), world.get_locations()[1].get_npi_active()); - ASSERT_EQ(copied_world.get_locations()[2].get_npi_active(), world.get_locations()[2].get_npi_active()); - ASSERT_EQ(copied_world.get_locations()[3].get_npi_active(), world.get_locations()[3].get_npi_active()); - ASSERT_EQ(copied_world.get_locations()[4].get_npi_active(), world.get_locations()[4].get_npi_active()); - ASSERT_EQ(copied_world.get_locations()[1].get_required_mask(), world.get_locations()[1].get_required_mask()); - ASSERT_EQ(copied_world.get_locations()[2].get_required_mask(), world.get_locations()[2].get_required_mask()); - ASSERT_EQ( - copied_world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), - world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); - ASSERT_EQ( - copied_world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), - mio::abm::InfectionState::Susceptible), - world.get_locations()[1].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Susceptible)); - ASSERT_EQ( - copied_world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), - world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); - ASSERT_EQ( - copied_world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), - mio::abm::InfectionState::Susceptible), - world.get_locations()[2].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Susceptible)); - ASSERT_EQ( - copied_world.get_locations()[3].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), - world.get_locations()[3].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); - ASSERT_EQ( - copied_world.get_locations()[4].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed), - world.get_locations()[4].get_subpopulation(mio::abm::TimePoint(0), mio::abm::InfectionState::Exposed)); - ASSERT_EQ(copied_world.get_locations()[1].get_cells().size(), world.get_locations()[1].get_cells().size()); - ASSERT_EQ(copied_world.get_locations()[2].get_cells().size(), world.get_locations()[2].get_cells().size()); - ASSERT_EQ(copied_world.get_locations()[3].get_cells().size(), world.get_locations()[2].get_cells().size()); - ASSERT_EQ(copied_world.get_locations()[4].get_cells().size(), world.get_locations()[2].get_cells().size()); - ASSERT_EQ(copied_world.get_locations()[1].get_cells()[0].m_persons.size(), - world.get_locations()[1].get_cells()[0].m_persons.size()); - ASSERT_EQ(copied_world.get_locations()[2].get_cells()[0].m_persons.size(), - world.get_locations()[2].get_cells()[0].m_persons.size()); - ASSERT_EQ(copied_world.get_locations()[3].get_cells()[0].m_persons.size(), - world.get_locations()[3].get_cells()[0].m_persons.size()); - ASSERT_EQ(copied_world.get_locations()[4].get_cells()[0].m_persons.size(), - world.get_locations()[4].get_cells()[0].m_persons.size()); - ASSERT_EQ(copied_world.get_locations()[1].get_cells()[0].m_persons[0], - world.get_locations()[1].get_cells()[0].m_persons[0]); - ASSERT_EQ(copied_world.get_locations()[2].get_cells()[0].m_persons[0], - world.get_locations()[2].get_cells()[0].m_persons[0]); - - ASSERT_EQ(copied_world.get_persons().size(), world.get_persons().size()); - ASSERT_EQ(copied_world.get_persons()[0].get_location().get_index(), - world.get_persons()[0].get_location().get_index()); - ASSERT_EQ(copied_world.get_persons()[1].get_location().get_index(), - world.get_persons()[1].get_location().get_index()); - ASSERT_EQ(copied_world.get_persons()[0].get_location().get_type(), - world.get_persons()[0].get_location().get_type()); - ASSERT_EQ(copied_world.get_persons()[1].get_location().get_type(), - world.get_persons()[1].get_location().get_type()); - ASSERT_EQ(copied_world.get_persons()[0].get_infection().get_infection_state(mio::abm::TimePoint(0)), - world.get_persons()[0].get_infection().get_infection_state(mio::abm::TimePoint(0))); - ASSERT_EQ(copied_world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Home), - world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Home)); - ASSERT_EQ(copied_world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Work), - world.get_persons()[0].get_mask_compliance(mio::abm::LocationType::Work)); - ASSERT_EQ(copied_world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Home), - world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Home)); - ASSERT_EQ(copied_world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Work), - world.get_persons()[1].get_mask_compliance(mio::abm::LocationType::Work)); - - // Assert the parameters, trips, locations, persons and their member variables of copied world are stored in different address of original world - ASSERT_NE(&(copied_world.parameters), &world.parameters); - ASSERT_NE(&(copied_world.get_trip_list()), &trip_data); - - ASSERT_NE(&copied_world.get_locations()[1], &world.get_locations()[1]); - ASSERT_NE(&copied_world.get_locations()[2], &world.get_locations()[2]); - ASSERT_NE(&copied_world.get_locations()[3], &world.get_locations()[3]); - ASSERT_NE(&copied_world.get_locations()[4], &world.get_locations()[4]); - ASSERT_NE(&copied_world.get_locations()[1].get_cells(), &world.get_locations()[1].get_cells()); - ASSERT_NE(&copied_world.get_locations()[2].get_cells(), &world.get_locations()[2].get_cells()); - ASSERT_NE(&copied_world.get_locations()[3].get_cells(), &world.get_locations()[3].get_cells()); - ASSERT_NE(&copied_world.get_locations()[4].get_cells(), &world.get_locations()[4].get_cells()); - ASSERT_NE(&(copied_world.get_locations()[1].get_cells()[0]), &(world.get_locations()[1].get_cells()[0])); - ASSERT_NE(&(copied_world.get_locations()[2].get_cells()[0]), &(world.get_locations()[2].get_cells()[0])); - ASSERT_NE(&(copied_world.get_locations()[1].get_cells()[0].m_persons[0]), - &(world.get_locations()[1].get_cells()[0].m_persons[0])); - ASSERT_NE(&(copied_world.get_locations()[2].get_cells()[0].m_persons[0]), - &(world.get_locations()[2].get_cells()[0].m_persons[0])); - - ASSERT_NE(&copied_world.get_persons()[0], &world.get_persons()[0]); - ASSERT_NE(&copied_world.get_persons()[1], &world.get_persons()[1]); - ASSERT_NE(&(copied_world.get_persons()[0].get_location()), &world.get_persons()[0].get_location()); - ASSERT_NE(&(copied_world.get_persons()[1].get_location()), &world.get_persons()[1].get_location()); - ASSERT_NE(&(copied_world.get_locations()[1]), &(world.get_locations()[1])); - ASSERT_NE(&(copied_world.get_locations()[2]), &(world.get_locations()[2])); - ASSERT_NE(&(copied_world.get_persons()[0].get_assigned_locations()), - &world.get_persons()[0].get_assigned_locations()); - ASSERT_NE(&(copied_world.get_persons()[1].get_assigned_locations()), - &world.get_persons()[1].get_assigned_locations()); - ASSERT_NE(&(copied_world.get_persons()[0].get_infection()), &world.get_persons()[0].get_infection()); - ASSERT_NE(&(copied_world.get_persons()[0].get_mask()), &world.get_persons()[0].get_mask()); - ASSERT_NE(&(copied_world.get_persons()[1].get_mask()), &world.get_persons()[1].get_mask()); - ASSERT_NE(&(copied_world.get_persons()[0].get_cells()), &world.get_persons()[0].get_cells()); - ASSERT_NE(&(copied_world.get_persons()[1].get_cells()), &world.get_persons()[1].get_cells()); - - // Evolve the world and check that the copied world has not evolved - copied_world.get_persons()[0].migrate_to(work, {0}); - copied_world.get_persons()[1].migrate_to(home, {0}); - ASSERT_NE(copied_world.get_persons()[0].get_location().get_type(), - world.get_persons()[0].get_location().get_type()); - ASSERT_NE(copied_world.get_persons()[1].get_location().get_type(), - world.get_persons()[1].get_location().get_type()); -} diff --git a/cpp/tests/test_analyze_result.cpp b/cpp/tests/test_analyze_result.cpp index bd05a06f18..73ca8bfb9f 100644 --- a/cpp/tests/test_analyze_result.cpp +++ b/cpp/tests/test_analyze_result.cpp @@ -18,8 +18,11 @@ * limitations under the License. */ #include "abm/analyze_result.h" -#include "memilio/compartments/simulation.h" +#include "abm/world.h" #include "matchers.h" +#include "memilio/compartments/simulation.h" +#include "memilio/data/analyze_result.h" +#include "ode_secir/analyze_result.h" #include "ode_secir/model.h" #include "gtest/gtest.h" diff --git a/cpp/tests/test_json_serializer.cpp b/cpp/tests/test_json_serializer.cpp index e6cd980ac4..9ef34343c3 100644 --- a/cpp/tests/test_json_serializer.cpp +++ b/cpp/tests/test_json_serializer.cpp @@ -487,39 +487,36 @@ TEST(TestJsonSerializer, abmLocation) EXPECT_EQ(r.value(), location); } -TEST(TestJsonSerializer, abmPerson) -{ - auto location = mio::abm::Location(mio::abm::LocationType::School, 0, 6); - auto person = make_test_person(location); - auto js = mio::serialize_json(person); - Json::Value expected_json; - expected_json["Location"]["index"] = Json::UInt(location.get_index()); - expected_json["Location"]["type"] = Json::UInt(location.get_type()); - expected_json["age"] = Json::UInt(2); - expected_json["id"] = Json::UInt(person.get_person_id()); - ASSERT_EQ(js.value(), expected_json); - - // auto r = mio::deserialize_json(expected_json, mio::Tag()); - // ASSERT_THAT(print_wrap(r), IsSuccess()); - // EXPECT_EQ(r.value(), person); -} +// TEST(TestJsonSerializer, abmPerson) // FIXME: (de)serialize is only partially implemented +// { +// auto location = mio::abm::Location(mio::abm::LocationType::School, 0, 6); +// auto person = make_test_person(location); +// auto js = mio::serialize_json(person); +// Json::Value expected_json; +// expected_json["Location"]["index"] = Json::UInt(location.get_id()); +// expected_json["Location"]["type"] = Json::UInt(location.get_type()); +// expected_json["age"] = Json::UInt(2); +// expected_json["id"] = Json::UInt(person.get_id()); +// ASSERT_EQ(js.value(), expected_json); + +// // auto r = mio::deserialize_json(expected_json, mio::Tag()); +// // ASSERT_THAT(print_wrap(r), IsSuccess()); +// // EXPECT_EQ(r.value(), person); +// } TEST(TestJsonSerializer, abmTrip) { - auto world = mio::abm::World(num_age_groups); - auto home_id = world.add_location(mio::abm::LocationType::Home); - auto work_id = world.add_location(mio::abm::LocationType::Work); - auto& home = world.get_individualized_location(home_id); - auto person = make_test_person(home); - mio::abm::Trip trip(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), work_id, home_id); + mio::abm::Location home{mio::abm::LocationType::Home, 0}; + mio::abm::Location work{mio::abm::LocationType::Work, 1}; + auto person = make_test_person(home); + // add a trip from home (0) to work (1) + mio::abm::Trip trip(person.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), 1, 0); auto js = mio::serialize_json(trip, true); Json::Value expected_json; - expected_json["person_id"] = Json::UInt(person.get_person_id()); - expected_json["time"] = Json::Int(mio::abm::hours(8).seconds()); - expected_json["destination_index"] = Json::UInt(work_id.index); - expected_json["destination_type"] = Json::UInt(work_id.type); - expected_json["origin_index"] = Json::UInt(home_id.index); - expected_json["origin_type"] = Json::UInt(home_id.type); + expected_json["person_id"] = Json::UInt(person.get_id()); + expected_json["time"] = Json::Int(mio::abm::hours(8).seconds()); + expected_json["destination"] = Json::UInt(1); // work + expected_json["origin"] = Json::UInt(0); // home ASSERT_EQ(js.value(), expected_json); auto r = mio::deserialize_json(expected_json, mio::Tag()); @@ -527,45 +524,45 @@ TEST(TestJsonSerializer, abmTrip) EXPECT_EQ(r.value(), trip); } -TEST(TestJsonSerializer, abmWorld) -{ - auto world = mio::abm::World(num_age_groups); - auto home_id = world.add_location(mio::abm::LocationType::Home); - auto work_id = world.add_location(mio::abm::LocationType::Work); - auto person = world.add_person(home_id, age_group_15_to_34); - mio::abm::Trip trip1(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), work_id, home_id); - mio::abm::Trip trip2(person.get_person_id(), mio::abm::TimePoint(0) + mio::abm::hours(11), work_id, home_id); - world.get_trip_list().add_trip(trip1, false); - world.get_trip_list().add_trip(trip2, true); - auto js = mio::serialize_json(world); - Json::Value expected_json; - expected_json["num_agegroups"] = Json::UInt(num_age_groups); - expected_json["trips"][0]["person_id"] = Json::UInt(person.get_person_id()); - expected_json["trips"][0]["time"] = Json::Int(mio::abm::hours(8).seconds()); - expected_json["trips"][0]["destination_index"] = Json::UInt(work_id.index); - expected_json["trips"][0]["destination_type"] = Json::UInt(work_id.type); - expected_json["trips"][0]["origin_index"] = Json::UInt(home_id.index); - expected_json["trips"][0]["origin_type"] = Json::UInt(home_id.type); - expected_json["trips"][1]["person_id"] = Json::UInt(person.get_person_id()); - expected_json["trips"][1]["time"] = Json::Int(mio::abm::hours(11).seconds()); - expected_json["trips"][1]["destination_index"] = Json::UInt(work_id.index); - expected_json["trips"][1]["destination_type"] = Json::UInt(work_id.type); - expected_json["trips"][1]["origin_index"] = Json::UInt(home_id.index); - expected_json["trips"][1]["origin_type"] = Json::UInt(home_id.type); - expected_json["locations"][0]["index"] = Json::UInt(0); - expected_json["locations"][0]["type"] = Json::UInt(mio::abm::LocationType::Cemetery); - expected_json["locations"][1]["index"] = Json::UInt(1); - expected_json["locations"][1]["type"] = Json::UInt(mio::abm::LocationType::Home); - expected_json["locations"][2]["index"] = Json::UInt(2); - expected_json["locations"][2]["type"] = Json::UInt(mio::abm::LocationType::Work); - expected_json["persons"][0]["Location"]["index"] = Json::UInt(1); - expected_json["persons"][0]["Location"]["type"] = Json::UInt(mio::abm::LocationType::Home); - expected_json["persons"][0]["age"] = Json::UInt(2); - expected_json["persons"][0]["id"] = Json::UInt(person.get_person_id()); - expected_json["use_migration_rules"] = Json::Value(true); - ASSERT_EQ(js.value(), expected_json); - - // auto r = mio::deserialize_json(expected_json, mio::Tag()); - // ASSERT_THAT(print_wrap(r), IsSuccess()); - // EXPECT_EQ(r.value(), world); -} +// TEST(TestJsonSerializer, abmWorld) // FIXME: (de)serialize is only partially implemented +// { +// auto world = mio::abm::World(num_age_groups); +// auto home_id = world.add_location(mio::abm::LocationType::Home); +// auto work_id = world.add_location(mio::abm::LocationType::Work); +// auto person = world.add_person(home_id, age_group_15_to_34); +// mio::abm::Trip trip1(person, mio::abm::TimePoint(0) + mio::abm::hours(8), work_id, home_id); +// mio::abm::Trip trip2(person, mio::abm::TimePoint(0) + mio::abm::hours(11), work_id, home_id); +// world.get_trip_list().add_trip(trip1, false); +// world.get_trip_list().add_trip(trip2, true); +// auto js = mio::serialize_json(world); +// Json::Value expected_json; +// expected_json["num_agegroups"] = Json::UInt(num_age_groups); +// expected_json["trips"][0]["person_id"] = Json::UInt(person); +// expected_json["trips"][0]["time"] = Json::Int(mio::abm::hours(8).seconds()); +// expected_json["trips"][0]["destination_index"] = Json::UInt(1); // work_id +// expected_json["trips"][0]["destination_type"] = Json::UInt(mio::abm::LocationType::Work); +// expected_json["trips"][0]["origin_index"] = Json::UInt(0); // home_id +// expected_json["trips"][0]["origin_type"] = Json::UInt(mio::abm::LocationType::Home); +// expected_json["trips"][1]["person_id"] = Json::UInt(person); +// expected_json["trips"][1]["time"] = Json::Int(mio::abm::hours(11).seconds()); +// expected_json["trips"][1]["destination_index"] = Json::UInt(1); // work_id +// expected_json["trips"][1]["destination_type"] = Json::UInt(mio::abm::LocationType::Work); +// expected_json["trips"][1]["origin_index"] = Json::UInt(0); // home_id +// expected_json["trips"][1]["origin_type"] = Json::UInt(mio::abm::LocationType::Home); +// expected_json["locations"][0]["index"] = Json::UInt(0); +// expected_json["locations"][0]["type"] = Json::UInt(mio::abm::LocationType::Cemetery); +// expected_json["locations"][1]["index"] = Json::UInt(1); +// expected_json["locations"][1]["type"] = Json::UInt(mio::abm::LocationType::Home); +// expected_json["locations"][2]["index"] = Json::UInt(2); +// expected_json["locations"][2]["type"] = Json::UInt(mio::abm::LocationType::Work); +// expected_json["persons"][0]["Location"]["index"] = Json::UInt(1); +// expected_json["persons"][0]["Location"]["type"] = Json::UInt(mio::abm::LocationType::Home); +// expected_json["persons"][0]["age"] = Json::UInt(2); +// expected_json["persons"][0]["id"] = Json::UInt(person); +// expected_json["use_migration_rules"] = Json::Value(true); +// ASSERT_EQ(js.value(), expected_json); + +// // auto r = mio::deserialize_json(expected_json, mio::Tag()); +// // ASSERT_THAT(print_wrap(r), IsSuccess()); +// // EXPECT_EQ(r.value(), world); +// } diff --git a/pycode/memilio-simulation/memilio/simulation/abm.cpp b/pycode/memilio-simulation/memilio/simulation/abm.cpp index 574c6eedd6..0cbd9f0f8a 100644 --- a/pycode/memilio-simulation/memilio/simulation/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/abm.cpp @@ -25,12 +25,13 @@ #include "utils/index.h" //Includes from MEmilio -#include "abm/abm.h" +#include "abm/simulation.h" #include "pybind11/attr.h" #include "pybind11/cast.h" #include "pybind11/pybind11.h" #include "pybind11/operators.h" +#include #include namespace py = pybind11; @@ -71,14 +72,17 @@ PYBIND11_MODULE(_simulation_abm, m) .def_readwrite("sensitivity", &mio::abm::TestParameters::sensitivity) .def_readwrite("specificity", &mio::abm::TestParameters::specificity); - pymio::bind_CustomIndexArray, mio::abm::VirusVariant, mio::AgeGroup>(m, "_AgeParameterArray"); + pymio::bind_CustomIndexArray, mio::abm::VirusVariant, mio::AgeGroup>( + m, "_AgeParameterArray"); pymio::bind_Index(m, "ExposureTypeIndex"); pymio::bind_ParameterSet(m, "ParametersBase"); pymio::bind_class(m, "Parameters") .def(py::init()) .def("check_constraints", &mio::abm::Parameters::check_constraints); - pymio::bind_ParameterSet(m, "LocalInfectionParameters").def(py::init()); + pymio::bind_ParameterSet( + m, "LocalInfectionParameters") + .def(py::init()); pymio::bind_class(m, "TimeSpan") .def(py::init(), py::arg("seconds") = 0) @@ -126,16 +130,16 @@ PYBIND11_MODULE(_simulation_abm, m) .def(py::self -= mio::abm::TimeSpan{}); pymio::bind_class(m, "LocationId") - .def(py::init([](uint32_t idx, mio::abm::LocationType type) { - return mio::abm::LocationId{idx, type}; - })) - .def_readwrite("index", &mio::abm::LocationId::index) - .def_readwrite("type", &mio::abm::LocationId::type) - .def(py::self == py::self) - .def(py::self != py::self); + .def(py::init(), py::arg("id")) + .def("index", &mio::abm::LocationId::get); + + pymio::bind_class(m, "PersonId") + .def(py::init(), py::arg("id")) + .def("index", &mio::abm::PersonId::get); pymio::bind_class(m, "Person") - .def("set_assigned_location", py::overload_cast(&mio::abm::Person::set_assigned_location)) + .def("set_assigned_location", + py::overload_cast(&mio::abm::Person::set_assigned_location)) .def_property_readonly("location", py::overload_cast<>(&mio::abm::Person::get_location, py::const_)) .def_property_readonly("age", &mio::abm::Person::get_age) .def_property_readonly("is_in_quarantine", &mio::abm::Person::is_in_quarantine); @@ -143,10 +147,12 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_class(m, "TestingCriteria") .def(py::init&, const std::vector&>(), py::arg("age_groups"), py::arg("infection_states")); - + pymio::bind_class(m, "GenericTest").def(py::init<>()); - pymio::bind_class(m, "AntigenTest").def(py::init<>()); - pymio::bind_class(m, "PCRTest").def(py::init<>()); + pymio::bind_class(m, "AntigenTest") + .def(py::init<>()); + pymio::bind_class(m, "PCRTest") + .def(py::init<>()); pymio::bind_class(m, "TestingScheme") .def(py::init(m, "TestingStrategy") - .def(py::init>&>()); + .def(py::init&>()); pymio::bind_class(m, "Location") .def_property_readonly("type", &mio::abm::Location::get_type) - .def_property_readonly("index", &mio::abm::Location::get_index) + .def_property_readonly("id", &mio::abm::Location::get_id) .def_property("infection_parameters", py::overload_cast<>(&mio::abm::Location::get_infection_parameters, py::const_), [](mio::abm::Location& self, mio::abm::LocalInfectionParameters params) { @@ -173,8 +179,8 @@ PYBIND11_MODULE(_simulation_abm, m) }); //copying and moving of ranges enabled below, see PYMIO_IGNORE_VALUE_TYPE - pymio::bind_Range().get_locations())>(m, "_WorldLocationsRange"); - pymio::bind_Range().get_persons())>(m, "_WorldPersonsRange"); + pymio::bind_Range().get_locations())>(m, "_WorldLocationsRange"); + pymio::bind_Range().get_persons())>(m, "_WorldPersonsRange"); pymio::bind_class(m, "Trip") .def(py::init(m, "World") .def(py::init()) .def("add_location", &mio::abm::World::add_location, py::arg("location_type"), py::arg("num_cells") = 1) - .def("add_person", &mio::abm::World::add_person, py::arg("location_id"), py::arg("age_group"), - py::return_value_policy::reference_internal) - .def_property_readonly("locations", &mio::abm::World::get_locations, + .def("add_person", py::overload_cast(&mio::abm::World::add_person), + py::arg("location_id"), py::arg("age_group")) + .def("assign_location", &mio::abm::World::assign_location, py::arg("person_id"), py::arg("location_id")) + .def_property_readonly("locations", py::overload_cast<>(&mio::abm::World::get_locations, py::const_), py::keep_alive<1, 0>{}) //keep this world alive while contents are referenced in ranges - .def_property_readonly("persons", &mio::abm::World::get_persons, py::keep_alive<1, 0>{}) + .def_property_readonly("persons", py::overload_cast<>(&mio::abm::World::get_persons, py::const_), + py::keep_alive<1, 0>{}) .def_property( "trip_list", py::overload_cast<>(&mio::abm::World::get_trip_list), [](mio::abm::World& self, const mio::abm::TripList& list) { diff --git a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py index a77d1c31d4..cbc87b613e 100644 --- a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py +++ b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py @@ -46,7 +46,7 @@ def test_locations(self): social_event_id = world.add_location(abm.LocationType.SocialEvent) self.assertEqual(len(world.locations), 3) - home = world.locations[home_id.index] + home = world.locations[home_id.index()] self.assertEqual(home.type, abm.LocationType.Home) testing_ages = [mio.AgeGroup(0)] @@ -70,15 +70,16 @@ def test_persons(self): home_id = world.add_location(abm.LocationType.Home) social_event_id = world.add_location(abm.LocationType.SocialEvent) - p1 = world.add_person( - home_id, mio.AgeGroup(2)) - p2 = world.add_person( - social_event_id, mio.AgeGroup(5)) + p1_id = world.add_person(home_id, mio.AgeGroup(2)) + p2_id = world.add_person(social_event_id, mio.AgeGroup(5)) + + p1 = world.persons[p1_id.index()] + p2 = world.persons[p2_id.index()] # check persons self.assertEqual(len(world.persons), 2) self.assertEqual(p1.age, mio.AgeGroup(2)) - self.assertEqual(p1.location.index, 1) + self.assertEqual(p1.location.index(), 1) self.assertEqual(world.persons[0], p1) self.assertEqual(world.persons[1], p2) @@ -88,20 +89,15 @@ def test_simulation(self): world = sim.world # add some locations and persons - for type in abm.LocationType.values(): - world.add_location(type) - home_id = abm.LocationId(0, abm.LocationType.Home) - social_event_id = abm.LocationId(0, abm.LocationType.SocialEvent) - work_id = abm.LocationId(0, abm.LocationType.Work) - p1 = world.add_person( - home_id, mio.AgeGroup(0)) - p2 = world.add_person( - home_id, mio.AgeGroup(2)) - for type in abm.LocationType.values(): - p1.set_assigned_location(abm.LocationId(0, type)) - p2.set_assigned_location(abm.LocationId(0, type)) - - social_event = world.locations[social_event_id.index] + home_id = world.add_location(abm.LocationType.Home) + social_event_id = world.add_location(abm.LocationType.SocialEvent) + work_id = world.add_location(abm.LocationType.Work) + p1_id = world.add_person(home_id, mio.AgeGroup(0)) + p2_id = world.add_person(home_id, mio.AgeGroup(2)) + + for loc_id in [home_id, social_event_id, work_id]: + world.assign_location(p1_id, loc_id) + world.assign_location(p2_id, loc_id) world.parameters.InfectedSymptomsToSevere[abm.VirusVariant.Wildtype, mio.AgeGroup( 0)] = 0.0