diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index bee7f41eaa..fc68f3eaa7 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -162,6 +162,7 @@ if(MEMILIO_BUILD_MODELS) add_subdirectory(models/sde_sir) add_subdirectory(models/sde_sirs) add_subdirectory(models/sde_seirvv) + add_subdirectory(models/graph_abm) endif() if(MEMILIO_BUILD_EXAMPLES) diff --git a/cpp/benchmarks/abm.cpp b/cpp/benchmarks/abm.cpp index 9efd4a58c7..a9a179ae1d 100644 --- a/cpp/benchmarks/abm.cpp +++ b/cpp/benchmarks/abm.cpp @@ -21,7 +21,7 @@ #include "benchmark/benchmark.h" -mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list seeds) +mio::abm::Simulation<> make_simulation(size_t num_persons, std::initializer_list seeds) { auto rng = mio::RandomNumberGenerator(); rng.seed(seeds); @@ -45,7 +45,7 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list::get_instance()( model.get_rng(), size_t(0), model.parameters.get_num_groups() - 1)); auto person = model.add_person(home, age); - model.assign_location(person, home); + model.assign_location(uint32_t(i), home); home_size++; } @@ -59,16 +59,16 @@ mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list::get_instance()(model.get_rng(), size_t(0), num_locs - 1); - model.assign_location(person.get_id(), locs[loc_idx]); + model.assign_location(uint32_t(p), locs[loc_idx]); } } //infections and masks for (auto& person : model.get_persons()) { - auto prng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); + auto prng = mio::abm::PersonalRandomNumberGenerator(person); //some % of people are infected, large enough to have some infection activity without everyone dying auto pct_infected = 0.05; if (mio::UniformDistribution::get_instance()(prng, 0.0, 1.0) < pct_infected) { diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 1d13454b6a..0f627f71a7 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -128,6 +128,10 @@ add_executable(history_example history.cpp) target_link_libraries(history_example PRIVATE memilio) target_compile_options(history_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) +add_executable(graph_abm_example graph_abm.cpp) +target_link_libraries(graph_abm_example PRIVATE memilio graph_abm abm) +target_compile_options(graph_abm_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + if(MEMILIO_HAS_JSONCPP) add_executable(ode_secir_read_graph_example ode_secir_read_graph.cpp) target_link_libraries(ode_secir_read_graph_example PRIVATE memilio ode_secir) @@ -164,7 +168,7 @@ if(MEMILIO_HAS_HDF5) endif() if(MEMILIO_HAS_JSONCPP) - add_executable(ide_initialization_example ide_initialization.cpp) - target_link_libraries(ide_initialization_example PRIVATE memilio ide_secir) - target_compile_options(ide_initialization_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + add_executable(ide_initialization_example ide_initialization.cpp) + target_link_libraries(ide_initialization_example PRIVATE memilio ide_secir) + target_compile_options(ide_initialization_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) endif() diff --git a/cpp/examples/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index 0b9873021b..9eba7358d3 100644 --- a/cpp/examples/abm_history_object.cpp +++ b/cpp/examples/abm_history_object.cpp @@ -140,7 +140,7 @@ int main() // The infection states are chosen randomly. auto persons = model.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(person); mio::abm::InfectionState infection_state = (mio::abm::InfectionState)(rand() % ((uint32_t)mio::abm::InfectionState::Count - 1)); if (infection_state != mio::abm::InfectionState::Susceptible) @@ -176,14 +176,14 @@ int main() struct LogTimePoint : mio::LogAlways { using Type = double; - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation<>& sim) { return sim.get_time().hours(); } }; struct LogLocationIds : mio::LogOnce { using Type = std::vector>; - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation<>& sim) { Type location_ids{}; for (auto& location : sim.get_model().get_locations()) { diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index c5af2510e2..aab66e3f62 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -55,7 +55,7 @@ int main() // For more than 1 family households we need families. These are parents and children and randoms (which are distributed like the data we have for these households). auto child = mio::abm::HouseholdMember(num_age_groups); // A child is 50/50% 0-4 or 5-14. child.set_age_weight(age_group_0_to_4, 1); - child.set_age_weight(age_group_0_to_4, 1); + child.set_age_weight(age_group_5_to_14, 1); auto parent = mio::abm::HouseholdMember(num_age_groups); // A parent is 50/50% 15-34 or 35-59. parent.set_age_weight(age_group_15_to_34, 1); @@ -121,7 +121,7 @@ int main() for (auto& person : model.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), infection_distribution)); - auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), model.parameters, start_date, infection_state)); @@ -138,7 +138,7 @@ int main() model.assign_location(id, hospital); model.assign_location(id, icu); //assign work/school to people depending on their age - if (person.get_age() == age_group_0_to_4) { + if (person.get_age() == age_group_5_to_14) { model.assign_location(id, school); } if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { diff --git a/cpp/examples/graph_abm.cpp b/cpp/examples/graph_abm.cpp new file mode 100644 index 0000000000..a4eedb9d55 --- /dev/null +++ b/cpp/examples/graph_abm.cpp @@ -0,0 +1,268 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Julia Bicker +* +* 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/household.h" +#include "abm/model.h" +#include "abm/infection_state.h" +#include "abm/location_type.h" +#include "abm/time.h" +#include "abm/person_id.h" +#include "graph_abm/graph_abm_mobility.h" +#include "graph_abm/graph_abmodel.h" +#include "memilio/io/history.h" +#include "memilio/mobility/graph.h" +#include +#include +#include +#include +#include +#include + +//Logger +struct Logger : mio::LogAlways { + /** + * A vector of tuples with the Location information i.e. each tuple contains the following information: + * - The LocationId (including the model id) + * - The total number of Persons at the location + * - A map containing the number of Persons per InfectionState at the location + */ + using Type = std::vector>>; + static Type log(const mio::abm::Simulation& sim) + { + Type location_information{}; + location_information.reserve(size_t(mio::abm::LocationType::Count)); + auto t = sim.get_time(); + for (auto&& loc : sim.get_model().get_locations()) { + std::map persons_per_infection_state; + for (size_t i = 0; i < static_cast(mio::abm::InfectionState::Count); ++i) { + auto inf_state = mio::abm::InfectionState(i); + persons_per_infection_state.insert( + {inf_state, sim.get_model().get_subpopulation(loc.get_id(), t, inf_state)}); + } + location_information.push_back(std::make_tuple(loc.get_model_id(), loc.get_type(), loc.get_id(), + sim.get_model().get_number_persons(loc.get_id()), + persons_per_infection_state)); + } + return location_information; + } +}; + +int main() +{ + // This is an example with three age groups representing children, adults and seniors. + size_t num_age_groups = 3; + const auto age_group_children = mio::AgeGroup(0); + const auto age_group_adults = mio::AgeGroup(1); + const auto age_group_seniors = mio::AgeGroup(2); + + auto model1 = mio::GraphABModel(num_age_groups, 0); + + //Set infection parameters + model1.parameters.get() = 4.; + model1.parameters.get() = 2.; + model1.parameters.get() = 4.; + model1.parameters.get() = 5.; + model1.parameters.get() = 6.; + model1.parameters.get() = 8.; + model1.parameters.get() = 7.; + model1.parameters.get() = 10.; + model1.parameters.get() = 11.; + + //Age group 0 goes to school and age group 1 goes to work + model1.parameters.get()[age_group_children] = true; + model1.parameters.get()[age_group_adults] = true; + + //Household members can be child, parent or senior + auto child = mio::abm::HouseholdMember(num_age_groups); + child.set_age_weight(age_group_children, 1); + auto parent = mio::abm::HouseholdMember(num_age_groups); + parent.set_age_weight(age_group_adults, 1); + auto adult = mio::abm::HouseholdMember(num_age_groups); + adult.set_age_weight(age_group_adults, 1); + adult.set_age_weight(age_group_seniors, 1); + + //Single-Person households + auto single_hh = mio::abm::Household(); + single_hh.add_members(adult, 1); + + //Two-Adult household + auto two_adult_hh = mio::abm::Household(); + two_adult_hh.add_members(adult, 2); + + //Single-Parent household + auto single_parent_hh = mio::abm::Household(); + single_parent_hh.add_members(child, 1); + single_parent_hh.add_members(parent, 1); + + //Family household + auto family_hh = mio::abm::Household(); + family_hh.add_members(child, 1); + family_hh.add_members(parent, 2); + + //Household groups for model 1 + auto single_hh_group_m1 = mio::abm::HouseholdGroup(); + single_hh_group_m1.add_households(single_hh, 5); + auto two_adult_hh_group_m1 = mio::abm::HouseholdGroup(); + two_adult_hh_group_m1.add_households(two_adult_hh, 3); + auto single_parent_hh_group_m1 = mio::abm::HouseholdGroup(); + single_parent_hh_group_m1.add_households(single_parent_hh, 5); + auto family_hh_group_m1 = mio::abm::HouseholdGroup(); + family_hh_group_m1.add_households(family_hh, 10); + add_household_group_to_model(model1, single_hh_group_m1); + add_household_group_to_model(model1, two_adult_hh_group_m1); + add_household_group_to_model(model1, single_hh_group_m1); + add_household_group_to_model(model1, family_hh_group_m1); + + auto model2 = mio::GraphABModel(num_age_groups, 1); + + //Set infection parameters + model2.parameters.get() = 4.; + model2.parameters.get() = 2.; + model2.parameters.get() = 4.; + model2.parameters.get() = 5.; + model2.parameters.get() = 6.; + model2.parameters.get() = 8.; + model2.parameters.get() = 7.; + model2.parameters.get() = 10.; + model2.parameters.get() = 11.; + + //Age group 0 goes to school and age group 1 goes to work + model2.parameters.get()[age_group_children] = true; + model2.parameters.get()[age_group_adults] = true; + + //Household groups for model 2 + auto single_hh_group_m2 = mio::abm::HouseholdGroup(); + single_hh_group_m2.add_households(single_hh, 6); + auto two_adult_hh_group_m2 = mio::abm::HouseholdGroup(); + two_adult_hh_group_m2.add_households(two_adult_hh, 2); + auto single_parent_hh_group_m2 = mio::abm::HouseholdGroup(); + single_parent_hh_group_m2.add_households(single_parent_hh, 10); + auto family_hh_group_m2 = mio::abm::HouseholdGroup(); + family_hh_group_m2.add_households(family_hh, 11); + add_household_group_to_model(model2, single_hh_group_m2); + add_household_group_to_model(model2, two_adult_hh_group_m2); + add_household_group_to_model(model2, single_hh_group_m2); + add_household_group_to_model(model2, family_hh_group_m2); + + //Create locations for both models + //model 1 + auto event_m1 = model1.add_location(mio::abm::LocationType::SocialEvent); + model1.get_location(event_m1).get_infection_parameters().set(10); + auto hospital_m1 = model1.add_location(mio::abm::LocationType::Hospital); + model1.get_location(hospital_m1).get_infection_parameters().set(10); + auto icu_m1 = model1.add_location(mio::abm::LocationType::ICU); + model1.get_location(icu_m1).get_infection_parameters().set(5); + auto shop_m1 = model1.add_location(mio::abm::LocationType::BasicsShop); + model1.get_location(shop_m1).get_infection_parameters().set(20); + auto school_m1 = model1.add_location(mio::abm::LocationType::School); + model1.get_location(school_m1).get_infection_parameters().set(20); + auto work_m1 = model1.add_location(mio::abm::LocationType::Work); + model1.get_location(work_m1).get_infection_parameters().set(10); + //model 2 + auto event_m2 = model2.add_location(mio::abm::LocationType::SocialEvent); + model2.get_location(event_m2).get_infection_parameters().set(10); + auto hospital_m2 = model2.add_location(mio::abm::LocationType::Hospital); + model2.get_location(hospital_m2).get_infection_parameters().set(10); + auto icu_m2 = model2.add_location(mio::abm::LocationType::ICU); + model2.get_location(icu_m2).get_infection_parameters().set(5); + auto shop_m2 = model2.add_location(mio::abm::LocationType::BasicsShop); + model2.get_location(shop_m2).get_infection_parameters().set(20); + auto school_m2 = model2.add_location(mio::abm::LocationType::School); + model2.get_location(school_m2).get_infection_parameters().set(20); + auto work_m2 = model2.add_location(mio::abm::LocationType::Work); + model2.get_location(work_m2).get_infection_parameters().set(10); + + auto start_date = mio::abm::TimePoint(0); + auto end_date = mio::abm::TimePoint(0) + mio::abm::days(30); + + //Assign infection states and locations to persons from model 1 + std::vector infection_distribution_m1{0.5, 0.3, 0.05, 0.05, 0.05, 0.05, 0.0, 0.0}; + for (auto& person : model1.get_persons()) { + mio::abm::InfectionState infection_state = mio::abm::InfectionState( + mio::DiscreteDistribution::get_instance()(model1.get_rng(), infection_distribution_m1)); + auto rng = mio::abm::PersonalRandomNumberGenerator(person); + if (infection_state != mio::abm::InfectionState::Susceptible) { + person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), + model1.parameters, start_date, infection_state)); + } + person.set_assigned_location(mio::abm::LocationType::SocialEvent, event_m1, model1.get_id()); + person.set_assigned_location(mio::abm::LocationType::BasicsShop, shop_m1, model1.get_id()); + person.set_assigned_location(mio::abm::LocationType::Hospital, hospital_m1, model1.get_id()); + person.set_assigned_location(mio::abm::LocationType::ICU, icu_m1, model1.get_id()); + if (person.get_age() == age_group_children) { + person.set_assigned_location(mio::abm::LocationType::School, school_m1, model1.get_id()); + } + if (person.get_age() == age_group_adults) { + //10% of adults in model 1 work in model 2 + size_t work_model = mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), + std::vector{0.9, 0.1}); + if (work_model == 1) { //person works in other model + person.set_assigned_location(mio::abm::LocationType::Work, work_m2, model2.get_id()); + } + else { //person works in same model + person.set_assigned_location(mio::abm::LocationType::Work, work_m1, model1.get_id()); + } + } + } + + //Assign infection states and locations to persons from model 2 + std::vector infection_distribution_m2{0.7, 0.1, 0.05, 0.05, 0.1, 0.0, 0.0, 0.0}; + for (auto& person : model2.get_persons()) { + mio::abm::InfectionState infection_state = mio::abm::InfectionState( + mio::DiscreteDistribution::get_instance()(model2.get_rng(), infection_distribution_m2)); + auto rng = mio::abm::PersonalRandomNumberGenerator(person); + if (infection_state != mio::abm::InfectionState::Susceptible) { + person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), + model2.parameters, start_date, infection_state)); + } + person.set_assigned_location(mio::abm::LocationType::SocialEvent, event_m2, model2.get_id()); + person.set_assigned_location(mio::abm::LocationType::BasicsShop, shop_m2, model2.get_id()); + person.set_assigned_location(mio::abm::LocationType::Hospital, hospital_m2, model2.get_id()); + person.set_assigned_location(mio::abm::LocationType::ICU, icu_m2, model2.get_id()); + if (person.get_age() == age_group_children) { + person.set_assigned_location(mio::abm::LocationType::School, school_m2, model2.get_id()); + } + if (person.get_age() == age_group_adults) { + //20% of adults in model 2 work in model 1 + size_t work_model = mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), + std::vector{0.2, 0.8}); + if (work_model == 1) { //person works in same model + person.set_assigned_location(mio::abm::LocationType::Work, work_m2, model2.get_id()); + } + else { //person works in other model + person.set_assigned_location(mio::abm::LocationType::Work, work_m1, model1.get_id()); + } + } + } + + using HistoryType = mio::History; + mio::Graph, mio::ABMMobilityEdge> graph; + graph.add_node(model1.get_id(), HistoryType{}, start_date, std::move(model1)); + graph.add_node(model2.get_id(), HistoryType{}, start_date, std::move(model2)); + graph.add_edge(model1.get_id(), model2.get_id()); + graph.add_edge(model2.get_id(), model1.get_id()); + + auto exchange_time_span = mio::abm::hours(12); + auto sim = mio::make_abm_graph_sim(start_date, exchange_time_span, std::move(graph)); + sim.advance(end_date); + + return 0; +} diff --git a/cpp/memilio/mobility/README.md b/cpp/memilio/mobility/README.md index 043854e4fd..9878cc60b5 100644 --- a/cpp/memilio/mobility/README.md +++ b/cpp/memilio/mobility/README.md @@ -3,7 +3,7 @@ This directory contains a module to loosely couple multiple simulation instances and model the mobility between them. Each instance can e.g. represent a different geographical region. The regions are stored as nodes in a graph, with edges between them representing the mobility between the regions. Currently, only compartment models are supported as nodes, but it will be extended for any model. At each time step, the simulation executes two following phases: -1. Evolve the simulation for each node independently +1. Advance the simulation for each node independently 2. Exchange people between nodes along the edges. The number of people exchanged depends on coefficients. The coefficient `a_i` of edge `e_xy` represents the percentage of people in compartment `i` moving from node `x` to node `y`. Like the contact matrices used in compartment models, the coefficients may contain dampings that change their value over time. During the next time step, the exchanged population will stay at their destination and participate in the evolution of that simulation. Afterwards, the people return. The total number of people returning is the same as the number that left. But the number of people in each compartment is adjusted according to the epidemiological situation in the destination node, e.g. some susceptible people that went from one node to another will have been exposed, so they return in a different compartment. See the [mobility header](metapopulation_mobility_instant.h) and the `MobilityEdge` and `SimulationNode` classes for technical details of the two phases. diff --git a/cpp/memilio/mobility/graph_simulation.h b/cpp/memilio/mobility/graph_simulation.h index 1537681f8b..b911b43fbd 100644 --- a/cpp/memilio/mobility/graph_simulation.h +++ b/cpp/memilio/mobility/graph_simulation.h @@ -29,16 +29,14 @@ namespace mio /** * @brief abstract simulation on a graph with alternating node and edge actions */ -template > +template class GraphSimulationBase { public: - using node_function = std::function; - + using node_function = node_f; using edge_function = edge_f; - GraphSimulationBase(double t0, double dt, const Graph& g, const node_function& node_func, + GraphSimulationBase(Timepoint t0, Timespan dt, const Graph& g, const node_function& node_func, const edge_function&& edge_func) : m_t(t0) , m_dt(dt) @@ -48,7 +46,7 @@ class GraphSimulationBase { } - GraphSimulationBase(double t0, double dt, Graph&& g, const node_function& node_func, + GraphSimulationBase(Timepoint t0, Timespan dt, Graph&& g, const node_function& node_func, const edge_function&& edge_func) : m_t(t0) , m_dt(dt) @@ -58,28 +56,7 @@ class GraphSimulationBase { } - void advance(double t_max = 1.0) - { - auto dt = m_dt; - while (m_t < t_max) { - if (m_t + dt > t_max) { - dt = t_max - m_t; - } - - for (auto& n : m_graph.nodes()) { - m_node_func(m_t, dt, n.property); - } - - m_t += dt; - - for (auto& e : m_graph.edges()) { - m_edge_func(m_t, dt, e.property, m_graph.nodes()[e.start_node_idx].property, - m_graph.nodes()[e.end_node_idx].property); - } - } - } - - double get_t() const + Timepoint get_t() const { return m_t; } @@ -100,28 +77,56 @@ class GraphSimulationBase } protected: - double m_t; - double m_dt; + Timepoint m_t; + Timespan m_dt; Graph m_graph; node_function m_node_func; edge_function m_edge_func; }; -template -class GraphSimulation : public GraphSimulationBase +template +class GraphSimulation : public GraphSimulationBase { - using GraphSimulationBase::GraphSimulationBase; + using Base = GraphSimulationBase; + using Base::GraphSimulationBase; + +public: + void advance(Timepoint t_max = 1.0) + { + auto dt = Base::m_dt; + while (Base::m_t < t_max) { + if (Base::m_t + dt > t_max) { + dt = t_max - Base::m_t; + } + + for (auto& n : Base::m_graph.nodes()) { + Base::m_node_func(Base::m_t, dt, n.property); + } + + Base::m_t += dt; + + for (auto& e : Base::m_graph.edges()) { + Base::m_edge_func(Base::m_t, dt, e.property, Base::m_graph.nodes()[e.start_node_idx].property, + Base::m_graph.nodes()[e.end_node_idx].property); + } + } + } }; template class GraphSimulationStochastic - : public GraphSimulationBase> + typename Graph::NodeProperty&, typename Graph::NodeProperty&)>, + std::function> { - using Base = - GraphSimulationBase>; + using Base = GraphSimulationBase, + std::function>; using node_function = typename Base::node_function; using edge_function = typename Base::edge_function; @@ -247,11 +252,11 @@ class GraphSimulationStochastic RandomNumberGenerator m_rng; }; -template -auto make_graph_sim(FP t0, FP dt, Graph&& g, NodeF&& node_func, EdgeF&& edge_func) +template +auto make_graph_sim(Timepoint t0, Timespan dt, Graph&& g, NodeF&& node_func, EdgeF&& edge_func) { - return GraphSimulation>(t0, dt, std::forward(g), std::forward(node_func), - std::forward(edge_func)); + return GraphSimulation, Timepoint, Timespan, EdgeF, NodeF>( + t0, dt, std::forward(g), std::forward(node_func), std::forward(edge_func)); } template diff --git a/cpp/memilio/mobility/metapopulation_mobility_instant.h b/cpp/memilio/mobility/metapopulation_mobility_instant.h index 1fccf00d1b..b64874d43b 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_instant.h +++ b/cpp/memilio/mobility/metapopulation_mobility_instant.h @@ -92,7 +92,7 @@ class SimulationNode return m_t0; } - void evolve(double t, double dt) + void advance(double t, double dt) { m_simulation.advance(t + dt); m_last_state = m_simulation.get_result().get_last_value(); @@ -535,8 +535,8 @@ auto get_mobility_factors(const SimulationNode& node, double t, const Eigen * detect a get_mobility_factors function for the Model type. */ template -using test_commuters_expr_t = decltype( - test_commuters(std::declval(), std::declval&>(), std::declval())); +using test_commuters_expr_t = decltype(test_commuters( + std::declval(), std::declval&>(), std::declval())); /** * Test persons when moving from their source node. @@ -642,12 +642,12 @@ void MobilityEdge::apply_mobility(FP t, FP dt, SimulationNode& node_fro /** * edge functor for mobility-based simulation. - * @see SimulationNode::evolve + * @see SimulationNode::advance */ template -void evolve_model(double t, double dt, SimulationNode& node) +void advance_model(double t, double dt, SimulationNode& node) { - node.evolve(t, dt); + node.advance(t, dt); } /** @@ -672,19 +672,24 @@ void apply_mobility(FP t, FP dt, MobilityEdge& mobilityEdge, SimulationNode< * @{ */ template -GraphSimulation, MobilityEdge>> +GraphSimulation, MobilityEdge>, FP, FP, + void (*)(double, double, mio::MobilityEdge<>&, mio::SimulationNode&, mio::SimulationNode&), + void (*)(double, double, mio::SimulationNode&)> make_mobility_sim(FP t0, FP dt, const Graph, MobilityEdge>& graph) { - return make_graph_sim(t0, dt, graph, &evolve_model, + return make_graph_sim(t0, dt, graph, static_cast&)>(&advance_model), static_cast&, SimulationNode&, SimulationNode&)>( &apply_mobility)); } template -GraphSimulation, MobilityEdge>> +GraphSimulation, MobilityEdge>, FP, FP, + void (*)(double, double, mio::MobilityEdge<>&, mio::SimulationNode&, mio::SimulationNode&), + void (*)(double, double, mio::SimulationNode&)> make_mobility_sim(FP t0, FP dt, Graph, MobilityEdge>&& graph) { - return make_graph_sim(t0, dt, std::move(graph), &evolve_model, + return make_graph_sim(t0, dt, std::move(graph), + static_cast&)>(&advance_model), static_cast&, SimulationNode&, SimulationNode&)>( &apply_mobility)); } diff --git a/cpp/memilio/mobility/metapopulation_mobility_stochastic.h b/cpp/memilio/mobility/metapopulation_mobility_stochastic.h index 6b18802cc1..d63e8d3209 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_stochastic.h +++ b/cpp/memilio/mobility/metapopulation_mobility_stochastic.h @@ -227,7 +227,7 @@ GraphSimulationStochastic, MobilityEdgeStochastic>> make_mobility_sim(double t0, double dt, const Graph, MobilityEdgeStochastic>& graph) { return make_graph_sim_stochastic( - t0, dt, graph, &evolve_model, + t0, dt, graph, &advance_model, static_cast&, SimulationNode&)>( &apply_mobility)); } @@ -237,7 +237,7 @@ GraphSimulationStochastic, MobilityEdgeStochastic>> make_mobility_sim(double t0, double dt, Graph, MobilityEdgeStochastic>&& graph) { return make_graph_sim_stochastic( - t0, dt, std::move(graph), &evolve_model, + t0, dt, std::move(graph), &advance_model, static_cast&, SimulationNode&)>( &apply_mobility)); } diff --git a/cpp/models/abm/common_abm_loggers.h b/cpp/models/abm/common_abm_loggers.h index 35b7873eb2..a453d9554b 100644 --- a/cpp/models/abm/common_abm_loggers.h +++ b/cpp/models/abm/common_abm_loggers.h @@ -91,7 +91,7 @@ struct LogLocationInformation : mio::LogOnce { * -# The number of cells in the location. * -# The capacity of the location. */ - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation<>& sim) { Type location_information{}; for (auto& location : sim.get_model().get_locations()) { @@ -120,7 +120,7 @@ struct LogPersonInformation : mio::LogOnce { * -# The index of the home location. * -# The age group of the person. */ - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation<>& sim) { Type person_information{}; person_information.reserve(sim.get_model().get_persons().size()); @@ -150,7 +150,7 @@ struct LogDataForMobility : mio::LogAlways { * -# The activity type. * -# The infection state. */ - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation<>& sim) { Type mobility_data{}; for (Person p : sim.get_model().get_persons()) { @@ -172,7 +172,7 @@ struct LogInfectionState : mio::LogAlways { * @param[in] sim The simulation of the abm. * @return A pair of the TimePoint and the TimeSeries of the number of Person%s in an #InfectionState. */ - static Type log(const mio::abm::Simulation& sim) + static Type log(const mio::abm::Simulation<>& sim) { Eigen::VectorXd sum = Eigen::VectorXd::Zero(Eigen::Index(mio::abm::InfectionState::Count)); diff --git a/cpp/models/abm/location.cpp b/cpp/models/abm/location.cpp index a7269c36b2..4260062f76 100644 --- a/cpp/models/abm/location.cpp +++ b/cpp/models/abm/location.cpp @@ -27,12 +27,13 @@ namespace mio namespace abm { -Location::Location(LocationType loc_type, LocationId loc_id, size_t num_agegroups, uint32_t num_cells) +Location::Location(LocationType loc_type, LocationId loc_id, size_t num_agegroups, int model_id, uint32_t num_cells) : m_type(loc_type) , m_id(loc_id) , m_parameters(num_agegroups) , m_cells(num_cells) , m_required_mask(MaskType::None) + , m_model_id(model_id) { assert(num_cells > 0 && "Number of cells has to be larger than 0."); } diff --git a/cpp/models/abm/location.h b/cpp/models/abm/location.h index 0bd435fda4..cd18d1b361 100644 --- a/cpp/models/abm/location.h +++ b/cpp/models/abm/location.h @@ -119,19 +119,22 @@ class Location * @param[in] loc_type The #LocationType. * @param[in] loc_id The index of the Location in the Model. * @param[in] num_agegroups [Default: 1] The number of age groups in the model. + * @param[in] model_id [Default: 0] The model id the Location is in. * @param[in] num_cells [Default: 1] The number of Cell%s in which the Location is divided. */ - explicit Location(LocationType loc_type, LocationId loc_id, size_t num_agegroups = 1, uint32_t num_cells = 1); + explicit Location(LocationType loc_type, LocationId loc_id, size_t num_agegroups = 1, int model_id = 0, + uint32_t num_cells = 1); /** * @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. */ - explicit Location(const Location& other, LocationId id) + explicit Location(const Location& other, LocationId id, int model_id = 0) : Location(other) { - m_id = id; + m_id = id; + m_model_id = model_id; } /** @@ -271,6 +274,15 @@ class Location .add("geographical_location", m_geographical_location); } + /** + * @brief Get the model id the location is in. Is only relevant for graph ABM or hybrid model. + * @return Model id of the location + */ + int get_model_id() const + { + return m_model_id; + } + private: friend DefaultFactory; Location() = default; @@ -281,6 +293,7 @@ class 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. GeographicalLocation m_geographical_location; ///< Geographical location (longitude and latitude) of the Location. + int m_model_id; ///< Model id the location is in. Only used for ABM graph model or hybrid graph model. }; } // namespace abm diff --git a/cpp/models/abm/mobility_data.h b/cpp/models/abm/mobility_data.h index 5dc04ba35a..83272cd732 100644 --- a/cpp/models/abm/mobility_data.h +++ b/cpp/models/abm/mobility_data.h @@ -39,7 +39,8 @@ enum class TransportMode : uint32_t PublicTransport, Walking, Other, - Unknown + Unknown, + Count //last!! }; /** diff --git a/cpp/models/abm/model.cpp b/cpp/models/abm/model.cpp index b77709d26d..fead965e23 100755 --- a/cpp/models/abm/model.cpp +++ b/cpp/models/abm/model.cpp @@ -24,10 +24,12 @@ #include "abm/person.h" #include "abm/location.h" #include "abm/mobility_rules.h" +#include "abm/person_id.h" #include "memilio/epidemiology/age_group.h" #include "memilio/utils/logging.h" #include "memilio/utils/mioomp.h" #include "memilio/utils/stl_util.h" +#include #include namespace mio @@ -38,7 +40,7 @@ namespace abm LocationId Model::add_location(LocationType type, uint32_t num_cells) { LocationId id{static_cast(m_locations.size())}; - m_locations.emplace_back(type, id, parameters.get_num_groups(), num_cells); + m_locations.emplace_back(type, id, parameters.get_num_groups(), m_id, num_cells); m_has_locations[size_t(type)] = true; // mark caches for rebuild @@ -51,7 +53,8 @@ LocationId Model::add_location(LocationType type, uint32_t num_cells) PersonId Model::add_person(const LocationId id, AgeGroup age) { - return add_person(Person(m_rng, get_location(id).get_type(), id, age)); + PersonId person_id = (static_cast(m_id)) << 32 | static_cast(m_persons.size()); + return add_person(Person(m_rng, get_location(id).get_type(), id, m_id, age, person_id)); } PersonId Model::add_person(Person&& person) @@ -59,17 +62,16 @@ PersonId Model::add_person(Person&& 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 Model."); + assert(person.get_id() != PersonId::invalid_ID() && "Added Person's unique id must be valid."); 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); + person.set_assigned_location(LocationType::Cemetery, m_cemetery_id, m_id); + m_persons.emplace_back(person); + m_activeness_statuses.push_back(true); 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; + return new_person.get_id(); } void Model::evolve(TimePoint t, TimeSpan dt) @@ -85,8 +87,12 @@ void Model::interaction(TimePoint t, TimeSpan dt) { const uint32_t num_persons = static_cast(m_persons.size()); PRAGMA_OMP(parallel for) - for (uint32_t person_id = 0; person_id < num_persons; ++person_id) { - interact(person_id, t, dt); + for (uint32_t person_index = 0; person_index < num_persons; ++person_index) { + if (m_activeness_statuses[person_index]) { + assert(m_persons[person_index].get_location_model_id() == m_id && + "Person is not in this model but still active."); + interact(m_persons[person_index], t, dt); + } } } @@ -94,65 +100,72 @@ void Model::perform_mobility(TimePoint t, TimeSpan dt) { const uint32_t num_persons = static_cast(m_persons.size()); PRAGMA_OMP(parallel for) - 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_mobility_rule = [&](auto rule) -> bool { - // run mobility rule and check if change of location can actually happen - 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(); - - // the Person cannot move if they do not wear mask as required at targeted location - if (target_location.is_mask_required() && !person.is_compliant(personal_rng, InterventionType::Mask)) { - return false; - } - // the Person cannot move if the capacity of targeted Location is reached - if (target_location.get_id() == current_location || - get_number_persons(target_location.get_id()) >= target_location.get_capacity().persons) { - return false; - } - // the Person cannot move if the performed TestingStrategy is positive - if (!m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { - return false; - } - // update worn mask to target location's requirements - if (target_location.is_mask_required()) { - // if the current MaskProtection level is lower than required, the Person changes mask - if (parameters.get()[person.get_mask().get_type()] < - parameters.get()[target_location.get_required_mask()]) { - person.set_mask(target_location.get_required_mask(), t); + for (uint32_t person_index = 0; person_index < num_persons; ++person_index) { + if (m_activeness_statuses[person_index]) { + Person& person = m_persons[person_index]; + auto personal_rng = PersonalRandomNumberGenerator(person); + + auto try_mobility_rule = [&](auto rule) -> bool { + // run mobility rule and check if change of location can actually happen + auto target_type = rule(personal_rng, person, t, dt, parameters); + if (person.get_assigned_location_model_id(target_type) == m_id) { + const Location& target_location = get_location(find_location(target_type, person)); + const LocationId current_location = person.get_location(); + + // the Person cannot move if they do not wear mask as required at targeted location + if (target_location.is_mask_required() && + !person.is_compliant(personal_rng, InterventionType::Mask)) { + return false; + } + // the Person cannot move if the capacity of targeted Location is reached + if (target_location.get_id() == current_location || + get_number_persons(target_location.get_id()) >= target_location.get_capacity().persons) { + return false; + } + // the Person cannot move if the performed TestingStrategy is positive + if (!m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + return false; + } + // update worn mask to target location's requirements + if (target_location.is_mask_required()) { + // if the current MaskProtection level is lower than required, the Person changes mask + if (parameters.get()[person.get_mask().get_type()] < + parameters.get()[target_location.get_required_mask()]) { + person.set_mask(target_location.get_required_mask(), t); + } + } + else { + person.set_mask(MaskType::None, t); + } + // all requirements are met, move to target location + change_location(person, target_location.get_id()); + return true; } + return false; + }; + + // run mobility rules one after the other if the corresponding location type exists + // shortcutting of bool operators ensures the rules stop after the first rule is applied + if (m_use_mobility_rules) { + (has_locations({LocationType::Cemetery}) && try_mobility_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_mobility_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_mobility_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_mobility_rule(&go_to_icu)) || + (has_locations({LocationType::School, LocationType::Home}) && try_mobility_rule(&go_to_school)) || + (has_locations({LocationType::Work, LocationType::Home}) && try_mobility_rule(&go_to_work)) || + (has_locations({LocationType::BasicsShop, LocationType::Home}) && try_mobility_rule(&go_to_shop)) || + (has_locations({LocationType::SocialEvent, LocationType::Home}) && + try_mobility_rule(&go_to_event)) || + (has_locations({LocationType::Home}) && try_mobility_rule(&go_to_quarantine)); } else { - person.set_mask(MaskType::None, t); + // no daily routine mobility, just infection related + (has_locations({LocationType::Cemetery}) && try_mobility_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_mobility_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_mobility_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_mobility_rule(&go_to_icu)) || + (has_locations({LocationType::Home}) && try_mobility_rule(&go_to_quarantine)); } - // all requirements are met, move to target location - change_location(person_id, target_location.get_id()); - return true; - }; - - // run mobility rules one after the other if the corresponding location type exists - // shortcutting of bool operators ensures the rules stop after the first rule is applied - if (m_use_mobility_rules) { - (has_locations({LocationType::Cemetery}) && try_mobility_rule(&get_buried)) || - (has_locations({LocationType::Home}) && try_mobility_rule(&return_home_when_recovered)) || - (has_locations({LocationType::Hospital}) && try_mobility_rule(&go_to_hospital)) || - (has_locations({LocationType::ICU}) && try_mobility_rule(&go_to_icu)) || - (has_locations({LocationType::School, LocationType::Home}) && try_mobility_rule(&go_to_school)) || - (has_locations({LocationType::Work, LocationType::Home}) && try_mobility_rule(&go_to_work)) || - (has_locations({LocationType::BasicsShop, LocationType::Home}) && try_mobility_rule(&go_to_shop)) || - (has_locations({LocationType::SocialEvent, LocationType::Home}) && try_mobility_rule(&go_to_event)) || - (has_locations({LocationType::Home}) && try_mobility_rule(&go_to_quarantine)); - } - else { - // no daily routine mobility, just infection related - (has_locations({LocationType::Cemetery}) && try_mobility_rule(&get_buried)) || - (has_locations({LocationType::Home}) && try_mobility_rule(&return_home_when_recovered)) || - (has_locations({LocationType::Hospital}) && try_mobility_rule(&go_to_hospital)) || - (has_locations({LocationType::ICU}) && try_mobility_rule(&go_to_icu)) || - (has_locations({LocationType::Home}) && try_mobility_rule(&go_to_quarantine)); } } @@ -165,7 +178,7 @@ void Model::perform_mobility(TimePoint t, TimeSpan dt) m_trip_list.increase_index()) { auto& trip = m_trip_list.get_next_trip(weekend); auto& person = get_person(trip.person_id); - auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); + auto personal_rng = PersonalRandomNumberGenerator(person); // skip the trip if the person is in quarantine or is dead if (person.is_in_quarantine(t, parameters) || person.get_infection_state(t) == InfectionState::Dead) { continue; @@ -180,7 +193,7 @@ void Model::perform_mobility(TimePoint t, TimeSpan dt) continue; } // all requirements are met, move to target location - change_location(person.get_id(), target_location.get_id(), trip.trip_mode); + change_location(person, target_location.get_id(), trip.trip_mode); // update worn mask to target location's requirements if (target_location.is_mask_required()) { // if the current MaskProtection level is lower than required, the Person changes mask @@ -211,7 +224,10 @@ void Model::build_compute_local_population_cache() const } // 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()]; + if (m_activeness_statuses[i]) { + assert(m_persons[i].get_location_model_id() == m_id && "Person is not in this model but still active."); + ++m_local_population_cache[m_persons[i].get_location().get()]; + } } // implicit taskloop barrier } // implicit single barrier } @@ -267,9 +283,12 @@ void Model::compute_exposure_caches(TimePoint t, TimeSpan dt) 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); + if (m_activeness_statuses[i]) { + assert(m_persons[i].get_location_model_id() == m_id && "Person is not in this model but still active."); + mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location], + m_contact_exposure_rates_cache[location], person, + get_location(person.get_location()), t, dt); + } } // implicit taskloop barrier } // implicit single barrier } @@ -305,11 +324,19 @@ auto Model::get_persons() -> Range> return std::make_pair(m_persons.begin(), m_persons.end()); } +auto Model::get_activeness_statuses() const -> Range> +{ + return std::make_pair(m_activeness_statuses.cbegin(), m_activeness_statuses.cend()); +} + +auto Model::get_activeness_statuses() -> Range> +{ + return std::make_pair(m_activeness_statuses.begin(), m_activeness_statuses.end()); +} + LocationId Model::find_location(LocationType type, const PersonId person) const { - 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; + return find_location(type, get_person(person)); } size_t Model::get_subpopulation_combined(TimePoint t, InfectionState s) const diff --git a/cpp/models/abm/model.h b/cpp/models/abm/model.h index 9c342c6303..452210da29 100644 --- a/cpp/models/abm/model.h +++ b/cpp/models/abm/model.h @@ -32,10 +32,12 @@ #include "abm/random_events.h" #include "abm/testing_strategy.h" #include "memilio/epidemiology/age_group.h" +#include "memilio/utils/logging.h" #include "memilio/utils/random_number_generator.h" #include "memilio/utils/stl_util.h" #include +#include #include namespace mio @@ -50,20 +52,26 @@ namespace abm class Model { public: - using LocationIterator = std::vector::iterator; - using ConstLocationIterator = std::vector::const_iterator; - using PersonIterator = std::vector::iterator; - using ConstPersonIterator = std::vector::const_iterator; + using LocationIterator = std::vector::iterator; + using ConstLocationIterator = std::vector::const_iterator; + using PersonIterator = std::vector::iterator; + using ConstPersonIterator = std::vector::const_iterator; + using ActivenessIterator = std::vector::iterator; + using ConstActivenessIterator = std::vector::const_iterator; + using MobilityRuleType = LocationType (*)(PersonalRandomNumberGenerator&, const Person&, TimePoint, TimeSpan, + const Parameters&); /** * @brief Create a Model. * @param[in] num_agegroups The number of AgeGroup%s in the simulated Model. Must be less than MAX_NUM_AGE_GROUPS. */ - Model(size_t num_agegroups) + Model(size_t num_agegroups, int id = 0) : parameters(num_agegroups) + , m_id(id) , m_trip_list() , m_use_mobility_rules(true) , m_cemetery_id(add_location(LocationType::Cemetery)) + , m_person_ids_equal_index(true) { assert(num_agegroups < MAX_NUM_AGE_GROUPS && "MAX_NUM_AGE_GROUPS exceeded."); } @@ -72,16 +80,18 @@ class Model * @brief Create a Model. * @param[in] params Initial simulation parameters. */ - Model(const Parameters& params) + Model(const Parameters& params, int id = 0) : parameters(params.get_num_groups()) + , m_id(id) , m_trip_list() , m_use_mobility_rules(true) , m_cemetery_id(add_location(LocationType::Cemetery)) + , m_person_ids_equal_index(true) { parameters = params; } - Model(const Model& other) + Model(const Model& other, int id = 0) : parameters(other.parameters) , m_local_population_cache() , m_air_exposure_rates_cache() @@ -89,8 +99,10 @@ class Model , m_is_local_population_cache_valid(false) , m_are_exposure_caches_valid(false) , m_exposure_caches_need_rebuild(true) + , m_id(id) , m_persons(other.m_persons) , m_locations(other.m_locations) + , m_activeness_statuses(other.m_activeness_statuses) , m_has_locations(other.m_has_locations) , m_testing_strategy(other.m_testing_strategy) , m_trip_list(other.m_trip_list) @@ -98,6 +110,7 @@ class Model , m_mobility_rules(other.m_mobility_rules) , m_cemetery_id(other.m_cemetery_id) , m_rng(other.m_rng) + , m_person_ids_equal_index(true) { } Model& operator=(const Model&) = default; @@ -183,14 +196,14 @@ class Model * @brief Add a Person to the Model. * @param[in] id The LocationID of the initial Location of the Person. * @param[in] age AgeGroup of the person. - * @return ID of the newly created Person. + * @return Id of the newly created Person. */ PersonId add_person(const LocationId id, AgeGroup age); /** * @brief Adds a copy of a given Person to the Model. * @param[in] person The Person to copy from. - * @return ID of the newly created Person. + * @return Id of the newly created Person. */ PersonId add_person(Person&& person); @@ -212,10 +225,19 @@ class Model Range> get_persons(); /** @} */ + /** + * @brief Get a range of all Person%s activeness statuses in the Model. + * @return A range of all Person%s activeness statuses. + * @{ + */ + Range> get_activeness_statuses() const; + Range> get_activeness_statuses(); + /** @} */ + /** * @brief Find an assigned Location of a Person. * @param[in] type The #LocationType that specifies the assigned Location. - * @param[in] person PersonId of the Person. + * @param[in] person Id of the Person. * @return ID of the Location of LocationType type assigend to person. */ LocationId find_location(LocationType type, const PersonId person) const; @@ -224,12 +246,12 @@ class Model * @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] person The Id 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); + assign_location(get_person(person), location); } /** @@ -310,6 +332,15 @@ class Model return m_rng; } + /** + * Get the model id. Is only relevant for graph abm or hybrid model. + * @return The model id + */ + int get_id() const + { + return m_id; + } + /** * @brief Add a TestingScheme to the set of schemes that are checked for testing at all Locations that have * the LocationType. @@ -328,22 +359,18 @@ class Model /** * @brief Get a reference to a Person from this Model. - * @param[in] id A person's PersonId. + * @param[in] person_id A Person's PersonId. * @return A reference to the Person. - * @{ */ - Person& get_person(PersonId id) + const Person& get_person(PersonId person_id) const { - assert(id.get() < m_persons.size() && "Given PersonId is not in this Model."); - return m_persons[id.get()]; + return get_person_impl(*this, person_id); } - const Person& get_person(PersonId id) const + Person& get_person(PersonId person_id) { - assert(id.get() < m_persons.size() && "Given PersonId is not in this Model."); - return m_persons[id.get()]; + return get_person_impl(*this, person_id); } - /** @} */ /** * @brief Get the number of Person%s of a particular #InfectionState for all Cell%s. @@ -355,7 +382,8 @@ class Model 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; + return p.get_location_model_id() == m_id && p.get_location() == location && + p.get_infection_state(t) == state; }); } @@ -375,7 +403,7 @@ class Model // Change the Location of a Person. this requires that Location is part of this Model. /** * @brief Let a Person change to another Location. - * @param[in] person PersonId of a Person from this Model. + * @param[in] person Id of a person from this Model. * @param[in] destination LocationId of the Location in this Model, which the Person should change to. * @param[in] mode The transport mode the person uses to change the Location. * @param[in] cells The cells within the destination the person should be in. @@ -383,37 +411,18 @@ class Model inline void change_location(PersonId person, LocationId destination, TransportMode mode = TransportMode::Unknown, const std::vector& cells = {0}) { - LocationId origin = get_location(person).get_id(); - const bool has_changed_location = - mio::abm::change_location(get_person(person), get_location(destination), mode, cells); - // if the person has changed location, invalidate exposure caches but keep population caches valid - if (has_changed_location) { - 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()]; - } - } + change_location(get_person(person), destination, mode, cells); } /** * @brief Let a person interact with the population at its current location. - * @param[in] person PersonId of a person from this Model. + * @param[in] person Id of a person from this Model. * @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); + interact(get_person(person), t, dt); } /** @@ -429,6 +438,18 @@ class Model return m_locations[id.get()]; } + /** + * @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 reference to the Person the location will be assigned to. + * @param[in] location The LocationId of the Location. + */ + void assign_location(Person& person, LocationId location) + { + person.set_assigned_location(get_location(location).get_type(), location, m_id); + } + Location& get_location(LocationId id) { assert(id != LocationId::invalid_id() && "Given LocationId must be valid."); @@ -439,7 +460,7 @@ class Model /** * @brief Get a reference to the location of a person. - * @param[in] id PersonId of a person. + * @param[in] id Id of a person. * @return Reference to the Location. * @{ */ @@ -454,7 +475,29 @@ class Model } /** @} */ -private: + /** + * @brief Get index of person in m_persons. + * @param[in] person_id A person's unique PersonId. + * First 32 bit are the Person's individual id and second 32 bit the Persons's home model id. + * @return Index of Person in m_persons vector. + * @{ + */ + uint32_t get_person_index(PersonId person_id) const + { + mio::log_debug("get_person_index is used leading to a search in m_persons."); + auto it = std::find_if(m_persons.begin(), m_persons.end(), [person_id](auto& person) { + return person.get_id() == person_id; + }); + if (it == m_persons.end()) { + log_error("Given PersonId is not in this Model."); + return std::numeric_limits::max(); + } + else { + return static_cast(std::distance(m_persons.begin(), it)); + } + } + +protected: /** * @brief Person%s interact at their Location and may become infected. * @param[in] t The current TimePoint. @@ -481,6 +524,104 @@ class Model */ void compute_exposure_caches(TimePoint t, TimeSpan dt); + // Change the Location of a Person. this requires that Location is part of this Model. + /** + * @brief Let a Person change to another Location. + * @param[in] person Reference to Person. + * @param[in] destination LocationId of the Location in this Model, which the Person should change to. + * @param[in] mode The transport mode the person uses to change the Location. + * @param[in] cells The cells within the destination the person should be in. + */ + inline void change_location(Person& person, LocationId destination, TransportMode mode = TransportMode::Unknown, + const std::vector& cells = {0}) + { + LocationId origin = get_location(person).get_id(); + const bool has_changed_location = mio::abm::change_location(person, get_location(destination), mode, cells); + // if the person has changed location, invalidate exposure caches but keep population caches valid + if (has_changed_location) { + 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 Get a reference to the location of a person. + * @param[in] person Reference to a Person. + * @return Reference to the Location. + * @{ + */ + inline Location& get_location(Person& person) + { + return get_location(person.get_location()); + } + + inline const Location& get_location(Person& person) const + { + return get_location(person.get_location()); + } + + /** + * @brief Find an assigned Location of a Person. + * @param[in] type The #LocationType that specifies the assigned Location. + * @param[in] person Reference to Person. + * @return ID of the Location of LocationType type assigend to person. + */ + LocationId find_location(LocationType type, const Person& person) const + { + auto location_id = person.get_assigned_location(type); + assert(location_id != LocationId::invalid_id() && "The person has no assigned location of that type."); + return location_id; + } + + /** + * @brief Let a person interact with the population at its current location. + * @param[in] person Reference to Person. + * @param[in] t Time step of the simulation. + * @param[in] dt Step size of the simulation. + */ + inline void interact(Person& 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(person); + mio::abm::interact(personal_rng, person, get_location(person.get_location()), + m_air_exposure_rates_cache[person.get_location().get()], + m_contact_exposure_rates_cache[person.get_location().get()], t, dt, parameters); + } + + /** + * @brief Implementation of Model::get_person. + * This function needs to use a template to deduce whether the model and returned person should be const. + * @param[in] m A reference to `*this`, so we can access m_persons. + * @param[in] person_id A Person's PersonId. + * @return A reference to the Person with matching ID. + */ + template + static std::conditional_t, const Person&, Person&> get_person_impl(M& m, PersonId person_id) + { + if (m.m_person_ids_equal_index) { + assert(static_cast(person_id.get()) < m.m_persons.size() && + "Given PersonId is not in this Model."); + return m.m_persons[static_cast(person_id.get())]; + } + else { + mio::log_warning("get_person is accessed by PersonId which does not align with the index of the person due " + "to former removal of persons. Therefore m_persons is searched."); + auto it = std::find_if(m.m_persons.begin(), m.m_persons.end(), [person_id](auto& person) { + return person.get_id() == person_id; + }); + assert(it != m.m_persons.end() && "Given PersonId is not in this Model."); + return *it; + }; + } + mutable Eigen::Matrix m_local_population_cache; ///< Current number of Persons in a given location. Eigen::Matrix @@ -491,19 +632,20 @@ class Model bool m_are_exposure_caches_valid = false; bool m_exposure_caches_need_rebuild = true; + int m_id; ///< Model id. Is only used for abm graph model or hybrid model. std::vector m_persons; ///< Vector of every Person. std::vector m_locations; ///< Vector of every Location. + std::vector + m_activeness_statuses; ///< Vector with activeness status for every person. Is only used for abm graph model or hybrid model. 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_mobility_rules; ///< Whether mobility rules are considered. - std::vector>> - m_mobility_rules; ///< Rules that govern the mobility between Location%s. + std::vector m_mobility_rules; ///< Rules that govern the mobility between Location%s. LocationId m_cemetery_id; // Central cemetery for all dead persons. RandomNumberGenerator m_rng; ///< Global random number generator + bool m_person_ids_equal_index; }; } // namespace abm diff --git a/cpp/models/abm/model_functions.cpp b/cpp/models/abm/model_functions.cpp index dad13e9b15..68bcca267b 100644 --- a/cpp/models/abm/model_functions.cpp +++ b/cpp/models/abm/model_functions.cpp @@ -133,7 +133,7 @@ bool change_location(Person& person, const Location& destination, const Transpor })); // make sure cell indices are valid if (person.get_location() != destination.get_id()) { - person.set_location(destination.get_type(), destination.get_id()); + person.set_location(destination.get_type(), destination.get_id(), destination.get_model_id()); person.get_cells() = cells; person.set_last_transport_mode(mode); diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 691f36ac00..a0e450a64a 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -23,7 +23,9 @@ #include "abm/parameters.h" #include "abm/infection.h" #include "abm/location.h" +#include "abm/person_id.h" #include "memilio/utils/random_number_generator.h" +#include #include namespace mio @@ -31,20 +33,24 @@ namespace mio namespace abm { -Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, LocationId location_id, AgeGroup age, - PersonId person_id) +Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, LocationId location_id, + int location_model_id, AgeGroup age, PersonId person_id) : m_location(location_id) , m_location_type(location_type) + , m_location_model_id(location_model_id) , m_assigned_locations((uint32_t)LocationType::Count, LocationId::invalid_id()) , m_home_isolation_start(TimePoint(-(std::numeric_limits::max() / 2))) , m_age(age) , m_time_at_location(0) , m_mask(Mask(MaskType::None, TimePoint(-(std::numeric_limits::max() / 2)))) , m_compliance((uint32_t)InterventionType::Count, 1.) - , m_person_id(person_id) , m_cells{0} , m_last_transport_mode(TransportMode::Unknown) , m_test_results({TestType::Count}, TestResult()) + , m_assigned_location_model_ids((int)LocationType::Count) + , m_person_id(person_id) + , m_rng_key(rng.get_key()) + , m_rng_index(static_cast(person_id.get())) { m_random_workgroup = UniformDistribution::get_instance()(rng); m_random_schoolgroup = UniformDistribution::get_instance()(rng); @@ -52,10 +58,11 @@ Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, Loca m_random_goto_school_hour = UniformDistribution::get_instance()(rng); } -Person::Person(const Person& other, PersonId id) +Person::Person(const Person& other, PersonId person_id) : Person(other) { - m_person_id = id; + m_person_id = person_id; + m_rng_index = static_cast(person_id.get()); } bool Person::is_infected(TimePoint t) const @@ -91,11 +98,12 @@ LocationId Person::get_location() const return m_location; } -void Person::set_location(LocationType type, LocationId id) +void Person::set_location(LocationType type, LocationId id, int model_id) { - m_location = id; - m_location_type = type; - m_time_at_location = TimeSpan(0); + m_location = id; + m_location_type = type; + m_location_model_id = model_id; + m_time_at_location = TimeSpan(0); } const Infection& Person::get_infection() const @@ -108,9 +116,10 @@ Infection& Person::get_infection() return m_infections.back(); } -void Person::set_assigned_location(LocationType type, LocationId id) +void Person::set_assigned_location(LocationType type, LocationId id, int model_id = 0) { - m_assigned_locations[static_cast(type)] = id; + m_assigned_locations[static_cast(type)] = id; + m_assigned_location_model_ids[static_cast(type)] = model_id; } LocationId Person::get_assigned_location(LocationType type) const @@ -118,6 +127,11 @@ LocationId Person::get_assigned_location(LocationType type) const return m_assigned_locations[static_cast(type)]; } +int Person::get_assigned_location_model_id(LocationType type) const +{ + return m_assigned_location_model_ids[static_cast(type)]; +} + bool Person::goes_to_work(TimePoint t, const Parameters& params) const { return m_random_workgroup < params.get().get_matrix_at(t.days())[0]; @@ -214,14 +228,14 @@ bool Person::is_compliant(PersonalRandomNumberGenerator& rng, InterventionType i ProtectionEvent Person::get_latest_protection() const { ProtectionType latest_protection_type = ProtectionType::NoProtection; - TimePoint infection_time = TimePoint(0); + TimePoint infection_time = TimePoint(0); if (!m_infections.empty()) { latest_protection_type = ProtectionType::NaturalInfection; - infection_time = m_infections.back().get_start_date(); + infection_time = m_infections.back().get_start_date(); } if (!m_vaccinations.empty() && infection_time.days() <= m_vaccinations.back().time.days()) { latest_protection_type = m_vaccinations.back().type; - infection_time = m_vaccinations.back().time; + infection_time = m_vaccinations.back().time; } return ProtectionEvent{latest_protection_type, infection_time}; } @@ -240,7 +254,7 @@ ScalarType Person::get_protection_factor(TimePoint t, VirusVariant virus, const void Person::set_mask(MaskType type, TimePoint t) { m_mask.change_mask(type, t); -} +} void Person::add_test_result(TimePoint t, TestType type, bool result) { diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 7fc9cc27e6..8933e85775 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -37,14 +37,13 @@ #include "abm/mobility_data.h" #include "memilio/epidemiology/age_group.h" #include "memilio/utils/random_number_generator.h" +#include namespace mio { namespace abm { -static constexpr uint32_t INVALID_PERSON_ID = std::numeric_limits::max(); - /** * @brief Agents in the simulated Model that can carry and spread the Infection. */ @@ -56,12 +55,13 @@ class Person * @param[in, out] rng RandomNumberGenerator. * @param[in, out] location Initial Location of the Person. * @param[in] age The AgeGroup of the Person. - * @param[in] person_id Index of the Person. + * @param[in] person_index Index of the Person. + * */ - explicit Person(mio::RandomNumberGenerator& rng, LocationType location_type, LocationId location_id, AgeGroup age, - PersonId person_id = PersonId::invalid_id()); + explicit Person(mio::RandomNumberGenerator& rng, LocationType location_type, LocationId location_id, + int location_model_id, AgeGroup age, PersonId person_id = PersonId::invalid_ID()); - explicit Person(const Person& other, PersonId id); + explicit Person(const Person& other, PersonId person_id); /** * @brief Compare two Person%s. @@ -134,11 +134,18 @@ class Person return m_location_type; } + int get_location_model_id() const + { + return m_location_model_id; + } + /** * @brief Change the location of the person. - * @param[in] id The new location. + * @param[in] type The LocationType of the new Location. + * @param[in] id The LocationId of the new Location. + * @param[in] model_id The model id of the new Location. */ - void set_location(LocationType type, LocationId id); + void set_location(LocationType type, LocationId id, int model_id); /** * @brief Get the time the Person has been at its current Location. @@ -168,8 +175,9 @@ class Person * Location of a certain #LocationType. * @param[in] type The LocationType of the Location. * @param[in] id The LocationId of the Location. + * @param[in] model_id The model id of the Location. */ - void set_assigned_location(LocationType type, LocationId id); + void set_assigned_location(LocationType type, LocationId id, int model_id); /** * @brief Returns the index of an assigned Location of the Person. @@ -188,6 +196,23 @@ class Person return m_assigned_locations; } + /** + * @brief Returns the model id of an assigned location of the Person. + * Assume that a Person has at most one assigned Location of a certain #LocationType. + * @param[in] type #LocationType of the assigned Location. + * @return The model id of the assigned Location. + */ + int get_assigned_location_model_id(LocationType type) const; + + /** + * @brief Get the assigned locations' model ids of the Person. + * @return A vector with the model ids of the assigned locations of the Person + */ + const std::vector& get_assigned_location_model_ids() const + { + return m_assigned_location_model_ids; + } + /** * @brief Draw if the Person goes to work or is in home office during lockdown at a specific TimePoint. * Every Person has a random number. Depending on this number and the time, the Person works from home in case of a @@ -255,7 +280,6 @@ class Person /** * @brief Get the PersonId of the Person. - * The PersonId should correspond to the index in m_persons in the Model. * @return The PersonId. */ PersonId get_id() const; @@ -374,6 +398,24 @@ class Person return m_rng_counter; } + /** + * @brief Get this Person's index that is used for the RandomNumberGenerator. + * @see mio::abm::PersonalRandomNumberGenerator. + */ + uint32_t get_rng_index() + { + return m_rng_index; + } + + /** + * @brief Get this Person's key that is used for the RandomNumberGenerator. + * @see mio::abm::PersonalRandomNumberGenerator. + */ + mio::Key get_rng_key() + { + return m_rng_key; + } + /** * @brief Get the latest #ProtectionType and its initial TimePoint of the Person. */ @@ -397,11 +439,12 @@ class Person .add("rnd_go_to_school_hour", m_random_goto_school_hour) .add("mask", m_mask) .add("compliance", m_compliance) - .add("id", m_person_id) .add("cells", m_cells) .add("last_transport_mode", m_last_transport_mode) .add("rng_counter", m_rng_counter) - .add("test_results", m_test_results); + .add("test_results", m_test_results) + .add("id", m_person_id) + .add("rng_index", m_rng_index); } /** @@ -423,6 +466,7 @@ class Person private: LocationId m_location; ///< Current Location of the Person. LocationType m_location_type; ///< Type of the current Location. + int m_location_model_id; ///< Model id of the current Location. Only used for Graph ABM. 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 vaccinations the Person has received. @@ -437,11 +481,15 @@ class Person Mask m_mask; ///< The Mask of the Person. std::vector m_compliance; ///< Vector of compliance values for all #InterventionType%s. Values from 0 to 1. - 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. CustomIndexArray m_test_results; ///< CustomIndexArray for TestResults. + std::vector + m_assigned_location_model_ids; ///< Vector with model ids of the assigned locations. Only used in graph abm. + PersonId m_person_id; ///< Unique identifier of a person. + mio::Key m_rng_key; ///< Key for PersonalRandomNumberGenerator + uint32_t m_rng_index; ///< Index for PersonalRandomNumberGenerator. + Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator. }; } // namespace abm @@ -451,7 +499,7 @@ template <> struct DefaultFactory { static abm::Person create() { - return abm::Person(thread_local_rng(), abm::LocationType::Count, abm::LocationId(), AgeGroup(0), + return abm::Person(thread_local_rng(), abm::LocationType::Count, abm::LocationId(), 0, AgeGroup(0), abm::PersonId()); } }; diff --git a/cpp/models/abm/person_id.h b/cpp/models/abm/person_id.h index 6d64f7d3d1..f4ca7df13f 100644 --- a/cpp/models/abm/person_id.h +++ b/cpp/models/abm/person_id.h @@ -29,22 +29,22 @@ namespace mio namespace abm { -/// Unique identifier for a Person within a Model. -struct MEMILIO_ENABLE_EBO PersonId : public mio::TypeSafe, public OperatorComparison { +/// Unique ID for a Person within a Model. +struct MEMILIO_ENABLE_EBO PersonId : public mio::TypeSafe, public OperatorComparison { /// @brief Create an ID. - PersonId(uint32_t id) - : mio::TypeSafe(id) + PersonId(uint64_t id) + : mio::TypeSafe(id) { } /// @brief Create an invalid ID. PersonId() - : mio::TypeSafe(std::numeric_limits::max()) + : mio::TypeSafe(std::numeric_limits::max()) { } /// @brief Value for invalid IDs. - const static PersonId invalid_id() + const static PersonId invalid_ID() { return PersonId(); } diff --git a/cpp/models/abm/personal_rng.cpp b/cpp/models/abm/personal_rng.cpp index bbdb99160b..2a5938fe80 100644 --- a/cpp/models/abm/personal_rng.cpp +++ b/cpp/models/abm/personal_rng.cpp @@ -20,21 +20,23 @@ #include "abm/personal_rng.h" #include "abm/person.h" +#include "abm/person_id.h" +#include namespace mio { namespace abm { -PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(mio::Key key, PersonId id, +PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(mio::Key key, uint32_t index, mio::Counter& counter) : m_key(key) - , m_person_id(id) + , m_person_index(index) , m_counter(counter) { } -PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(const mio::RandomNumberGenerator& rng, Person& person) - : PersonalRandomNumberGenerator(rng.get_key(), person.get_id(), person.get_rng_counter()) +PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(Person& person) + : PersonalRandomNumberGenerator(person.get_rng_key(), person.get_rng_index(), person.get_rng_counter()) { } diff --git a/cpp/models/abm/personal_rng.h b/cpp/models/abm/personal_rng.h index 9d9c91a338..7a359bb10f 100644 --- a/cpp/models/abm/personal_rng.h +++ b/cpp/models/abm/personal_rng.h @@ -23,6 +23,7 @@ #include "memilio/utils/random_number_generator.h" #include "abm/person_id.h" +#include namespace mio { @@ -51,18 +52,16 @@ class PersonalRandomNumberGenerator : public mio::RandomNumberGeneratorBase key, PersonId id, mio::Counter& counter); + PersonalRandomNumberGenerator(mio::Key key, uint32_t index, 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); + PersonalRandomNumberGenerator(Person& person); /** * @return Get the key. @@ -77,7 +76,7 @@ class PersonalRandomNumberGenerator : public mio::RandomNumberGeneratorBase get_counter() const { - return mio::rng_totalsequence_counter(m_person_id.get(), m_counter); + return mio::rng_totalsequence_counter(m_person_index, m_counter); } /** @@ -90,7 +89,7 @@ class PersonalRandomNumberGenerator : public mio::RandomNumberGeneratorBase m_key; ///< Global RNG Key - PersonId m_person_id; ///< Id of the Person + uint32_t m_person_index; ///< Index of the Person mio::Counter& m_counter; ///< Reference to the Person's rng counter }; diff --git a/cpp/models/abm/simulation.cpp b/cpp/models/abm/simulation.cpp index 3c822c7ec0..e85cbc632a 100644 --- a/cpp/models/abm/simulation.cpp +++ b/cpp/models/abm/simulation.cpp @@ -24,19 +24,5 @@ namespace mio namespace abm { -Simulation::Simulation(TimePoint t, Model&& model) - : m_model(std::move(model)) - , m_t(t) - , m_dt(hours(1)) -{ -} - -void Simulation::evolve_model(TimePoint tmax) -{ - auto dt = std::min(m_dt, tmax - m_t); - m_model.evolve(m_t, dt); - m_t += m_dt; -} - } // namespace abm } // namespace mio diff --git a/cpp/models/abm/simulation.h b/cpp/models/abm/simulation.h index 415dccedbb..2f973cf092 100644 --- a/cpp/models/abm/simulation.h +++ b/cpp/models/abm/simulation.h @@ -32,6 +32,7 @@ namespace abm /** * @brief Run the Simulation in discrete steps, evolve the Model and report results. */ +template class Simulation { @@ -41,7 +42,12 @@ class Simulation * @param[in] t0 The starting time of the Simulation. * @param[in] model The Model to simulate. */ - Simulation(TimePoint t0, Model&& model); + Simulation(TimePoint t0, M&& model) + : m_model(std::move(model)) + , m_t(t0) + , m_dt(hours(1)) + { + } /** * @brief Create a Simulation with an empty Model. @@ -50,7 +56,7 @@ class Simulation * @param[in] t0 The starting time of the Simulation. */ Simulation(TimePoint t0, size_t num_agegroups) - : Simulation(t0, Model(num_agegroups)) + : Simulation(t0, M(num_agegroups)) { } @@ -81,20 +87,25 @@ class Simulation /** * @brief Get the Model that this Simulation evolves. */ - Model& get_model() + M& get_model() { return m_model; } - const Model& get_model() const + const M& get_model() const { return m_model; } private: void store_result_at(TimePoint t); - void evolve_model(TimePoint tmax); + void evolve_model(TimePoint tmax) + { + auto dt = std::min(m_dt, tmax - m_t); + m_model.evolve(m_t, dt); + m_t += m_dt; + } - Model m_model; ///< The Model to simulate. + M m_model; ///< The Model to simulate. TimePoint m_t; ///< The current TimePoint of the Simulation. TimeSpan m_dt; ///< The length of the time steps. }; diff --git a/cpp/models/abm/trip_list.h b/cpp/models/abm/trip_list.h index 654940b208..a95eafeb1d 100644 --- a/cpp/models/abm/trip_list.h +++ b/cpp/models/abm/trip_list.h @@ -22,9 +22,13 @@ #include "abm/location_id.h" #include "abm/mobility_data.h" +#include "abm/person.h" #include "abm/person_id.h" #include "abm/time.h" +#include "abm/location_type.h" +#include "memilio/io/io.h" #include "memilio/io/default_serialize.h" +#include #include namespace mio @@ -36,48 +40,67 @@ namespace abm * @brief A trip describes a change of Location from one Location to another Location. */ struct Trip { + //TODO: Origin is currently not used for the trips. Should we delete it then? PersonId person_id; /**< Person that makes the trip and corresponds to the index into the structure m_persons from Model, where all Person%s are saved.*/ - TimePoint time; ///< Time at which a Person changes the Location. + TimePoint time; ///< Daytime at which a Person changes the Location. LocationId destination; ///< Location where the Person changes to. + int destination_model_id; ///< Model id of destination Location. LocationId origin; ///< Location where the Person starts the Trip. + int origin_model_id; ///< Model id of origin Location. std::vector cells; /**< If destination consists of different Cell%s, this gives the index of the Cell%s the Person changes to.*/ TransportMode trip_mode; ///< Mode of transportation. 1:Bike, 2:Car (Driver), 3:Car (Co-Driver)), 4:Public Transport, 5:Walking, 6:Other/Unknown - ActivityType - activity_type; ///< Type of activity. 1:Workplace, 2:Education, 3:Shopping, 4:Leisure, 5:Private Matters, 6:Other Activity, 7:Home, 8:Unknown Activity + LocationType destination_type; ///< Type of destination Location. /** * @brief Construct a new Trip. * @param[in] id ID of the Person that makes the Trip. * @param[in] time_new Time at which a Person changes the Location this currently cant be set for s specific day just a timepoint in a day. * @param[in] destination Location where the Person changes to. + * @param[in] destination_model_id Model the Person changes to. * @param[in] origin Location where the person starts the Trip. + * @param[in] origin_model_id Model the Person starts the Trip. * @param[in] input_cells The index of the Cell%s the Person changes to. */ + Trip(PersonId id, TimePoint time_new, LocationId dest, int dest_model_id, LocationId orig, int orig_model_id, + TransportMode mode_of_transport, LocationType type_of_activity, const std::vector& input_cells = {}) + : person_id(id) + , time(mio::abm::TimePoint(time_new.time_since_midnight().seconds())) + , destination(dest) + , destination_model_id(dest_model_id) + , origin(orig) + , origin_model_id(orig_model_id) + , cells(input_cells) + , trip_mode(mode_of_transport) + , destination_type(type_of_activity) + { + } + Trip(PersonId id, TimePoint time_new, LocationId dest, LocationId orig, TransportMode mode_of_transport, - ActivityType type_of_activity, const std::vector& input_cells = {}) + LocationType type_of_activity, const std::vector& input_cells = {}) : person_id(id) , time(mio::abm::TimePoint(time_new.time_since_midnight().seconds())) , destination(dest) + , destination_model_id(0) , origin(orig) + , origin_model_id(0) , cells(input_cells) , trip_mode(mode_of_transport) - , activity_type(type_of_activity) + , destination_type(type_of_activity) { } - Trip(PersonId id, TimePoint time_new, LocationId dest, const std::vector& input_cells = {}) - : Trip(id, time_new, dest, dest, mio::abm::TransportMode::Unknown, mio::abm::ActivityType::UnknownActivity, - input_cells) + Trip(PersonId id, TimePoint time_new, LocationId dest, LocationId orig, LocationType type_of_activity, + const std::vector& input_cells = {}) + : Trip(id, time_new, dest, orig, mio::abm::TransportMode::Unknown, type_of_activity, input_cells) { } - Trip(PersonId id, TimePoint time_new, LocationId dest, LocationId orig, + Trip(PersonId id, TimePoint time_new, LocationId dest, LocationType type_of_activity, const std::vector& input_cells = {}) - : Trip(id, time_new, dest, orig, mio::abm::TransportMode::Unknown, mio::abm::ActivityType::UnknownActivity, - input_cells) + : Trip(id, time_new, dest, dest, mio::abm::TransportMode::Unknown, type_of_activity, input_cells) { } @@ -190,7 +213,7 @@ template <> struct DefaultFactory { static abm::Trip create() { - return abm::Trip{abm::PersonId{}, abm::TimePoint{}, abm::LocationId{}}; + return abm::Trip{abm::PersonId{}, abm::TimePoint{}, abm::LocationId{}, abm::LocationType{}}; } }; diff --git a/cpp/models/graph_abm/CMakeLists.txt b/cpp/models/graph_abm/CMakeLists.txt new file mode 100644 index 0000000000..982b3512c3 --- /dev/null +++ b/cpp/models/graph_abm/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(graph_abm + graph_abmodel.h + graph_abmodel.cpp + graph_abm_mobility.cpp + graph_abm_mobility.h +) + +target_link_libraries(graph_abm PUBLIC memilio) +target_include_directories(graph_abm PUBLIC + $ + $ +) + +target_compile_options(graph_abm PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/models/graph_abm/graph_abm_mobility.cpp b/cpp/models/graph_abm/graph_abm_mobility.cpp new file mode 100644 index 0000000000..af3fae0c89 --- /dev/null +++ b/cpp/models/graph_abm/graph_abm_mobility.cpp @@ -0,0 +1,31 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Julia Bicker +* +* 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 "graph_abm/graph_abm_mobility.h" +#include "abm/simulation.h" +#include "abm/model.h" +#include "abm/person.h" +#include "abm/location_type.h" +#include "abm/parameters.h" + +namespace mio +{ + +} // namespace mio \ No newline at end of file diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h new file mode 100644 index 0000000000..686db87994 --- /dev/null +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -0,0 +1,200 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Julia Bicker +* +* 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_GRAPH_MOBILITY_H +#define MIO_ABM_GRAPH_MOBILITY_H + +#include "abm/simulation.h" +#include "abm/time.h" +#include "abm/location_type.h" +#include "abm/parameters.h" +#include "abm/person.h" +#include "abm/person_id.h" +#include "abm/model_functions.h" +#include "graph_abm/graph_abmodel.h" +#include "memilio/mobility/graph_simulation.h" +#include "memilio/mobility/graph.h" +#include "memilio/utils/compiler_diagnostics.h" +#include +#include +#include +#include +#include + +namespace mio +{ +/** +* @brief Represents the ABM simulation in one node of the ABM graph model. +*/ +template +class ABMSimulationNode +{ + +public: + using Sim = abm::Simulation; + + template ::value, void>> + ABMSimulationNode(std::tuple&& history, Args&&... args) + : m_simulation(std::forward(args)...) + , m_history(history) + { + } + + /** + *@brief Get abm simulation in this node. + */ + Sim& get_simulation() + { + return m_simulation; + } + const Sim& get_simulation() const + { + return m_simulation; + } + + /** + * @brief Get history object(s) in this node. + */ + std::tuple& get_history() + { + return m_history; + } + const std::tuple& get_history() const + { + return m_history; + } + + /** + * @brief advances the simulation in this node by t+dt and logs information in History object(s) + * @tparam History history object type(s) + * @param[in] t Current time point + * @param[in] dt Time span that shoulb be advanced + * @param[in, out] history History object(s) storing simulation information + */ + void advance(mio::abm::TimePoint t, mio::abm::TimeSpan dt) + { + m_simulation.advance(t + dt, std::get<0>(m_history)); + } + +private: + Sim m_simulation; ///< ABM Simulation of the node + std::tuple m_history; +}; + +/** + * Represents the mobility between two nodes. + */ +template +class ABMMobilityEdge +{ + +public: + /** + * @brief Exchanges persons via the edge. + * Commuters are given by the person buffer of node_from. + * @param[in] node_from Commuters home node + * @param[in] node_to Node commuters (temporarily) move to + * @param[in] t Echange time point + */ + void apply_mobility(ABMSimulationNode& node_from, ABMSimulationNode& node_to, + abm::TimePoint /*t*/) + { + auto& model_from = node_from.get_simulation().get_model(); + auto& model_to = node_to.get_simulation().get_model(); + auto& persons_to_change = model_from.get_person_buffer(); + //sort vector such that we start removing the persons from the bottom + std::sort(persons_to_change.begin(), persons_to_change.end()); + //iterate over all persons that change from node_from + for (int i = int(persons_to_change.size()) - 1; i >= 0; --i) { + auto& person = model_from.get_persons()[persons_to_change[i]]; + auto target_type = person.get_location_type(); + //check if Person uses this edge + if (person.get_assigned_location_model_id(target_type) == model_to.get_id()) { + auto target_id = person.get_assigned_location(target_type); + //set correct location for person + person.set_location(target_type, target_id, model_to.get_id()); + //add person to model_to + model_to.add_person(std::move(person)); + //remove person from model_from + model_from.remove_person(persons_to_change[i]); + //correct indices in persons buffer from node_from + //here it is required that the vector is sorted + for (size_t j = i + 1; j < persons_to_change.size(); ++j) { + persons_to_change[j]--; + } + //delete current index from list + persons_to_change.erase(persons_to_change.begin() + i); + } + } + } +}; + +/** + * @brief Edge functor for abm graph simulation. + * @see ABMMobilityEdge::apply_mobility + * The attribute dt is required by the GraphSimulation class and therefore an input argument of the function. + * However it is not used in ABMMobilityEdge::apply_mobility. + * @param[in] t Time point the functor is applied. + * @param[in] edge ABMMobilityEdge for which the functor is applied. + * @param[in] node_from Edge start node. + * @param[in] node_to Edge end node. + */ +template +void apply_mobility(abm::TimePoint t, abm::TimeSpan /*dt*/, ABMMobilityEdge& edge, + ABMSimulationNode& node_from, ABMSimulationNode& node_to) +{ + edge.apply_mobility(node_from, node_to, t); +} + +/** + * @brief Node functor for abm graph simulation. + * @see ABMSimulationNode::advance + * @param[in] t Time point the functor is applied. + * @param[in] dt Time interval the node is advanced. + * @param[in] node ABMSimulationNode to which the functor is applied. + */ +template +void advance_model(abm::TimePoint t, abm::TimeSpan dt, ABMSimulationNode& node) +{ + node.advance(t, dt); +} + +/** + * @brief Creates an abm graph simulation. + * Every dt time step for each edge the persons that want to change to a location in another node + * are removed from the model in their former location's node and added to the model of the new location. + * @param[in] t0 Start time point of the simulation. + * @param[in] dt Step between mobility on edges. + * @param[in] graph Graph for simulation. + */ +template +GraphSimulation, ABMMobilityEdge>, abm::TimePoint, abm::TimeSpan, + void (*)(mio::abm::TimePoint, mio::abm::TimeSpan, mio::ABMMobilityEdge&, + mio::ABMSimulationNode&, mio::ABMSimulationNode&), + void (*)(mio::abm::TimePoint, mio::abm::TimeSpan, mio::ABMSimulationNode&)> +make_abm_graph_sim(abm::TimePoint t0, abm::TimeSpan dt, + Graph, ABMMobilityEdge>&& graph) +{ + return make_graph_sim(t0, dt, std::move(graph), &advance_model, &apply_mobility); +} + +} // namespace mio + +#endif // MIO_ABM_GRAPH_MOBILITY_H diff --git a/cpp/models/graph_abm/graph_abmodel.cpp b/cpp/models/graph_abm/graph_abmodel.cpp new file mode 100644 index 0000000000..1f610d94c4 --- /dev/null +++ b/cpp/models/graph_abm/graph_abmodel.cpp @@ -0,0 +1,26 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Julia Bicker +* +* 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 "graph_abm/graph_abmodel.h" + +namespace mio +{ + +} //namespace mio diff --git a/cpp/models/graph_abm/graph_abmodel.h b/cpp/models/graph_abm/graph_abmodel.h new file mode 100644 index 0000000000..05490bcbe6 --- /dev/null +++ b/cpp/models/graph_abm/graph_abmodel.h @@ -0,0 +1,211 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Julia Bicker +* +* 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_GRAPH_ABMODEL_H +#define MIO_ABM_GRAPH_ABMODEL_H + +#include "abm/location_type.h" +#include "abm/model.h" +#include "abm/person_id.h" +#include "abm/time.h" +#include "abm/location_id.h" +#include "memilio/utils/compiler_diagnostics.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/mioomp.h" +#include "abm/mobility_rules.h" +#include "abm/mobility_rules.h" +#include +#include +#include +#include + +namespace mio +{ +using namespace abm; +class GraphABModel : public abm::Model +{ + using Base = Model; + +public: + GraphABModel(size_t num_agegroups, int id, + std::vector mobility_rules = + std::vector{&get_buried, &return_home_when_recovered, &go_to_hospital, + &go_to_icu, &go_to_school, &go_to_work, &go_to_shop, + &go_to_event, &go_to_quarantine}) + : Base(num_agegroups, id) + { + Base::m_mobility_rules = mobility_rules; + } + + /** + * @brief Get person buffer. + */ + std::vector& get_person_buffer() + { + return m_person_buffer; + } + + /** + * @brief Removes person from the model. + * @param[in] pos Index of person in m_persons vector. + */ + void remove_person(size_t pos) + { + Base::m_persons.erase(Base::m_persons.begin() + pos); + Base::m_activeness_statuses.erase(Base::m_activeness_statuses.begin() + pos); + Base::m_person_ids_equal_index = false; + } + + /** + * @brief Evolve the Graph Model one time step. + * @param[in] t Current time. + * @param[in] dt Length of the time step. + */ + void evolve(TimePoint t, TimeSpan dt) + { + Base::begin_step(t, dt); + log_info("Graph ABM Model interaction."); + Base::interaction(t, dt); + log_info("Graph ABM Model mobility."); + perform_mobility(t, dt); + } + +private: + void perform_mobility(TimePoint t, TimeSpan dt) + { + const uint32_t num_persons = static_cast(Base::m_persons.size()); + for (uint32_t person_index = 0; person_index < num_persons; ++person_index) { + if (Base::m_activeness_statuses[person_index]) { + Person& person = Base::m_persons[person_index]; + auto personal_rng = PersonalRandomNumberGenerator(person); + + auto try_mobility_rule = [&](auto rule) -> bool { + //run mobility rule and check if change of location can actually happen + auto target_type = rule(personal_rng, person, t, dt, parameters); + if (person.get_assigned_location_model_id(target_type) == Base::m_id) { + const Location& target_location = Base::get_location(Base::find_location(target_type, person)); + const LocationId current_location = person.get_location(); + // the Person cannot move if they do not wear mask as required at targeted location + if (target_location.is_mask_required() && + !person.is_compliant(personal_rng, InterventionType::Mask)) { + return false; + } + // the Person cannot move if the capacity of targeted Location is reached + if (target_location.get_id() == current_location || + get_number_persons(target_location.get_id()) >= target_location.get_capacity().persons) { + return false; + } + // the Person cannot move if the performed TestingStrategy is positive + if (!m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + return false; + } + // update worn mask to target location's requirements + if (target_location.is_mask_required()) { + // if the current MaskProtection level is lower than required, the Person changes mask + if (parameters.get()[person.get_mask().get_type()] < + parameters.get()[target_location.get_required_mask()]) { + person.set_mask(target_location.get_required_mask(), t); + } + } + else { + person.set_mask(MaskType::None, t); + } + Base::change_location(person, target_location.get_id()); + return true; + } + else { //person moves to other world + Base::m_activeness_statuses[person_index] = false; + person.set_location(target_type, abm::LocationId::invalid_id(), + std::numeric_limits::max()); + m_person_buffer.push_back(person_index); + m_are_exposure_caches_valid = false; + m_is_local_population_cache_valid = false; + return true; + } + }; + + for (auto rule : Base::m_mobility_rules) { + bool applied = try_mobility_rule(rule); + //only use one mobility rule per person + if (applied) { + break; + } + } + } + } + + // check if a person makes a trip + bool weekend = t.is_weekend(); + size_t num_trips = Base::m_trip_list.num_trips(weekend); + + for (; Base::m_trip_list.get_current_index() < num_trips && + Base::m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds(); + Base::m_trip_list.increase_index()) { + auto& trip = Base::m_trip_list.get_next_trip(weekend); + auto& person = get_person(trip.person_id); + auto person_index = Base::get_person_index(trip.person_id); + auto personal_rng = PersonalRandomNumberGenerator(person); + // skip the trip if the person is in quarantine or is dead + if (person.is_in_quarantine(t, parameters) || person.get_infection_state(t) == InfectionState::Dead) { + continue; + } + if (trip.destination_model_id == Base::m_id) { + auto& target_location = get_location(trip.destination); + // skip the trip if the Person wears mask as required at targeted location + if (target_location.is_mask_required() && !person.is_compliant(personal_rng, InterventionType::Mask)) { + continue; + } + // skip the trip if the performed TestingStrategy is positive + if (!Base::m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + continue; + } + // all requirements are met, move to target location + change_location(person, target_location.get_id(), trip.trip_mode); + // update worn mask to target location's requirements + if (target_location.is_mask_required()) { + // if the current MaskProtection level is lower than required, the Person changes mask + if (parameters.get()[person.get_mask().get_type()] < + parameters.get()[target_location.get_required_mask()]) { + person.set_mask(target_location.get_required_mask(), t); + } + } + else { + person.set_mask(MaskType::None, t); + } + } + else { //person moves to other world + Base::m_activeness_statuses[person_index] = false; + person.set_location(trip.destination_type, abm::LocationId::invalid_id(), + std::numeric_limits::max()); + m_person_buffer.push_back(person_index); + m_are_exposure_caches_valid = false; + m_is_local_population_cache_valid = false; + } + } + if (((t).days() < std::floor((t + dt).days()))) { + Base::m_trip_list.reset_index(); + } + } + + std::vector m_person_buffer; ///< List with indices of persons that are subject to move to another node. +}; +} // namespace mio + +#endif //MIO_ABM_GRAPH_ABMODEL_H diff --git a/cpp/simulations/abm.cpp b/cpp/simulations/abm.cpp index 8bfee8db00..655a078892 100644 --- a/cpp/simulations/abm.cpp +++ b/cpp/simulations/abm.cpp @@ -448,7 +448,7 @@ void assign_infection_state(mio::abm::Model& model, mio::abm::TimePoint t, doubl { auto persons = model.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(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 5853c220f0..f97da97de8 100644 --- a/cpp/simulations/abm_braunschweig.cpp +++ b/cpp/simulations/abm_braunschweig.cpp @@ -22,6 +22,7 @@ #include "abm/lockdown_rules.h" #include "abm/parameters.h" #include "abm/person.h" +#include "abm/person_id.h" #include "abm/simulation.h" #include "abm/model.h" #include "memilio/epidemiology/age_group.h" @@ -31,6 +32,7 @@ #include "boost/algorithm/string/split.hpp" #include "boost/algorithm/string/classification.hpp" +#include #include #include #include @@ -87,7 +89,7 @@ void assign_infection_state(mio::abm::Model& model, mio::abm::TimePoint t, doubl { auto persons = model.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); + auto rng = mio::abm::PersonalRandomNumberGenerator(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) @@ -220,11 +222,11 @@ void create_model_from_data(mio::abm::Model& model, const std::string& filename, count_of_titles++; } - std::map locations = {}; - std::map pids_data_to_model = {}; - std::map person_ids = {}; - std::map> locations_before; - std::map> locations_after; + std::map locations = {}; + std::map pids_data_to_model = {}; + std::map person_ids = {}; + std::map> locations_before; + std::map> locations_after; // For the model we need: Hospitals, ICUs (for both we just create one for now), Homes for each unique householdID, One Person for each person_id with respective age and home_id. @@ -343,7 +345,7 @@ void create_model_from_data(mio::abm::Model& model, const std::string& filename, split_line(line, &row); line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); - uint32_t person_data_id = row[index["puid"]]; + uint64_t person_data_id = row[index["puid"]]; if (person_ids.find(person_data_id) == person_ids.end()) break; @@ -383,9 +385,10 @@ void create_model_from_data(mio::abm::Model& model, const std::string& filename, // For trips where the start location is not known use Home instead start_location = model.get_person(pid_itr->second).get_assigned_location(mio::abm::LocationType::Home); } - model.get_trip_list().add_trip(mio::abm::Trip( - 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))); + model.get_trip_list().add_trip( + mio::abm::Trip(static_cast(pid_itr->first), + mio::abm::TimePoint(0) + mio::abm::minutes(trip_start), target_location, start_location, + mio::abm::TransportMode(transport_mode), mio::abm::LocationType(acticity_end))); } model.get_trip_list().use_weekday_trips_on_weekend(); } @@ -843,8 +846,8 @@ void set_parameters(mio::abm::Parameters params) * Create a sampled simulation with start time t0. * @param t0 The start time of the Simulation. */ -mio::abm::Simulation create_sampled_simulation(const std::string& input_file, const mio::abm::TimePoint& t0, - int max_num_persons) +mio::abm::Simulation<> create_sampled_simulation(const std::string& input_file, const mio::abm::TimePoint& t0, + int max_num_persons) { // Assumed percentage of infection state at the beginning of the simulation. ScalarType exposed_prob = 0.005, infected_no_symptoms_prob = 0.001, infected_symptoms_prob = 0.001, diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index c2872eb166..206eba3da8 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -80,6 +80,7 @@ set(TESTSOURCES random_number_test.h sanitizers.cpp temp_file_register.h + test_graph_abm.cpp ) if(MEMILIO_HAS_JSONCPP) @@ -100,7 +101,7 @@ endif() add_executable(memilio-test ${TESTSOURCES}) target_include_directories(memilio-test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(memilio-test PRIVATE memilio ode_secir ode_seir ode_secirvvs ode_secirts ode_seair ide_seir ide_secir lct_secir glct_secir abm gtest_main AD::AD) +target_link_libraries(memilio-test PRIVATE memilio ode_secir ode_seir ode_secirvvs ode_secirts ode_seair ide_seir ide_secir lct_secir glct_secir abm gtest_main AD::AD graph_abm) target_compile_options(memilio-test PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) # make unit tests find the test data files diff --git a/cpp/tests/abm_helpers.cpp b/cpp/tests/abm_helpers.cpp index c9a3a45bed..8f70ba384d 100644 --- a/cpp/tests/abm_helpers.cpp +++ b/cpp/tests/abm_helpers.cpp @@ -19,16 +19,17 @@ */ #include "abm_helpers.h" #include "abm/person.h" +#include "abm/person_id.h" #include "memilio/utils/random_number_generator.h" mio::abm::Person make_test_person(mio::RandomNumberGenerator& rng, mio::abm::Location& location, mio::AgeGroup age, mio::abm::InfectionState infection_state, mio::abm::TimePoint t, - mio::abm::Parameters params) + mio::abm::Parameters params, mio::abm::PersonId id) { assert(age.get() < params.get_num_groups()); - mio::abm::Person p(rng, location.get_type(), location.get_id(), age); + mio::abm::Person p(rng, location.get_type(), location.get_id(), location.get_model_id(), age, id); if (infection_state != mio::abm::InfectionState::Susceptible) { - auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(p); p.add_new_infection( mio::abm::Infection(rng_p, static_cast(0), age, params, t, infection_state)); } @@ -38,8 +39,8 @@ mio::abm::Person make_test_person(mio::RandomNumberGenerator& rng, mio::abm::Loc mio::abm::PersonId add_test_person(mio::abm::Model& model, mio::abm::LocationId loc_id, mio::AgeGroup age, mio::abm::InfectionState infection_state, mio::abm::TimePoint t) { - return model.add_person( - make_test_person(model.get_rng(), model.get_location(loc_id), age, infection_state, t, model.parameters)); + return model.add_person(make_test_person(model.get_rng(), model.get_location(loc_id), age, infection_state, t, + model.parameters, static_cast(model.get_persons().size()))); } void interact_testing(mio::abm::PersonalRandomNumberGenerator& personal_rng, mio::abm::Person& person, diff --git a/cpp/tests/abm_helpers.h b/cpp/tests/abm_helpers.h index 8567853dae..d3c9de20ba 100644 --- a/cpp/tests/abm_helpers.h +++ b/cpp/tests/abm_helpers.h @@ -21,6 +21,7 @@ #define ABM_HELPERS_H #include "abm/model.h" +#include "abm/person_id.h" #include "gmock/gmock.h" @@ -92,10 +93,12 @@ struct ScopedMockDistribution { /** * @brief Create a Person without a Model object. Intended for simple use in tests. */ -mio::abm::Person make_test_person(mio::RandomNumberGenerator& rng, mio::abm::Location& location, mio::AgeGroup age = age_group_15_to_34, +mio::abm::Person make_test_person(mio::RandomNumberGenerator& rng, 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), - mio::abm::Parameters params = mio::abm::Parameters(num_age_groups)); + mio::abm::Parameters params = mio::abm::Parameters(num_age_groups), + mio::abm::PersonId id = mio::abm::PersonId(0)); /** * @brief Add a Person to the Model. Intended for simple use in tests. diff --git a/cpp/tests/test_abm_infection.cpp b/cpp/tests/test_abm_infection.cpp index 1930bf8108..1325457eb1 100644 --- a/cpp/tests/test_abm_infection.cpp +++ b/cpp/tests/test_abm_infection.cpp @@ -20,6 +20,7 @@ #include "abm/location_type.h" #include "abm/person.h" +#include "abm/person_id.h" #include "abm_helpers.h" #include "random_number_test.h" @@ -38,7 +39,7 @@ TEST_F(TestInfection, init) //set up a personal RNG for infections //uses uniformdistribution but result doesn't matter, so init before the mock auto counter = mio::Counter(0); - auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); + auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), 0, counter); ScopedMockDistribution>>> mock_uniform_dist; EXPECT_CALL(mock_uniform_dist.get_mock(), invoke) @@ -112,14 +113,14 @@ TEST_F(TestInfection, init) TEST_F(TestInfection, getInfectionState) { auto counter = mio::Counter(0); - auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); - auto params = mio::abm::Parameters(num_age_groups); - auto t = mio::abm::TimePoint(0); + auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), 0, counter); + auto params = mio::abm::Parameters(num_age_groups); + auto t = mio::abm::TimePoint(0); // Initialize infection in Exposed state auto infection = mio::abm::Infection(prng, mio::abm::VirusVariant::Wildtype, age_group_15_to_34, params, t, mio::abm::InfectionState::Exposed, - {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); + {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); // Test infection state at different time points EXPECT_EQ(infection.get_infection_state(t), mio::abm::InfectionState::Exposed); @@ -132,9 +133,9 @@ TEST_F(TestInfection, getInfectionState) TEST_F(TestInfection, drawInfectionCourseForward) { auto counter = mio::Counter(0); - auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); - auto params = mio::abm::Parameters(num_age_groups); - auto t = mio::abm::TimePoint(0); + auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), 0, counter); + auto params = mio::abm::Parameters(num_age_groups); + auto t = mio::abm::TimePoint(0); // Mock recovery transition params.get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 1; @@ -144,7 +145,7 @@ TEST_F(TestInfection, drawInfectionCourseForward) .WillRepeatedly(testing::Return(0.8)); // Sixth draw: Recovered draw in drawInfectionCourseForward auto infection1 = mio::abm::Infection(prng, mio::abm::VirusVariant::Wildtype, age_group_15_to_34, params, t, mio::abm::InfectionState::InfectedCritical, - {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); + {mio::abm::ProtectionType::NoProtection, mio::abm::TimePoint(0)}, true); // Test state transitions from Critical to Recovered EXPECT_EQ(infection1.get_infection_state(t), mio::abm::InfectionState::InfectedCritical); EXPECT_EQ(infection1.get_infection_state(t + mio::abm::days(1)), mio::abm::InfectionState::Recovered); @@ -166,10 +167,10 @@ TEST_F(TestInfection, drawInfectionCourseForward) */ TEST_F(TestInfection, drawInfectionCourseBackward) { - auto counter = mio::Counter(0); - auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), mio::abm::PersonId(0), counter); - auto t = mio::abm::TimePoint(1); - auto dt = mio::abm::days(1); + auto counter = mio::Counter(0); + auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng().get_key(), 0, counter); + auto t = mio::abm::TimePoint(1); + auto dt = mio::abm::days(1); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Time to go from all infected states to recover is 1 day (dt). @@ -224,7 +225,8 @@ TEST_F(TestInfection, getPersonalProtectiveFactor) const ScalarType eps = 1e-4; auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), age_group_15_to_34); + auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), location.get_model_id(), + age_group_15_to_34); person.add_new_vaccination(mio::abm::ProtectionType::GenericVaccine, mio::abm::TimePoint(0)); auto latest_protection = person.get_latest_protection(); @@ -260,42 +262,42 @@ TEST_F(TestInfection, getPersonalProtectiveFactor) // Test Parameter InfectionProtectionFactor and get_protection_factor() t = mio::abm::TimePoint(0) + mio::abm::days(2); auto infection_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(infection_protection_factor, 0.91, eps); EXPECT_NEAR(person.get_protection_factor(t, mio::abm::VirusVariant::Wildtype, params), 0.91, eps); t = mio::abm::TimePoint(0) + mio::abm::days(15); infection_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(infection_protection_factor, 0.8635, eps); EXPECT_NEAR(person.get_protection_factor(t, mio::abm::VirusVariant::Wildtype, params), 0.8635, eps); t = mio::abm::TimePoint(0) + mio::abm::days(40); infection_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(infection_protection_factor, 0.81, eps); EXPECT_NEAR(person.get_protection_factor(t, mio::abm::VirusVariant::Wildtype, params), 0.81, eps); // Test Parameter SeverityProtectionFactor t = mio::abm::TimePoint(0) + mio::abm::days(2); auto severity_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(severity_protection_factor, 0.91, eps); t = mio::abm::TimePoint(0) + mio::abm::days(15); severity_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(severity_protection_factor, 0.8635, eps); t = mio::abm::TimePoint(0) + mio::abm::days(40); severity_protection_factor = params.get()[{ - latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}]( - t.days() - latest_protection.time.days()); + latest_protection.type, age_group_15_to_34, mio::abm::VirusVariant::Wildtype}](t.days() - + latest_protection.time.days()); EXPECT_NEAR(severity_protection_factor, 0.81, eps); // Test Parameter HighViralLoadProtectionFactor diff --git a/cpp/tests/test_abm_location.cpp b/cpp/tests/test_abm_location.cpp index a46eba094e..7b6fe62cfa 100644 --- a/cpp/tests/test_abm_location.cpp +++ b/cpp/tests/test_abm_location.cpp @@ -32,7 +32,7 @@ using TestLocation = RandomNumberTest; TEST_F(TestLocation, initCell) { // Create a location of type PublicTransport with 2 cells. - mio::abm::Location location(mio::abm::LocationType::PublicTransport, 0, 6, 2); + mio::abm::Location location(mio::abm::LocationType::PublicTransport, 0, 6, 0, 2); // Verify that the number of cells created is as expected (2). EXPECT_EQ(location.get_cells().size(), 2); } @@ -56,7 +56,7 @@ TEST_F(TestLocation, computeSpacePerPersonRelative) using testing::Return; // Create a location of type Home with 3 cells. - mio::abm::Location home(mio::abm::LocationType::Home, 0, 6, 3); + mio::abm::Location home(mio::abm::LocationType::Home, 0, 6, 0, 3); home.set_capacity(4, 264, 0); // Capacity for Cell 1 home.set_capacity(2, 132, 1); // Capacity for Cell 2 home.set_capacity(0, 0, 2); // Capacity for Cell 3 @@ -112,7 +112,7 @@ TEST_F(TestLocation, interact) auto susceptible = make_test_person(this->get_rng(), location, age, mio::abm::InfectionState::Susceptible, t, params); EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(Return(0.5)); // Probability of no infection - auto person_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susceptible); + auto person_rng = mio::abm::PersonalRandomNumberGenerator(susceptible); interact_testing(person_rng, susceptible, location, local_population, t, dt, params); EXPECT_EQ(susceptible.get_infection_state(t + dt), mio::abm::InfectionState::Susceptible); diff --git a/cpp/tests/test_abm_lockdown_rules.cpp b/cpp/tests/test_abm_lockdown_rules.cpp index e1de296984..13d08aca01 100644 --- a/cpp/tests/test_abm_lockdown_rules.cpp +++ b/cpp/tests/test_abm_lockdown_rules.cpp @@ -52,12 +52,12 @@ TEST_F(TestLockdownRules, school_closure) .WillRepeatedly(testing::Return(1.0)); // Set up two people with assigned locations (home and school) - auto p1 = mio::abm::Person(this->get_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(this->get_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()); + auto p1 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + p1.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p1.set_assigned_location(school.get_type(), school.get_id(), school.get_model_id()); + auto p2 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + p2.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p2.set_assigned_location(school.get_type(), school.get_id(), school.get_model_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; @@ -71,9 +71,9 @@ TEST_F(TestLockdownRules, school_closure) mio::abm::set_school_closure(t, 0.7, params); // Test that p1 stays home and p2 goes to school - auto p1_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(p1); EXPECT_EQ(mio::abm::go_to_school(p1_rng, p1, t_morning, dt, params), mio::abm::LocationType::Home); - auto p2_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(p2); EXPECT_EQ(mio::abm::go_to_school(p2_rng, p2, t_morning, dt, params), mio::abm::LocationType::School); } @@ -101,9 +101,9 @@ TEST_F(TestLockdownRules, school_opening) .WillRepeatedly(testing::Return(1.0)); // Set up one person with assigned locations (home and school) - auto p = mio::abm::Person(this->get_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()); + auto p = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p.set_assigned_location(school.get_type(), school.get_id(), school.get_model_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) @@ -114,12 +114,12 @@ TEST_F(TestLockdownRules, school_opening) params.get()[age_group_15_to_34] = true; params.get()[age_group_35_to_59] = true; - // Apply school closure, then reopening + // Apply school closure, then reopening mio::abm::set_school_closure(t_closing, 1., params); mio::abm::set_school_closure(t_opening, 0., params); // Test that after reopening, the person goes to school - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); EXPECT_EQ(mio::abm::go_to_school(p_rng, p, t_morning, dt, params), mio::abm::LocationType::School); } @@ -157,17 +157,19 @@ TEST_F(TestLockdownRules, home_office) .WillOnce(testing::Return(0.7)) .WillRepeatedly(testing::Return(1.0)); - auto person1 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_15_to_34); - auto person2 = mio::abm::Person(this->get_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 person1 = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + auto person2 = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + person1.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + person1.set_assigned_location(work.get_type(), work.get_id(), work.get_model_id()); + person2.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + person2.set_assigned_location(work.get_type(), work.get_id(), work.get_model_id()); // Check that person1 goes to work and person2 stays at home. - auto p1_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(person1); EXPECT_EQ(mio::abm::go_to_work(p1_rng, person1, t_morning, dt, params), mio::abm::LocationType::Work); - auto p2_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(person2); EXPECT_EQ(mio::abm::go_to_work(p2_rng, person2, t_morning, dt, params), mio::abm::LocationType::Home); } @@ -193,9 +195,9 @@ TEST_F(TestLockdownRules, no_home_office) .WillOnce(testing::Return(0.7)) .WillRepeatedly(testing::Return(1.0)); - auto p = mio::abm::Person(this->get_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()); + auto p = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p.set_assigned_location(work.get_type(), work.get_id(), work.get_model_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; @@ -210,7 +212,7 @@ TEST_F(TestLockdownRules, no_home_office) mio::abm::set_home_office(t_opening, 0., params); // Test that after removing the home office rules, p goes back to the office. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); EXPECT_EQ(mio::abm::go_to_work(p_rng, p, t_morning, dt, params), mio::abm::LocationType::Work); } @@ -225,16 +227,16 @@ TEST_F(TestLockdownRules, social_event_closure) mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location event(mio::abm::LocationType::SocialEvent, 1, num_age_groups); - auto p = mio::abm::Person(this->get_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()); + auto p = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p.set_assigned_location(event.get_type(), event.get_id(), event.get_model_id()); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Close social events mio::abm::close_social_events(t, 1, params); // Checks that p stays home instead of attending social events during a closure. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); EXPECT_EQ(mio::abm::go_to_event(p_rng, p, t_evening, dt, params), mio::abm::LocationType::Home); } @@ -250,9 +252,9 @@ TEST_F(TestLockdownRules, social_events_opening) mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location event(mio::abm::LocationType::SocialEvent, 1, num_age_groups); - auto p = mio::abm::Person(this->get_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()); + auto p = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + p.set_assigned_location(event.get_type(), event.get_id(), event.get_model_id()); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); // Close then open social events @@ -264,6 +266,6 @@ TEST_F(TestLockdownRules, social_events_opening) EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(testing::Return(0.01)); // Test that after reopening, p attends social events again. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); EXPECT_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 67941f3125..1396f1b94f 100644 --- a/cpp/tests/test_abm_masks.cpp +++ b/cpp/tests/test_abm_masks.cpp @@ -80,9 +80,9 @@ TEST_F(TestMasks, maskProtection) mio::abm::Location infection_location(mio::abm::LocationType::School, 0, num_age_groups); // Two susceptible persons, one with a mask, one without auto susc_person1 = mio::abm::Person(this->get_rng(), infection_location.get_type(), infection_location.get_id(), - age_group_15_to_34); + infection_location.get_model_id(), age_group_15_to_34); auto susc_person2 = mio::abm::Person(this->get_rng(), infection_location.get_type(), infection_location.get_id(), - age_group_15_to_34); + infection_location.get_model_id(), age_group_15_to_34); // Infected person interacting with susceptible persons auto infected1 = make_test_person(this->get_rng(), infection_location, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t, params); // infected 7 days prior @@ -99,11 +99,11 @@ TEST_F(TestMasks, maskProtection) mock_exponential_dist; // Person 1 interaction with full protection EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillOnce(testing::Return(1)); - auto p1_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susc_person1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(susc_person1); interact_testing(p1_rng, susc_person1, infection_location, {susc_person1, susc_person2, infected1}, t, dt, params); // Person 2 interaction without protection EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillOnce(testing::Return(0.5)); - auto p2_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susc_person2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(susc_person2); interact_testing(p2_rng, susc_person2, infection_location, {susc_person1, susc_person2, infected1}, t, dt, params); // susc_person1 (with mask) should remain susceptible diff --git a/cpp/tests/test_abm_mobility_rules.cpp b/cpp/tests/test_abm_mobility_rules.cpp index c4b84a8f66..1ad41809a0 100644 --- a/cpp/tests/test_abm_mobility_rules.cpp +++ b/cpp/tests/test_abm_mobility_rules.cpp @@ -32,8 +32,8 @@ TEST_F(TestMobilityRules, random_mobility) { int t = 0, dt = 1; auto default_type = mio::abm::LocationType::Cemetery; - auto person = mio::abm::Person(this->get_rng(), default_type, 0, age_group_15_to_34); - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); + auto person = mio::abm::Person(this->get_rng(), default_type, 0, 0, age_group_15_to_34); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(person); auto params = mio::abm::Parameters(num_age_groups); ScopedMockDistribution>>> mock_exp_dist; @@ -84,15 +84,17 @@ TEST_F(TestMobilityRules, 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(this->get_rng(), home.get_type(), home.get_id(), age_group_5_to_14); - auto p_adult = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_15_to_34); + auto p_child = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + auto p_adult = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_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::PersonalRandomNumberGenerator(this->get_rng(), p_child); - auto adult_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child); + auto child_rng = mio::abm::PersonalRandomNumberGenerator(p_child); + auto adult_rng = mio::abm::PersonalRandomNumberGenerator(p_adult); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) params.get() = false; params.get()[age_group_5_to_14] = true; @@ -133,13 +135,11 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times) mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); auto p_child_goes_to_school_at_6 = - mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = - mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_6); + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_6); auto p_child_goes_to_school_at_8 = - mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_8 = - mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_8); + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_8 = mio::abm::PersonalRandomNumberGenerator(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); @@ -199,15 +199,13 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times_with_smaller_ mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); // First student goes to school at 6:00 AM auto p_child_goes_to_school_at_6 = - mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = - mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_6); + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_6); // Second student goes to school at 8:30 AM auto p_child_goes_to_school_at_8_30 = - mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_8_30 = - mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_8_30); + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_8_30 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_8_30); // Time points for the morning scenarios auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); @@ -243,8 +241,9 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times_with_smaller_ TEST_F(TestMobilityRules, school_return) { mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); - auto p_child = mio::abm::Person(this->get_rng(), school.get_type(), school.get_id(), age_group_5_to_14); - auto rng_child = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child); + auto p_child = + mio::abm::Person(this->get_rng(), school.get_type(), school.get_id(), school.get_model_id(), age_group_5_to_14); + auto rng_child = mio::abm::PersonalRandomNumberGenerator(p_child); // Simulate a time point after school hours auto t = mio::abm::TimePoint(0) + mio::abm::hours(15); @@ -275,10 +274,12 @@ TEST_F(TestMobilityRules, worker_goes_to_work) .WillOnce(testing::Return(0.)) .WillRepeatedly(testing::Return(1.0)); - auto p_retiree = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_60_to_79); - auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_retiree); - auto p_adult = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_15_to_34); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); + auto p_retiree = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); + auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(p_retiree); + auto p_adult = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(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); @@ -323,10 +324,12 @@ TEST_F(TestMobilityRules, worker_goes_to_work_with_non_dividable_timespan) .WillRepeatedly(testing::Return(1.0)); // Set up two people: one retiree and one working adult. - auto p_retiree = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_60_to_79); - auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_retiree); - auto p_adult = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_15_to_34); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); + auto p_retiree = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); + auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(p_retiree); + auto p_adult = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(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); @@ -377,13 +380,11 @@ TEST_F(TestMobilityRules, workers_go_to_work_in_different_times) // Create two workers: one goes to work at 6 AM and the other at 8 AM. auto p_adult_goes_to_work_at_6 = - mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_15_to_34); - auto rng_adult_goes_to_work_at_6 = - mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult_goes_to_work_at_6); + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + auto rng_adult_goes_to_work_at_6 = mio::abm::PersonalRandomNumberGenerator(p_adult_goes_to_work_at_6); auto p_adult_goes_to_work_at_8 = - mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_15_to_34); - auto rng_adult_goes_to_work_at_8 = - mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult_goes_to_work_at_8); + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); + auto rng_adult_goes_to_work_at_8 = mio::abm::PersonalRandomNumberGenerator(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); @@ -425,11 +426,12 @@ TEST_F(TestMobilityRules, work_return) { mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); // Set up a random number generator and a worker who is currently at work - auto p_adult = mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), age_group_35_to_59); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); + auto p_adult = + mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), work.get_model_id(), age_group_35_to_59); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(p_adult); // Set the time to 5 PM (17:00) when the worker should return home - auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); + auto dt = mio::abm::hours(1); // Test that the worker, who is currently at work, goes home after 5 PM EXPECT_EQ(mio::abm::go_to_work(rng_adult, p_adult, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); @@ -449,7 +451,7 @@ TEST_F(TestMobilityRules, quarantine) auto p_inf1 = make_test_person(this->get_rng(), work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_inf1 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf1); + auto rng_inf1 = mio::abm::PersonalRandomNumberGenerator(p_inf1); p_inf1.get_tested(rng_inf1, t, test_params); // Check detected infected person quarantines at home EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf1, p_inf1, t, dt, mio::abm::Parameters(num_age_groups)), @@ -457,14 +459,14 @@ TEST_F(TestMobilityRules, quarantine) auto p_inf2 = make_test_person(this->get_rng(), work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_inf2 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf2); + auto rng_inf2 = mio::abm::PersonalRandomNumberGenerator(p_inf2); // Check that undetected infected person does not quaratine EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf2, p_inf2, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); auto p_inf3 = make_test_person(this->get_rng(), hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf3 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf3); + auto rng_inf3 = mio::abm::PersonalRandomNumberGenerator(p_inf3); p_inf1.get_tested(rng_inf3, t, test_params); // Check that detected infected person does not leave hospital to quarantine EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf3, p_inf3, t, dt, mio::abm::Parameters(num_age_groups)), @@ -477,11 +479,11 @@ TEST_F(TestMobilityRules, quarantine) TEST_F(TestMobilityRules, hospital) { mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); auto p_inf = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf); + auto rng_inf = mio::abm::PersonalRandomNumberGenerator(p_inf); // Ensure person goes to the hospital when severely infected EXPECT_EQ(mio::abm::go_to_hospital(rng_inf, p_inf, t, dt, mio::abm::Parameters(num_age_groups)), @@ -489,7 +491,7 @@ TEST_F(TestMobilityRules, hospital) auto p_car = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - auto rng_car = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_car); + auto rng_car = mio::abm::PersonalRandomNumberGenerator(p_car); // Ensure person has infection symptoms still stay at home EXPECT_EQ(mio::abm::go_to_hospital(rng_car, p_car, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); @@ -511,10 +513,11 @@ TEST_F(TestMobilityRules, go_shopping) // Create an infected child in the hospital auto p_hosp = make_test_person(this->get_rng(), hospital, age_group_0_to_4, mio::abm::InfectionState::InfectedSymptoms, t_weekday); - auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_hosp); + auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(p_hosp); // Create a healthy elderly person at home - auto p_home = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_60_to_79); - auto rng_home = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_home); + auto p_home = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); + auto rng_home = mio::abm::PersonalRandomNumberGenerator(p_home); // Check that an infected person stays in the hospital and doesn't go shopping EXPECT_EQ(mio::abm::go_to_shop(rng_hosp, p_hosp, t_weekday, dt, mio::abm::Parameters(num_age_groups)), @@ -548,7 +551,7 @@ TEST_F(TestMobilityRules, shop_return) mio::abm::Location shop(mio::abm::LocationType::BasicsShop, 0, num_age_groups); auto p = make_test_person(this->get_rng(), shop, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); - auto rng_p = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(p); // Simulate the person spending 1 hour at the shop p.add_time_at_location(dt); @@ -564,11 +567,13 @@ TEST_F(TestMobilityRules, go_event) { // Initialize two people, one at work and one at home mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); - auto p_work = mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), age_group_35_to_59); - auto rng_work = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_work); + auto p_work = + mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), work.get_model_id(), age_group_35_to_59); + auto rng_work = mio::abm::PersonalRandomNumberGenerator(p_work); mio::abm::Location home(mio::abm::LocationType::Home, 1, num_age_groups); - auto p_home = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), age_group_60_to_79); - auto rng_home = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_home); + auto p_home = + mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); + auto rng_home = mio::abm::PersonalRandomNumberGenerator(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); @@ -607,8 +612,9 @@ TEST_F(TestMobilityRules, event_return) mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location social_event(mio::abm::LocationType::SocialEvent, 1, num_age_groups); // Initialize the person at the social event location - auto p = mio::abm::Person(this->get_rng(), social_event.get_type(), social_event.get_id(), age_group_15_to_34); - auto rng_p = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto p = mio::abm::Person(this->get_rng(), social_event.get_type(), social_event.get_id(), + social_event.get_model_id(), age_group_15_to_34); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(p); // Simulate the person spending 3 hours at the social event p.add_time_at_location(dt); // After spending the time at the social event, the person should return home @@ -621,11 +627,11 @@ TEST_F(TestMobilityRules, event_return) TEST_F(TestMobilityRules, icu) { mio::abm::Location hospital(mio::abm::LocationType::Hospital, 0, num_age_groups); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); auto p_hosp = make_test_person(this->get_rng(), hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedCritical, t); - auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_hosp); + auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(p_hosp); // Ensure critically infected person goes to the ICU EXPECT_EQ(mio::abm::go_to_icu(rng_hosp, p_hosp, t, dt, mio::abm::Parameters(num_age_groups)), @@ -634,7 +640,7 @@ TEST_F(TestMobilityRules, icu) mio::abm::Location work(mio::abm::LocationType::Work, 1, num_age_groups); auto p_work = make_test_person(this->get_rng(), work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_work = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_work); + auto rng_work = mio::abm::PersonalRandomNumberGenerator(p_work); // Ensure infected with symptions person can still go to work EXPECT_EQ(mio::abm::go_to_icu(rng_work, p_work, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); @@ -646,14 +652,14 @@ TEST_F(TestMobilityRules, icu) TEST_F(TestMobilityRules, recover) { mio::abm::Location hospital(mio::abm::LocationType::Hospital, 0); - auto t = mio::abm::TimePoint(12346); - auto dt = mio::abm::hours(1); + auto t = mio::abm::TimePoint(12346); + auto dt = mio::abm::hours(1); auto p_rec = make_test_person(this->get_rng(), hospital, age_group_60_to_79, mio::abm::InfectionState::Recovered, t); - auto rng_rec = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_rec); + auto rng_rec = mio::abm::PersonalRandomNumberGenerator(p_rec); auto p_inf = make_test_person(this->get_rng(), hospital, age_group_60_to_79, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf); + auto rng_inf = mio::abm::PersonalRandomNumberGenerator(p_inf); // Ensure recovered person returns home and infected severe person stay in hospital EXPECT_EQ(mio::abm::return_home_when_recovered(rng_rec, p_rec, t, dt, {num_age_groups}), mio::abm::LocationType::Home); @@ -670,7 +676,7 @@ TEST_F(TestMobilityRules, dead) auto t = mio::abm::TimePoint(12346); auto dt = mio::abm::hours(1); auto p_dead = make_test_person(this->get_rng(), icu, age_group_60_to_79, mio::abm::InfectionState::Dead, t); - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_dead); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(p_dead); 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_model.cpp b/cpp/tests/test_abm_model.cpp index c1a76b5299..948abd4901 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -17,10 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/location.h" +#include "abm/location_type.h" #include "abm/person.h" #include "abm/model.h" #include "abm_helpers.h" +#include "memilio/epidemiology/age_group.h" #include "random_number_test.h" +#include using TestModel = RandomNumberTest; @@ -84,16 +88,15 @@ TEST_F(TestModel, addPerson) auto model = mio::abm::Model(num_age_groups); auto location = model.add_location(mio::abm::LocationType::School); - model.add_person(location, age_group_15_to_34); - model.add_person(location, age_group_35_to_59); + auto id1 = model.add_person(location, age_group_15_to_34); + auto id2 = model.add_person(location, age_group_35_to_59); // Verify the number of persons in the model and their respective age groups. EXPECT_EQ(model.get_persons().size(), 2); - EXPECT_EQ(model.get_person(0).get_age(), age_group_15_to_34); - EXPECT_EQ(model.get_person(1).get_age(), age_group_35_to_59); + EXPECT_EQ(model.get_person(id1).get_age(), age_group_15_to_34); + EXPECT_EQ(model.get_person(id2).get_age(), age_group_35_to_59); } - /** * @brief Test combined subpopulation count by location type in the Model class. */ @@ -132,7 +135,7 @@ TEST_F(TestModel, getSubpopulationCombined) TEST_F(TestModel, findLocation) { // Create a model and add different location types. - auto model = mio::abm::Model(num_age_groups); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); auto home_id = model.add_location(mio::abm::LocationType::Home); @@ -142,9 +145,9 @@ TEST_F(TestModel, findLocation) // Add a person to the model and assign them to multiple locations. auto person_id = add_test_person(model, home_id); auto& person = model.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); + person.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + person.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + person.set_assigned_location(mio::abm::LocationType::School, school_id, model.get_id()); // Verify that the find_location method correctly identifies each assigned location. EXPECT_EQ(model.find_location(mio::abm::LocationType::Work, person_id), work_id); @@ -165,9 +168,9 @@ TEST_F(TestModel, evolveStateTransition) { using testing::Return; - auto t = mio::abm::TimePoint(0); - auto dt = mio::abm::hours(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Setup incubation and infection period parameters to prevent state transitions within one hour. p1 and p3 don't transition. @@ -185,7 +188,7 @@ TEST_F(TestModel, evolveStateTransition) .get()[{mio::abm::VirusVariant::Wildtype, age_group_15_to_34}] = 2 * dt.days(); - // Add locations and persons to the model with different initial infection states. + // Add locations and persons to the model with different initial infection states. auto location1 = model.add_location(mio::abm::LocationType::School); auto location2 = model.add_location(mio::abm::LocationType::Work); add_test_person(model, location1, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); @@ -197,9 +200,9 @@ TEST_F(TestModel, evolveStateTransition) auto& p3 = model.get_persons()[2]; // Assign persons to their respective locations. - 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); + p1.set_assigned_location(mio::abm::LocationType::School, location1, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::School, location1, model.get_id()); + p3.set_assigned_location(mio::abm::LocationType::Work, location2, model.get_id()); // Setup mock so p2 becomes infected ScopedMockDistribution>>> @@ -221,9 +224,9 @@ TEST_F(TestModel, evolveMobilityRules) { using testing::Return; - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Setup infection period parameters to prevent state transitions within one hour. p1 doesn't transition. @@ -259,12 +262,12 @@ TEST_F(TestModel, evolveMobilityRules) auto& p1 = model.get_person(pid1); auto& p2 = model.get_person(pid2); - 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); + p1.set_assigned_location(mio::abm::LocationType::School, school_id, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::School, school_id, model.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); ScopedMockDistribution>>> mock_exponential_dist; @@ -288,9 +291,9 @@ TEST_F(TestModel, evolveMobilityTrips) using testing::Return; // Initialize model, time, and step size for simulation. - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(2); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(2); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Setup so p1-p5 don't do transition @@ -325,25 +328,28 @@ TEST_F(TestModel, evolveMobilityTrips) auto& p4 = model.get_person(pid4); auto& p5 = model.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); + p1.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id, model.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p3.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p4.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p3.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, model.get_id()); + p4.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, model.get_id()); + p5.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id, model.get_id()); + p5.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p5.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); // Set trips for persons between assigned locations. mio::abm::TripList& data = model.get_trip_list(); - 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); + mio::abm::Trip trip1(p1.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id, + mio::abm::LocationType::Work); + mio::abm::Trip trip2(p2.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id, + mio::abm::LocationType::SocialEvent); + mio::abm::Trip trip3(p5.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), event_id, home_id, + mio::abm::LocationType::SocialEvent); data.add_trip(trip1); data.add_trip(trip2); data.add_trip(trip3); @@ -375,13 +381,13 @@ TEST_F(TestModel, evolveMobilityTrips) .WillOnce(testing::Return(0.8)) // draw random beta p4 .RetiresOnSaturation(); - auto rng_p1 = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), p1); + auto rng_p1 = mio::abm::PersonalRandomNumberGenerator(p1); p1.add_new_infection(mio::abm::Infection(rng_p1, static_cast(0), p1.get_age(), model.parameters, t, mio::abm::InfectionState::InfectedNoSymptoms)); - auto rng_p3 = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), p1); + auto rng_p3 = mio::abm::PersonalRandomNumberGenerator(p1); p3.add_new_infection(mio::abm::Infection(rng_p3, static_cast(0), p3.get_age(), model.parameters, t, mio::abm::InfectionState::InfectedSevere)); - auto rng_p4 = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), p1); + auto rng_p4 = mio::abm::PersonalRandomNumberGenerator(p1); p4.add_new_infection(mio::abm::Infection(rng_p4, static_cast(0), p4.get_age(), model.parameters, t, mio::abm::InfectionState::Recovered)); @@ -430,10 +436,12 @@ TEST_F(TestModel, evolveMobilityTrips) // Add additional weekend trips for further verification. bool weekend = true; - 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); + mio::abm::Trip tripweekend1(p1.get_id(), mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), event_id, + work_id, mio::abm::LocationType::SocialEvent); + mio::abm::Trip tripweekend2(p2.get_id(), mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), home_id, + event_id, mio::abm::LocationType::Home); + mio::abm::Trip tripweekend3(p5.get_id(), mio::abm::TimePoint(0) + mio::abm::days(6) + mio::abm::hours(10), work_id, + event_id, mio::abm::LocationType::Work); data.add_trip(tripweekend1, weekend); data.add_trip(tripweekend2, weekend); data.add_trip(tripweekend3, weekend); @@ -462,9 +470,9 @@ TEST_F(TestModel, reachCapacity) using testing::Return; // Initialize time and model. - auto t = mio::abm::TimePoint{mio::abm::hours(8).seconds()}; - auto dt = mio::abm::hours(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint{mio::abm::hours(8).seconds()}; + auto dt = mio::abm::hours(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); model.parameters.get()[age_group_5_to_14] = true; @@ -489,10 +497,10 @@ TEST_F(TestModel, reachCapacity) auto p2 = add_test_person(model, home_id, age_group_5_to_14); // Assign both persons to School and Home. - model.get_person(p1).set_assigned_location(mio::abm::LocationType::School, school_id); - model.get_person(p2).set_assigned_location(mio::abm::LocationType::School, school_id); - model.get_person(p1).set_assigned_location(mio::abm::LocationType::Home, home_id); - model.get_person(p2).set_assigned_location(mio::abm::LocationType::Home, home_id); + model.get_person(p1).set_assigned_location(mio::abm::LocationType::School, school_id, 0); + model.get_person(p2).set_assigned_location(mio::abm::LocationType::School, school_id, 0); + model.get_person(p1).set_assigned_location(mio::abm::LocationType::Home, home_id, 0); + model.get_person(p2).set_assigned_location(mio::abm::LocationType::Home, home_id, 0); // Set the capacity of the school to 1 person with a distance requirement of 66. model.get_location(school_id).set_capacity(1, 66); @@ -540,18 +548,21 @@ TEST_F(TestModel, checkMobilityOfDeadPerson) auto& p_dead = model.get_persons()[0]; auto& p_severe = model.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); + p_dead.set_assigned_location(mio::abm::LocationType::ICU, icu_id, model.get_id()); + p_dead.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_dead.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, model.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::ICU, icu_id, model.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); // Add trip to see if a dead person can change location outside of cemetery by scheduled trips mio::abm::TripList& trip_list = model.get_trip_list(); - 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); + mio::abm::Trip trip1(p_dead.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(2), work_id, home_id, + mio::abm::LocationType::Work); + mio::abm::Trip trip2(p_dead.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id, + mio::abm::LocationType::Home); + mio::abm::Trip trip3(p_severe.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(3), home_id, icu_id, + mio::abm::LocationType::Home); trip_list.add_trip(trip1); trip_list.add_trip(trip2); trip_list.add_trip(trip3); @@ -576,7 +587,7 @@ using TestModelTestingCriteria = RandomNumberTest; */ TEST_F(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) { - auto model = mio::abm::Model(num_age_groups); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Make sure the infected person stay in Infected long enough model.parameters.get()[{mio::abm::VirusVariant(0), age_group_15_to_34}] = @@ -595,9 +606,9 @@ TEST_F(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) auto pid = add_test_person(model, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, current_time - test_time); auto& person = model.get_person(pid); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); - person.set_assigned_location(mio::abm::LocationType::Home, home_id); - person.set_assigned_location(mio::abm::LocationType::Work, work_id); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); + person.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + person.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); auto validity_period = mio::abm::days(1); const auto start_date = mio::abm::TimePoint(20); @@ -626,7 +637,8 @@ TEST_F(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) .WillOnce(testing::Return(0.0)) // Draw for isolation compliance (doesn't matter in this test) .WillOnce( testing::Return(0.7)); // Person complies with testing (even though there is not testing strategy left) - EXPECT_EQ(model.get_testing_strategy().run_strategy(rng_person, person, work, current_time), false); // Testing scheme active and restricts entry + EXPECT_EQ(model.get_testing_strategy().run_strategy(rng_person, person, work, current_time), + false); // Testing scheme active and restricts entry // Try to re-add the same testing scheme and confirm it doesn't duplicate, then remove it. model.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, @@ -666,7 +678,7 @@ TEST_F(TestModel, checkParameterConstraints) params.get()[mio::abm::MaskType::FFP2] = 0.6; params.get()[mio::abm::MaskType::Surgical] = 0.7; params.get() = mio::abm::TimePoint(0); - // Check that the parameter values are within their constraints (should pass). + // Check that the parameter values are within their constraints (should pass). EXPECT_FALSE(params.check_constraints()); params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -1.; @@ -686,8 +698,8 @@ TEST_F(TestModel, checkParameterConstraints) params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 5.; params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -6.; EXPECT_TRUE(params.check_constraints()); - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 6.; - params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -7.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 6.; + params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -7.; EXPECT_TRUE(params.check_constraints()); params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = 7.; params.get()[{mio::abm::VirusVariant::Wildtype, age_group_0_to_4}] = -8.; @@ -741,10 +753,10 @@ TEST_F(TestModel, mobilityRulesWithAppliedNPIs) { using testing::Return; // Test when the NPIs are applied, people can enter targeted location if they comply to the rules. - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(1); - auto test_time = mio::abm::minutes(30); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto test_time = mio::abm::minutes(30); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); model.parameters @@ -797,17 +809,17 @@ TEST_F(TestModel, mobilityRulesWithAppliedNPIs) auto& p_no_test = model.get_person(p_id_no_test); auto& p_no_isolation = model.get_person(p_id_no_isolation); - p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::School, school_id); - p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_no_mask.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_no_mask.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_no_test.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_no_test.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_no_isolation.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_no_isolation.set_assigned_location(mio::abm::LocationType::Home, home_id); + p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::School, school_id, model.get_id()); + p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_no_mask.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_no_mask.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_no_test.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_no_test.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_no_isolation.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_no_isolation.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); auto testing_criteria = mio::abm::TestingCriteria( {}, {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}); @@ -858,10 +870,10 @@ TEST_F(TestModel, mobilityTripWithAppliedNPIs) { using testing::Return; // Test when the NPIs are applied, people can enter targeted location if they comply to the rules. - auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); - auto dt = mio::abm::hours(1); - auto test_time = mio::abm::minutes(30); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0) + mio::abm::hours(8); + auto dt = mio::abm::hours(1); + auto test_time = mio::abm::minutes(30); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); model.parameters @@ -914,17 +926,17 @@ TEST_F(TestModel, mobilityTripWithAppliedNPIs) auto& p_no_test = model.get_person(p_id_no_test); auto& p_no_isolation = model.get_person(p_id_no_isolation); - p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::School, school_id); - p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_no_mask.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_no_mask.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_no_test.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_no_test.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_no_isolation.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_no_isolation.set_assigned_location(mio::abm::LocationType::Home, home_id); + p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_compliant_go_to_work.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::School, school_id, model.get_id()); + p_compliant_go_to_school.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_no_mask.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_no_mask.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_no_test.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_no_test.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_no_isolation.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); + p_no_isolation.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); auto testing_criteria = mio::abm::TestingCriteria( {}, {mio::abm::InfectionState::InfectedSymptoms, mio::abm::InfectionState::InfectedNoSymptoms}); @@ -949,11 +961,11 @@ TEST_F(TestModel, mobilityTripWithAppliedNPIs) // Using trip list mio::abm::TripList& trip_list = model.get_trip_list(); - mio::abm::Trip trip1(p_compliant_go_to_work.get_id(), t, work_id, home_id); - mio::abm::Trip trip2(p_compliant_go_to_school.get_id(), t, school_id, home_id); - mio::abm::Trip trip3(p_no_mask.get_id(), t, work_id, home_id); - mio::abm::Trip trip4(p_no_test.get_id(), t, work_id, home_id); - mio::abm::Trip trip5(p_no_isolation.get_id(), t, work_id, home_id); + mio::abm::Trip trip1(p_compliant_go_to_work.get_id(), t, work_id, home_id, mio::abm::LocationType::Work); + mio::abm::Trip trip2(p_compliant_go_to_school.get_id(), t, school_id, home_id, mio::abm::LocationType::School); + mio::abm::Trip trip3(p_no_mask.get_id(), t, work_id, home_id, mio::abm::LocationType::Work); + mio::abm::Trip trip4(p_no_test.get_id(), t, work_id, home_id, mio::abm::LocationType::Work); + mio::abm::Trip trip5(p_no_isolation.get_id(), t, work_id, home_id, mio::abm::LocationType::Work); trip_list.add_trip(trip1); trip_list.add_trip(trip2); trip_list.add_trip(trip3); @@ -988,13 +1000,14 @@ TEST_F(TestModel, personCanDieInHospital) { using testing::Return; - auto t = mio::abm::TimePoint(0); - auto dt = mio::abm::days(1); - auto model = mio::abm::Model(num_age_groups); + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::days(1); + auto model = mio::abm::Model(num_age_groups); model.get_rng() = this->get_rng(); // Time to go from infected with symptoms to severe infection is 1 day(dt). - model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 1; + model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = + 1; // Time to go from severe infection to critical is 1 day(dt). model.parameters.get()[{mio::abm::VirusVariant::Wildtype, age_group_60_to_79}] = 1; // Time to go from severe infection to dead is 1 day(dt). @@ -1016,10 +1029,10 @@ TEST_F(TestModel, personCanDieInHospital) add_test_person(model, home_id, age_group_60_to_79, mio::abm::InfectionState::Dead, t + dt); auto& p_severe = model.get_persons()[0]; - 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); - p_severe.set_assigned_location(mio::abm::LocationType::Work, work_id); + p_severe.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, model.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::ICU, icu_id, model.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); // Check the infection course goes from InfectedSymptoms to Severe to Dead and skips Critical EXPECT_EQ(p_severe.get_infection_state(t - dt), mio::abm::InfectionState::InfectedSymptoms); diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 61a9b5b337..329afaae5e 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -22,6 +22,7 @@ #include "abm/location_type.h" #include "abm/mobility_rules.h" #include "abm/person.h" +#include "abm/person_id.h" #include "abm/time.h" #include "abm_helpers.h" #include "random_number_test.h" @@ -37,12 +38,18 @@ TEST_F(TestPerson, init) { mio::abm::Location location(mio::abm::LocationType::Work, 7, num_age_groups); auto t = mio::abm::TimePoint(0); - auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), age_group_60_to_79); + auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), location.get_model_id(), + age_group_60_to_79); // Verify default state and location assignments. 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()); + + // Verify copy constructors + auto copied_person = mio::abm::Person(person, 0); + EXPECT_EQ(copied_person.get_infection_state(t), mio::abm::InfectionState::Susceptible); + EXPECT_EQ(copied_person.get_location(), location.get_id()); + EXPECT_EQ(copied_person.get_id(), 0); } /** @@ -51,9 +58,9 @@ TEST_F(TestPerson, init) TEST_F(TestPerson, change_location) { mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - mio::abm::Location loc1(mio::abm::LocationType::PublicTransport, 1, 6, 1); + mio::abm::Location loc1(mio::abm::LocationType::PublicTransport, 1, 6, 0, 1); mio::abm::Location loc2(mio::abm::LocationType::School, 2, num_age_groups); - mio::abm::Location loc3(mio::abm::LocationType::PublicTransport, 3, 6, 2); + mio::abm::Location loc3(mio::abm::LocationType::PublicTransport, 3, 6, 0, 2); auto person = make_test_person(this->get_rng(), home, age_group_0_to_4, mio::abm::InfectionState::Recovered); // Check that a person does not change location to its current location @@ -89,28 +96,31 @@ TEST_F(TestPerson, change_location) TEST_F(TestPerson, setGetAssignedLocation) { mio::abm::Location location(mio::abm::LocationType::Work, 2, num_age_groups); - auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), age_group_35_to_59); + auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), location.get_model_id(), + age_group_35_to_59); // Assign and verify a location for the person. - person.set_assigned_location(location.get_type(), location.get_id()); + person.set_assigned_location(location.get_type(), location.get_id(), location.get_model_id()); EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(2)); // Change the assigned location and verify. - person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(4)); + person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(4), 0); EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(4)); // Fuzzing: assign random valid LocationId values and verify correctness. for (int i = 0; i < 100; ++i) { auto random_id = this->random_integer(0, std::numeric_limits::max()); auto random_type = this->random_integer(0, (int)mio::abm::LocationType::Count - 1); - person.set_assigned_location((mio::abm::LocationType)random_type, mio::abm::LocationId(random_id)); + person.set_assigned_location((mio::abm::LocationType)random_type, mio::abm::LocationId(random_id), 0); EXPECT_EQ(person.get_assigned_location((mio::abm::LocationType)random_type), mio::abm::LocationId(random_id)); } // Boundary test cases: test with boundary LocationIds. - person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(0)); + person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(0), 0); EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(0)); - person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(std::numeric_limits::max())); - EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(std::numeric_limits::max())); + person.set_assigned_location(mio::abm::LocationType::Work, mio::abm::LocationId(std::numeric_limits::max()), + 0); + EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), + mio::abm::LocationId(std::numeric_limits::max())); } /** @@ -146,7 +156,7 @@ TEST_F(TestPerson, quarantine) auto person = make_test_person(this->get_rng(), home, age_group_35_to_59, mio::abm::InfectionState::InfectedSymptoms, t_morning, infection_parameters); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); // Test quarantine when a person is tested and positive. person.get_tested(rng_person, t_morning, test_params); @@ -173,9 +183,10 @@ TEST_F(TestPerson, get_tested) mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); auto infected = make_test_person(this->get_rng(), loc, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - auto rng_infected = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), infected); - auto susceptible = mio::abm::Person(this->get_rng(), loc.get_type(), loc.get_id(), age_group_15_to_34); - auto rng_suscetible = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susceptible); + auto rng_infected = mio::abm::PersonalRandomNumberGenerator(infected); + auto susceptible = + mio::abm::Person(this->get_rng(), loc.get_type(), loc.get_id(), loc.get_model_id(), age_group_15_to_34); + auto rng_suscetible = mio::abm::PersonalRandomNumberGenerator(susceptible); auto pcr_parameters = params.get()[mio::abm::TestType::PCR]; auto antigen_parameters = params.get()[mio::abm::TestType::Antigen]; @@ -234,7 +245,7 @@ TEST_F(TestPerson, getCells) { // Initialize home and target locations with designated cells. mio::abm::Location home(mio::abm::LocationType::Home, 0, 6, 1); - mio::abm::Location location(mio::abm::LocationType::PublicTransport, 1, 6, 7); + mio::abm::Location location(mio::abm::LocationType::PublicTransport, 1, 6, 0, 7); // Create a test person at the home location. auto person = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms); @@ -259,8 +270,9 @@ TEST_F(TestPerson, interact) mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::TimePoint t(0); // Create a person and set up a random number generator specific to that person. - auto person = mio::abm::Person(this->get_rng(), loc.get_type(), loc.get_id(), age_group_15_to_34); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); + auto person = + mio::abm::Person(this->get_rng(), loc.get_type(), loc.get_id(), loc.get_model_id(), age_group_15_to_34); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); auto dt = mio::abm::seconds(8640); //0.1 days // Simulate interaction and check that the person accumulates time at the location. interact_testing(rng_person, person, loc, {person}, t, dt, infection_parameters); @@ -317,9 +329,10 @@ TEST_F(TestPerson, getMaskProtectiveFactor) */ TEST_F(TestPerson, getLatestProtection) { - auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), age_group_15_to_34); - auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); + auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); + auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), location.get_model_id(), + age_group_15_to_34); + auto prng = mio::abm::PersonalRandomNumberGenerator(person); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); auto t = mio::abm::TimePoint(0); @@ -343,13 +356,12 @@ TEST_F(TestPerson, getLatestProtection) */ TEST_F(TestPerson, rng) { - auto p = - mio::abm::Person(this->get_rng(), mio::abm::LocationType::Home, 0, age_group_35_to_59, mio::abm::PersonId(13)); + auto p = mio::abm::Person(this->get_rng(), mio::abm::LocationType::Home, 0, 0, age_group_35_to_59, 13); EXPECT_EQ(p.get_rng_counter(), mio::Counter(0)); // Verify RNG counter increments. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); EXPECT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{0})); p_rng(); @@ -392,7 +404,7 @@ TEST_F(TestPerson, isCompliant) // Create test person and associated random number generator auto person = make_test_person(this->get_rng(), home); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); // Test cases with a complete truth table for compliance levels struct TestCase { diff --git a/cpp/tests/test_abm_serialization.cpp b/cpp/tests/test_abm_serialization.cpp index 7dc7043ebb..7dbdc0249d 100644 --- a/cpp/tests/test_abm_serialization.cpp +++ b/cpp/tests/test_abm_serialization.cpp @@ -198,7 +198,6 @@ TEST(TestAbmSerialization, Person) reference_json["assigned_locations"] = json_uint_array({i++, i++, i++, i++, i++, i++, i++, i++, i++, i++, i++}); reference_json["cells"] = json_uint_array({i++}); reference_json["compliance"] = json_double_array({(double)i++, (double)i++, (double)i++}); - reference_json["id"] = Json::UInt(i++); reference_json["infections"] = Json::Value(Json::arrayValue); reference_json["last_transport_mode"] = Json::UInt(i++); reference_json["location"] = Json::UInt(i++); @@ -215,6 +214,8 @@ TEST(TestAbmSerialization, Person) mio::serialize_json(mio::CustomIndexArray{}).value(); reference_json["time_at_location"]["seconds"] = Json::Int(i++); reference_json["vaccinations"] = Json::Value(Json::arrayValue); + reference_json["id"] = Json::UInt(i++); + reference_json["rng_index"] = Json::UInt(i++); test_json_serialization(reference_json); } diff --git a/cpp/tests/test_abm_simulation.cpp b/cpp/tests/test_abm_simulation.cpp index 944e652281..abe8f72982 100644 --- a/cpp/tests/test_abm_simulation.cpp +++ b/cpp/tests/test_abm_simulation.cpp @@ -17,10 +17,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/location_type.h" #include "abm_helpers.h" #include "abm/common_abm_loggers.h" #include "matchers.h" #include "memilio/io/history.h" +#include TEST(TestSimulation, advance_random) { @@ -98,13 +100,20 @@ TEST(TestSimulation, advanceWithCommonHistory) mio::abm::TripList& trip_list = model.get_trip_list(); // We add trips for person two to test the history and if it is working correctly - 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); + mio::abm::Trip trip1(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(2), work_id, + home_id, mio::abm::LocationType::Work); + mio::abm::Trip trip2(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(3), icu_id, + home_id, mio::abm::LocationType::ICU); + mio::abm::Trip trip3(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(4), hospital_id, + home_id, mio::abm::LocationType::Hospital); + mio::abm::Trip trip4(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(5), social_id, + home_id, mio::abm::LocationType::SocialEvent); + mio::abm::Trip trip5(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(6), basics_id, + home_id, mio::abm::LocationType::BasicsShop); + mio::abm::Trip trip6(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(7), public_id, + home_id, mio::abm::LocationType::PublicTransport); + mio::abm::Trip trip7(static_cast(person2.get()), mio::abm::TimePoint(0) + mio::abm::hours(8), home_id, + home_id, mio::abm::LocationType::Home); 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 10e359a9a2..8c8fb5de06 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -30,7 +30,8 @@ TEST_F(TestTestingCriteria, addRemoveAndEvaluateTestCriteria) // Create test locations and a person in a specific infection state. mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); - auto person = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); + auto person = + make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); mio::abm::TimePoint t{0}; // Initialize testing criteria with no age group or infection state set. @@ -119,10 +120,10 @@ TEST_F(TestTestingScheme, runScheme) auto person1 = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, start_date - test_params_pcr.required_time); - auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person1); + auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(person1); auto person2 = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::Recovered, start_date - test_params_pcr.required_time); - auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person2); + auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(person2); // Mock uniform distribution to control random behavior in testing. ScopedMockDistribution>>> mock_uniform_dist; @@ -170,10 +171,10 @@ TEST_F(TestTestingScheme, initAndRunTestingStrategy) auto person1 = make_test_person(this->get_rng(), loc_work, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, start_date - test_params_pcr.required_time); - auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person1); + auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(person1); auto person2 = make_test_person(this->get_rng(), loc_work, age_group_15_to_34, mio::abm::InfectionState::Recovered, start_date - test_params_pcr.required_time); - auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person2); + auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(person2); // Mock uniform distribution to control random behavior in testing. ScopedMockDistribution>>> mock_uniform_dist; diff --git a/cpp/tests/test_analyze_result.cpp b/cpp/tests/test_analyze_result.cpp index 181606800b..9f963a2e53 100644 --- a/cpp/tests/test_analyze_result.cpp +++ b/cpp/tests/test_analyze_result.cpp @@ -179,7 +179,7 @@ TEST(TestInterpolateGraph, basic) g.add_node(0, Model(1), 0.5); g.add_node(1, Model(1), 0.5); for (auto& n : g.nodes()) { - n.property.evolve(0.5, 4.0); + n.property.advance(0.5, 4.0); } auto interpolated = mio::interpolate_simulation_result(g); diff --git a/cpp/tests/test_dynamic_npis.cpp b/cpp/tests/test_dynamic_npis.cpp index 34b7ea63ec..2b5c9a5a12 100644 --- a/cpp/tests/test_dynamic_npis.cpp +++ b/cpp/tests/test_dynamic_npis.cpp @@ -297,8 +297,8 @@ TEST(DynamicNPIs, mobility) EXPECT_CALL(node_from.get_simulation(), advance).Times(1).WillOnce([&](auto t) { node_from.get_simulation().result.add_time_point(t, last_state_safe); }); - node_from.evolve(3.0, 2.5); - node_to.evolve(3.0, 2.5); + node_from.advance(3.0, 2.5); + node_to.advance(3.0, 2.5); edge.apply_mobility(3.0, 2.5, node_from, node_to); EXPECT_EQ(edge.get_parameters().get_coefficients()[0].get_dampings().size(), 0); //threshold not exceeded @@ -306,8 +306,8 @@ TEST(DynamicNPIs, mobility) EXPECT_CALL(node_from.get_simulation(), advance).Times(1).WillOnce([&](auto t) { node_from.get_simulation().result.add_time_point(t, last_state_crit); }); - node_from.evolve(4.5, 1.5); - node_to.evolve(4.5, 1.5); + node_from.advance(4.5, 1.5); + node_to.advance(4.5, 1.5); edge.apply_mobility(4.5, 1.5, node_from, node_to); EXPECT_EQ(edge.get_parameters().get_coefficients()[0].get_dampings().size(), @@ -316,8 +316,8 @@ TEST(DynamicNPIs, mobility) EXPECT_CALL(node_from.get_simulation(), advance).Times(1).WillOnce([&](auto t) { node_from.get_simulation().result.add_time_point(t, last_state_crit); }); - node_from.evolve(6.0, 1.5); - node_to.evolve(6.0, 1.5); + node_from.advance(6.0, 1.5); + node_to.advance(6.0, 1.5); edge.apply_mobility(6.0, 1.5, node_from, node_to); EXPECT_EQ(edge.get_parameters().get_coefficients()[0].get_dampings().size(), 2); //NPIs implemented diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp new file mode 100644 index 0000000000..187c3c68f4 --- /dev/null +++ b/cpp/tests/test_graph_abm.cpp @@ -0,0 +1,262 @@ +/* +* Copyright (C) 2020-2024 MEmilio +* +* Authors: Julia Bicker +* +* 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/location.h" +#include "abm/location_id.h" +#include "abm/mobility_data.h" +#include "abm/model.h" +#include "abm/parameters.h" +#include "graph_abm/graph_abmodel.h" +#include "abm/location_type.h" +#include "abm/time.h" +#include "graph_abm/graph_abm_mobility.h" +#include "memilio/epidemiology/age_group.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/miompi.h" +#include "memilio/mobility/graph.h" +#include "abm_helpers.h" +#include +#include +#include +#include + +struct MockHistory { + + template + void log(const T& t) + { + mio::unused(t); + } +}; + +TEST(TestGraphAbm, test_advance_node) +{ + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(10); + auto model = mio::GraphABModel(size_t(1), 1); + model.parameters.get()[mio::AgeGroup(0)] = true; + auto home_id = model.add_location(mio::abm::LocationType::Home); + auto& home = model.get_location(home_id); + auto work = mio::abm::Location(mio::abm::LocationType::Work, mio::abm::LocationId(0), size_t(1), 2); + auto pid = model.add_person(home_id, mio::AgeGroup(0)); + auto index = model.get_person_index(pid); + auto& p = model.get_person(pid); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p.set_assigned_location(work.get_type(), work.get_id(), 2); + mio::ABMSimulationNode node(MockHistory{}, t, std::move(model)); + EXPECT_EQ(node.get_simulation().get_model().get_activeness_statuses()[index], true); + node.advance(t, dt); + + EXPECT_EQ(node.get_simulation().get_time(), mio::abm::TimePoint(dt.seconds())); + EXPECT_EQ(node.get_simulation().get_model().get_activeness_statuses()[index], false); + EXPECT_EQ(node.get_simulation().get_model().get_person_buffer().size(), 1); + EXPECT_EQ(node.get_simulation().get_model().get_person_buffer()[0], index); +} + +TEST(TestGraphAbm, test_apply_mobility) +{ + auto model1 = + mio::GraphABModel(size_t(2), 1, std::vector{&mio::abm::go_to_work}); + auto model2 = + mio::GraphABModel(size_t(2), 2, std::vector{&mio::abm::go_to_work}); + auto model3 = + mio::GraphABModel(size_t(2), 3, std::vector{&mio::abm::go_to_work}); + model1.parameters.get()[mio::AgeGroup(0)] = true; + model2.parameters.get()[mio::AgeGroup(0)] = true; + model3.parameters.get()[mio::AgeGroup(0)] = true; + + //all persons go to work at 7am + model1.parameters.get()[mio::AgeGroup(0)] = mio::abm::hours(7); + model1.parameters.get()[mio::AgeGroup(0)] = mio::abm::hours(7); + + auto work_id_1 = model1.add_location(mio::abm::LocationType::Work); + auto home_id = model1.add_location(mio::abm::LocationType::Home); + auto work_id_2 = model2.add_location(mio::abm::LocationType::Work); + auto work_id_3 = model3.add_location(mio::abm::LocationType::Work); + auto event_id_1 = model1.add_location(mio::abm::LocationType::SocialEvent); + auto event_id_2 = model2.add_location(mio::abm::LocationType::SocialEvent); + auto& work_1 = model1.get_location(work_id_1); + auto& work_2 = model2.get_location(work_id_2); + auto& work_3 = model3.get_location(work_id_3); + auto& home = model1.get_location(home_id); + auto& event_1 = model1.get_location(event_id_1); + auto& event_2 = model2.get_location(event_id_2); + + EXPECT_EQ(work_1.get_model_id(), 1); + EXPECT_EQ(work_2.get_model_id(), 2); + EXPECT_EQ(work_3.get_model_id(), 3); + + auto p1_id = model1.add_person(home_id, mio::AgeGroup(0)); + auto p2_id = model1.add_person(home_id, mio::AgeGroup(0)); + auto p3_id = model1.add_person(home_id, mio::AgeGroup(1)); + auto p4_id = model1.add_person(home_id, mio::AgeGroup(1)); + auto p5_id = model1.add_person(home_id, mio::AgeGroup(0)); + auto& p1 = model1.get_person(p1_id); + auto& p2 = model1.get_person(p2_id); + auto& p3 = model1.get_person(p3_id); + auto& p4 = model1.get_person(p4_id); + auto& p5 = model1.get_person(p5_id); + p1.set_assigned_location(work_1.get_type(), work_1.get_id(), work_1.get_model_id()); + p2.set_assigned_location(work_2.get_type(), work_2.get_id(), work_2.get_model_id()); + p5.set_assigned_location(work_3.get_type(), work_3.get_id(), work_3.get_model_id()); + p1.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p2.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p3.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p4.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p5.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p3.set_assigned_location(event_1.get_type(), event_1.get_id(), event_1.get_model_id()); + p4.set_assigned_location(event_2.get_type(), event_2.get_id(), event_2.get_model_id()); + + mio::abm::TripList& trips = model1.get_trip_list(); + mio::abm::Trip trip1(p3.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), event_id_1, model1.get_id(), home_id, + model1.get_id(), mio::abm::TransportMode::Unknown, mio::abm::LocationType::SocialEvent); + mio::abm::Trip trip2(p4.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), event_id_2, model2.get_id(), home_id, + model1.get_id(), mio::abm::TransportMode::Unknown, mio::abm::LocationType::SocialEvent); + + trips.add_trip(trip1); + trips.add_trip(trip2); + + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(12); + mio::ABMSimulationNode node1(MockHistory{}, t, std::move(model1)); + mio::ABMSimulationNode node2(MockHistory{}, t, std::move(model2)); + mio::ABMSimulationNode node3(MockHistory{}, t, std::move(model3)); + + node1.advance(t, dt); + node2.advance(t, dt); + node3.advance(t, dt); + + EXPECT_EQ(node2.get_simulation().get_model().get_persons().size(), 0); + EXPECT_EQ(node3.get_simulation().get_model().get_persons().size(), 0); + EXPECT_EQ(node1.get_simulation().get_model().get_persons().size(), 5); + EXPECT_EQ(node1.get_simulation() + .get_model() + .get_activeness_statuses()[node1.get_simulation().get_model().get_person_index(p2_id)], + false); + EXPECT_EQ(node1.get_simulation() + .get_model() + .get_activeness_statuses()[node1.get_simulation().get_model().get_person_index(p4_id)], + false); + EXPECT_EQ(node1.get_simulation() + .get_model() + .get_activeness_statuses()[node1.get_simulation().get_model().get_person_index(p5_id)], + false); + + mio::ABMMobilityEdge edge; + edge.apply_mobility(node1, node2, t); + edge.apply_mobility(node1, node3, t); + + EXPECT_EQ(node1.get_simulation().get_model().get_persons().size(), 2); + EXPECT_EQ(node2.get_simulation().get_model().get_persons().size(), 2); + EXPECT_EQ(node3.get_simulation().get_model().get_persons().size(), 1); + EXPECT_EQ(node1.get_simulation().get_model().get_person_buffer().size(), 0); +} + +TEST(TestGraphABM, test_graph_simulation) +{ + auto model1 = mio::GraphABModel(size_t(1), 0); + auto model2 = mio::GraphABModel(size_t(1), 1); + + mio::abm::TimePoint t0 = mio::abm::TimePoint(0); + mio::abm::TimePoint tmax = t0 + mio::abm::days(5); + + mio::Graph, mio::ABMMobilityEdge> graph; + graph.add_node(model1.get_id(), MockHistory{}, t0, std::move(model1)); + graph.add_node(model2.get_id(), MockHistory{}, t0, std::move(model2)); + graph.add_edge(model1.get_id(), model2.get_id()); + graph.add_edge(model2.get_id(), model1.get_id()); + + auto sim = mio::make_abm_graph_sim(t0, mio::abm::hours(12), std::move(graph)); + sim.advance(tmax); + + EXPECT_EQ(sim.get_t(), tmax); +} + +TEST(TestGraphABM, mask_compliance) +{ + auto model = mio::GraphABModel(size_t(2), 0, std::vector{&mio::abm::go_to_work}); + model.parameters.get()[mio::AgeGroup(1)] = true; + model.parameters.get()[mio::AgeGroup(0)] = true; + //add home, work and school location + auto home_id = model.add_location(mio::abm::LocationType::Home); + auto work_id = model.add_location(mio::abm::LocationType::Work); + auto school_id = model.add_location(mio::abm::LocationType::School); + auto& work = model.get_location(work_id); + auto& home = model.get_location(home_id); + auto& school = model.get_location(school_id); + //school and work require FFP2 masks + school.set_required_mask(mio::abm::MaskType::FFP2); + work.set_required_mask(mio::abm::MaskType::FFP2); + auto p_id1 = model.add_person(home_id, mio::AgeGroup(1)); + auto p_id2 = model.add_person(home_id, mio::AgeGroup(0)); + auto& p1 = model.get_person(p_id1); + auto& p2 = model.get_person(p_id2); + p1.set_assigned_location(work.get_type(), work.get_id(), work.get_model_id()); + p1.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + p2.set_assigned_location(school.get_type(), school.get_id(), school.get_model_id()); + p2.set_assigned_location(home.get_type(), home.get_id(), home.get_model_id()); + //person is not compliant with mask + p1.set_compliance(mio::abm::InterventionType::Mask, 0.0); + p2.set_compliance(mio::abm::InterventionType::Mask, 0.0); + + //add trips for p2 + mio::abm::TripList& trips = model.get_trip_list(); + mio::abm::Trip trip1(p2.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(8), school_id, model.get_id(), home_id, + model.get_id(), mio::abm::TransportMode::Unknown, mio::abm::LocationType::School); + + trips.add_trip(trip1); + + auto t0 = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(12); + auto t = t0; + model.evolve(t, dt); + t += dt; + //persons should still be at home + EXPECT_EQ(p1.get_location_type(), mio::abm::LocationType::Home); + EXPECT_EQ(p2.get_location_type(), mio::abm::LocationType::Home); + //person is compliant with mask + p1.set_compliance(mio::abm::InterventionType::Mask, 1.0); + p2.set_compliance(mio::abm::InterventionType::Mask, 1.0); + model.evolve(t, dt); + t += dt; + model.evolve(t, dt); + //person should be at work and school + EXPECT_EQ(p1.get_location_type(), mio::abm::LocationType::Work); + EXPECT_EQ(p2.get_location_type(), mio::abm::LocationType::School); +} + +TEST(TestGraphABM, test_get_person) +{ + auto model = mio::GraphABModel(size_t(2), 0, std::vector{&mio::abm::go_to_work}); + auto home = model.add_location(mio::abm::LocationType::Home); + auto work = model.add_location(mio::abm::LocationType::Work); + auto pid1 = model.add_person(home, mio::AgeGroup(0)); + auto pid2 = model.add_person(work, mio::AgeGroup(1)); + + auto& p1 = model.get_person(pid1); + EXPECT_EQ(p1.get_location(), home); + EXPECT_EQ(p1.get_age(), mio::AgeGroup(0)); + model.remove_person(model.get_person_index(pid1)); + EXPECT_EQ(model.get_person_index(pid1), std::numeric_limits::max()); + + auto& p2 = model.get_person(pid2); + EXPECT_EQ(p2.get_location(), work); + EXPECT_EQ(p2.get_age(), mio::AgeGroup(1)); +} diff --git a/cpp/tests/test_mobility.cpp b/cpp/tests/test_mobility.cpp index d9cab171ec..6b115ca2fb 100644 --- a/cpp/tests/test_mobility.cpp +++ b/cpp/tests/test_mobility.cpp @@ -81,7 +81,7 @@ TEST(TestMobility, compareNoMobilityWithSingleIntegration) 1e-6); } -TEST(TestMobility, nodeEvolve) +TEST(TestMobility, nodeAdvance) { using Model = mio::osecir::Model; Model model(1); @@ -100,7 +100,7 @@ TEST(TestMobility, nodeEvolve) double dt = 0.5; mio::SimulationNode> node(model, t0); - node.evolve(t0, dt); + node.advance(t0, dt); ASSERT_DOUBLE_EQ(node.get_result().get_last_time(), t0 + dt); ASSERT_EQ(print_wrap(node.get_result().get_last_value()), print_wrap(node.get_last_state())); } @@ -141,8 +141,8 @@ TEST(TestMobility, edgeApplyMobility) print_wrap((Eigen::VectorXd(10) << 990 + 99, 0, 0, 0, 10 + 1, 0, 0, 0, 0, 0).finished())); //returns - node1.evolve(t, 0.5); - node2.evolve(t, 0.5); + node1.advance(t, 0.5); + node2.advance(t, 0.5); t += 0.5; edge.apply_mobility(t, 0.5, node1, node2); auto v = node1.get_result().get_last_value(); @@ -159,8 +159,8 @@ TEST(TestMobility, edgeApplyMobility) EXPECT_DOUBLE_EQ(node2.get_result().get_last_value().sum(), 1000); //change node again - node1.evolve(t, 0.5); - node2.evolve(t, 0.5); + node1.advance(t, 0.5); + node2.advance(t, 0.5); t += 0.5; edge.apply_mobility(t, 0.5, node1, node2); EXPECT_DOUBLE_EQ(node1.get_result().get_last_value().sum(), 900); @@ -233,4 +233,4 @@ TEST(TestMobility, add_mobility_result_time_point) EXPECT_NEAR(mobility[0], 11.0, 1e-12); EXPECT_NEAR(mobility[1], 5.0, 1e-12); EXPECT_NEAR(mobility[2], 113.0, 1e-12); -} \ No newline at end of file +} diff --git a/cpp/thirdparty/CMakeLists.txt b/cpp/thirdparty/CMakeLists.txt index 4835a4d606..7a6b672b4e 100644 --- a/cpp/thirdparty/CMakeLists.txt +++ b/cpp/thirdparty/CMakeLists.txt @@ -84,7 +84,8 @@ if(MEMILIO_USE_BUNDLED_BOOST) include(FetchContent) FetchContent_Declare(boost - #don't use the URL from github, that download isn't complete and requires more setup (subrepositories, bootstrapping) + + # don't use the URL from github, that download isn't complete and requires more setup (subrepositories, bootstrapping) URL https://archives.boost.io/release/${MEMILIO_BOOST_VERSION}/source/boost_${MEMILIO_BOOST_VERSION_UNDERSC}.tar.gz ) FetchContent_GetProperties(boost) @@ -97,9 +98,9 @@ if(MEMILIO_USE_BUNDLED_BOOST) add_dependencies(boost boost-bootstrap) add_library(Boost::boost ALIAS boost) target_include_directories(boost SYSTEM INTERFACE $) - - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14) + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14) target_compile_options(boost INTERFACE "-Wno-c++20-attribute-extensions") else() target_compile_options(boost INTERFACE "-Wno-c++20-extensions") @@ -122,13 +123,15 @@ if(MEMILIO_USE_BUNDLED_BOOST) ${boost_SOURCE_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp ${boost_SOURCE_DIR}/libs/filesystem/src/windows_file_codecvt.cpp ) + # Ensure that the boost atomic library is used instead of the standard atomic library, where some functionality is only available as of C++20. target_compile_definitions(boost_filesystem PUBLIC BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF) target_link_libraries(boost_filesystem PUBLIC boost_disable_autolink boost) set_property(TARGET boost_filesystem PROPERTY POSITION_INDEPENDENT_CODE ON) add_library(Boost::filesystem ALIAS boost_filesystem) - if(NOT MSVC) #on gcc and apple clang we need to define BOOST_NO_CXX98_FUNCTION_BASE because a deprecated function is sometimes used in boost + + if(NOT MSVC) # on gcc and apple clang we need to define BOOST_NO_CXX98_FUNCTION_BASE because a deprecated function is sometimes used in boost target_compile_definitions(boost_filesystem PUBLIC BOOST_NO_CXX98_FUNCTION_BASE) endif() @@ -193,15 +196,15 @@ else() to the directory containing the jsoncppConfig.cmake file to build with JsonCpp.") endif() -if (MEMILIO_ENABLE_MPI) +if(MEMILIO_ENABLE_MPI) find_package(MPI REQUIRED COMPONENTS CXX) endif() -if (MEMILIO_ENABLE_OPENMP) +if(MEMILIO_ENABLE_OPENMP) find_package(OpenMP REQUIRED COMPONENTS CXX) endif() -#Random123 library for random number generators +# Random123 library for random number generators message(STATUS "Downloading Random123 library") include(FetchContent) diff --git a/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp b/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp index 11b925c29e..48a2cfa19a 100644 --- a/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/bindings/models/abm.cpp @@ -19,6 +19,7 @@ */ //Includes from pymio +#include "abm/person_id.h" #include "pybind_util.h" #include "utils/custom_index_array.h" #include "utils/parameter_set.h" @@ -72,7 +73,6 @@ PYBIND11_MODULE(_simulation_abm, m) .value("Antigen", mio::abm::TestType::Antigen) .value("PCR", mio::abm::TestType::PCR); - pymio::bind_class(m, "TimeSpan") .def(py::init(), py::arg("seconds") = 0) .def_property_readonly("seconds", &mio::abm::TimeSpan::seconds) @@ -117,7 +117,7 @@ PYBIND11_MODULE(_simulation_abm, m) .def(py::self += mio::abm::TimeSpan{}) .def(py::self - mio::abm::TimeSpan{}) .def(py::self -= mio::abm::TimeSpan{}); - + pymio::bind_class(m, "TestParameters") .def(py::init()) .def_readwrite("sensitivity", &mio::abm::TestParameters::sensitivity) @@ -143,12 +143,12 @@ PYBIND11_MODULE(_simulation_abm, m) .def("index", &mio::abm::LocationId::get); pymio::bind_class(m, "PersonId") - .def(py::init(), py::arg("id")) + .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); @@ -186,14 +186,15 @@ PYBIND11_MODULE(_simulation_abm, m) pymio::bind_Range().get_persons())>(m, "_ModelPersonsRange"); pymio::bind_class(m, "Trip") - .def(py::init>(), py::arg("person_id"), py::arg("time"), py::arg("destination"), py::arg("origin"), - py::arg("cells") = std::vector()) + py::arg("type_of_activity"), py::arg("cells") = std::vector()) .def_readwrite("person_id", &mio::abm::Trip::person_id) .def_readwrite("time", &mio::abm::Trip::time) .def_readwrite("destination", &mio::abm::Trip::destination) .def_readwrite("origin", &mio::abm::Trip::origin) + .def_readwrite("destination_type", &mio::abm::Trip::destination_type) .def_readwrite("cells", &mio::abm::Trip::cells); pymio::bind_class(m, "TripList") @@ -207,7 +208,9 @@ PYBIND11_MODULE(_simulation_abm, m) .def("add_location", &mio::abm::Model::add_location, py::arg("location_type"), py::arg("num_cells") = 1) .def("add_person", py::overload_cast(&mio::abm::Model::add_person), py::arg("location_id"), py::arg("age_group")) - .def("assign_location", &mio::abm::Model::assign_location, py::arg("person_id"), py::arg("location_id")) + .def("assign_location", + py::overload_cast(&mio::abm::Model::assign_location), + py::arg("person_id"), py::arg("location_id")) .def_property_readonly("locations", py::overload_cast<>(&mio::abm::Model::get_locations, py::const_), py::keep_alive<1, 0>{}) //keep this model alive while contents are referenced in ranges .def_property_readonly("persons", py::overload_cast<>(&mio::abm::Model::get_persons, py::const_), @@ -228,12 +231,12 @@ PYBIND11_MODULE(_simulation_abm, m) }, py::return_value_policy::reference_internal); - pymio::bind_class(m, "Simulation") + pymio::bind_class, pymio::EnablePickling::Never>(m, "Simulation") .def(py::init()) .def("advance", - static_cast(&mio::abm::Simulation::advance), + static_cast::*)(mio::abm::TimePoint)>(&mio::abm::Simulation<>::advance), py::arg("tmax")) - .def_property_readonly("model", py::overload_cast<>(&mio::abm::Simulation::get_model)); + .def_property_readonly("model", py::overload_cast<>(&mio::abm::Simulation<>::get_model)); m.attr("__version__") = "dev"; } diff --git a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py index 53cc7b0496..60fcf31d0b 100644 --- a/pycode/memilio-simulation/memilio/simulation_test/test_abm.py +++ b/pycode/memilio-simulation/memilio/simulation_test/test_abm.py @@ -106,9 +106,9 @@ def test_simulation(self): # trips trip_list = abm.TripList() trip_list.add_trip(abm.Trip(0, abm.TimePoint( - 0) + abm.hours(8), social_event_id, home_id)) + 0) + abm.hours(8), social_event_id, home_id, abm.LocationType.SocialEvent)) trip_list.add_trip(abm.Trip(1, abm.TimePoint(0) + - abm.hours(8), work_id, home_id)) + abm.hours(8), work_id, home_id, abm.LocationType.Work)) model.trip_list = trip_list model.use_mobility_rules = False self.assertEqual(model.trip_list.num_trips(), 2)