diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index b4e3595e82..356de9ae13 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -150,6 +150,7 @@ if(MEMILIO_BUILD_MODELS) add_subdirectory(models/ode_sir) add_subdirectory(models/sde_sir) add_subdirectory(models/sde_sirs) + add_subdirectory(models/graph_abm) endif() if(MEMILIO_BUILD_EXAMPLES) diff --git a/cpp/benchmarks/abm.cpp b/cpp/benchmarks/abm.cpp index 7246db43a7..3371b9052d 100644 --- a/cpp/benchmarks/abm.cpp +++ b/cpp/benchmarks/abm.cpp @@ -1,6 +1,7 @@ #include "abm/simulation.h" #include "benchmark/benchmark.h" +#include mio::abm::Simulation make_simulation(size_t num_persons, std::initializer_list seeds) { @@ -144,6 +145,38 @@ void abm_benchmark(benchmark::State& state, size_t num_persons, std::initializer } } +void abm_inactive_persons_benchmark(benchmark::State& state, size_t num_persons, size_t num_inactive_persons, + std::initializer_list seeds) +{ + mio::set_log_level(mio::LogLevel::warn); + for (auto&& _ : state) { + state.PauseTiming(); //exclude the setup from the benchmark + auto sim = make_simulation(num_persons + num_inactive_persons, seeds); + //deactivate num_inactive_persons + for (size_t p_id = 0; p_id < num_inactive_persons; ++p_id) { + sim.get_world().set_activeness(mio::abm::PersonId(p_id)); + } + state.ResumeTiming(); + + //simulated time should be long enough to have full infection runs and migration to every location + auto final_time = sim.get_time() + mio::abm::days(10); + sim.advance(final_time); + + //debug output can be enabled to check for unexpected results (e.g. infections dieing out) + //normally should have no significant effect on runtime + const bool monitor_infection_activity = false; + if constexpr (monitor_infection_activity) { + std::cout << "num_persons = " << num_persons << "\n"; + for (auto inf_state = 0; inf_state < (int)mio::abm::InfectionState::Count; inf_state++) { + std::cout << "inf_state = " << inf_state << ", sum = " + << sim.get_world().get_subpopulation_combined(sim.get_time(), + mio::abm::InfectionState(inf_state)) + << "\n"; + } + } + } +} + //Measure ABM simulation run time with different sizes and different seeds. //Fixed RNG seeds to make runs comparable. When there are code changes, the simulation will still //run differently due to different sequence of random numbers being drawn. But for large enough sizes @@ -156,4 +189,34 @@ BENCHMARK_CAPTURE(abm_benchmark, abm_benchmark_50k, 50000, {14159265u, 35897932u BENCHMARK_CAPTURE(abm_benchmark, abm_benchmark_100k, 100000, {38462643u, 38327950u})->Unit(benchmark::kMillisecond); BENCHMARK_CAPTURE(abm_benchmark, abm_benchmark_200k, 200000, {28841971u, 69399375u})->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_50k, 50000, 50000, + {14159265u, 35897932u}) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_100k, 100000, 100000, + {38462643u, 38327950u}) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_200k, 200000, 200000, + {28841971u, 69399375u}) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_50k_2, 50000, 100000, + {14159265u, 35897932u}) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_100k_2, 100000, 200000, + {38462643u, 38327950u}) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_200k_2, 200000, 400000, + {28841971u, 69399375u}) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_50k_3, 50000, 150000, + {14159265u, 35897932u}) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_100k_3, 100000, 300000, + {38462643u, 38327950u}) + ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(abm_inactive_persons_benchmark, abm_inactive_persons_benchmark_200k_3, 200000, 600000, + {28841971u, 69399375u}) + ->Unit(benchmark::kMillisecond); + BENCHMARK_MAIN(); diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 0d3b703ffe..897625f3f3 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -112,6 +112,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(abm_minimal_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) @@ -144,7 +148,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_minimal.cpp b/cpp/examples/abm_minimal.cpp index 33491dcb7b..9acc4e09e0 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -41,7 +41,7 @@ int main() world.parameters.get() = 4.; // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) - world.parameters.get() = false; + world.parameters.get() = false; world.parameters.get()[age_group_5_to_14] = true; // Set the age group the can go to work is AgeGroup(2) and AgeGroup(3) (i.e. 15-34 and 35-59) world.parameters.get().set_multiple({age_group_15_to_34, age_group_35_to_59}, true); @@ -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); @@ -138,7 +138,7 @@ int main() world.assign_location(id, hospital); world.assign_location(id, icu); //assign work/school to people depending on their age - if (person.get_age() == age_group_0_to_4) { + if (person.get_age() == age_group_5_to_14) { world.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..a107be850d --- /dev/null +++ b/cpp/examples/graph_abm.cpp @@ -0,0 +1,274 @@ +/* +* 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/world.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/mobility_rules.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 world 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{}; + auto t = sim.get_time(); + for (auto&& loc : sim.get_world().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_world().get_subpopulation(loc.get_id(), t, inf_state)}); + } + location_information.push_back(std::make_tuple(loc.get_world_id(), loc.get_type(), loc.get_id(), + sim.get_world().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 world1 = mio::abm::World(num_age_groups, 0); + + //Set infection parameters + world1.parameters.get() = 4.; + world1.parameters.get() = 2.; + world1.parameters.get() = 4.; + world1.parameters.get() = 5.; + world1.parameters.get() = 6.; + world1.parameters.get() = 8.; + world1.parameters.get() = 7.; + world1.parameters.get() = 10.; + world1.parameters.get() = 11.; + + //Age group 0 goes to school and age group 1 goes to work + world1.parameters.get()[age_group_children] = true; + world1.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); + + // Vector holding all persons for the graph simulation. This vector is copied to all worlds at the end. + std::vector persons; + + //Household groups for world 1 + auto single_hh_group_w1 = mio::abm::HouseholdGroup(); + single_hh_group_w1.add_households(single_hh, 5); + auto two_adult_hh_group_w1 = mio::abm::HouseholdGroup(); + two_adult_hh_group_w1.add_households(two_adult_hh, 3); + auto single_parent_hh_group_w1 = mio::abm::HouseholdGroup(); + single_parent_hh_group_w1.add_households(single_parent_hh, 5); + auto family_hh_group_w1 = mio::abm::HouseholdGroup(); + family_hh_group_w1.add_households(family_hh, 10); + add_household_group_to_world(world1, single_hh_group_w1); + add_household_group_to_world(world1, two_adult_hh_group_w1); + add_household_group_to_world(world1, single_hh_group_w1); + add_household_group_to_world(world1, family_hh_group_w1); + + //add persons from world 0 to vector + for (auto& person : world1.get_persons()) { + mio::abm::PersonId new_id{static_cast(persons.size())}; + persons.emplace_back(person, new_id); + } + + auto world2 = mio::abm::World(num_age_groups, 1); + + //Household groups for world 2 + auto single_hh_group_w2 = mio::abm::HouseholdGroup(); + single_hh_group_w2.add_households(single_hh, 6); + auto two_adult_hh_group_w2 = mio::abm::HouseholdGroup(); + two_adult_hh_group_w2.add_households(two_adult_hh, 2); + auto single_parent_hh_group_w2 = mio::abm::HouseholdGroup(); + single_parent_hh_group_w2.add_households(single_parent_hh, 10); + auto family_hh_group_w2 = mio::abm::HouseholdGroup(); + family_hh_group_w2.add_households(family_hh, 11); + add_household_group_to_world(world2, single_hh_group_w2); + add_household_group_to_world(world2, two_adult_hh_group_w2); + add_household_group_to_world(world2, single_hh_group_w2); + add_household_group_to_world(world2, family_hh_group_w2); + + //add persons from world 1 to vector + for (auto& person : world2.get_persons()) { + mio::abm::PersonId new_id{static_cast(persons.size())}; + persons.emplace_back(person, new_id); + } + + //Create locations for both worlds + //world 0 + auto event_w1 = world1.add_location(mio::abm::LocationType::SocialEvent); + world1.get_location(event_w1).get_infection_parameters().set(10); + auto hospital_w1 = world1.add_location(mio::abm::LocationType::Hospital); + world1.get_location(hospital_w1).get_infection_parameters().set(10); + auto icu_w1 = world1.add_location(mio::abm::LocationType::ICU); + world1.get_location(icu_w1).get_infection_parameters().set(5); + auto shop_w1 = world1.add_location(mio::abm::LocationType::BasicsShop); + world1.get_location(shop_w1).get_infection_parameters().set(20); + auto school_w1 = world1.add_location(mio::abm::LocationType::School); + world1.get_location(school_w1).get_infection_parameters().set(20); + auto work_w1 = world1.add_location(mio::abm::LocationType::Work); + world1.get_location(work_w1).get_infection_parameters().set(10); + //World 1 + auto event_w2 = world2.add_location(mio::abm::LocationType::SocialEvent); + world2.get_location(event_w2).get_infection_parameters().set(10); + auto hospital_w2 = world2.add_location(mio::abm::LocationType::Hospital); + world2.get_location(hospital_w2).get_infection_parameters().set(10); + auto icu_w2 = world2.add_location(mio::abm::LocationType::ICU); + world2.get_location(icu_w2).get_infection_parameters().set(5); + auto shop_w2 = world2.add_location(mio::abm::LocationType::BasicsShop); + world2.get_location(shop_w2).get_infection_parameters().set(20); + auto school_w2 = world2.add_location(mio::abm::LocationType::School); + world2.get_location(school_w2).get_infection_parameters().set(20); + auto work_w2 = world2.add_location(mio::abm::LocationType::Work); + world2.get_location(work_w2).get_infection_parameters().set(10); + + auto start_date = mio::abm::TimePoint(0); + auto end_date = mio::abm::TimePoint(0) + mio::abm::days(30); + std::vector params_e1; + std::vector params_e2; + + //Assign infection states and locations + std::vector infection_distribution{0.5, 0.3, 0.05, 0.05, 0.05, 0.05, 0.0, 0.0}; + for (auto& person : persons) { + mio::abm::InfectionState infection_state = mio::abm::InfectionState( + mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), infection_distribution)); + auto rng = mio::abm::PersonalRandomNumberGenerator(mio::thread_local_rng(), person); + if (infection_state != mio::abm::InfectionState::Susceptible) { + person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), + world1.parameters, start_date, infection_state)); + } + // Assign locations to persons from world 1 + if (person.get_assigned_location_world_id(mio::abm::LocationType::Home) == world1.get_id()) { + person.set_assigned_location(mio::abm::LocationType::SocialEvent, event_w1, world1.get_id()); + person.set_assigned_location(mio::abm::LocationType::BasicsShop, shop_w1, world1.get_id()); + person.set_assigned_location(mio::abm::LocationType::Hospital, hospital_w1, world1.get_id()); + person.set_assigned_location(mio::abm::LocationType::ICU, icu_w1, world1.get_id()); + if (person.get_age() == age_group_children) { + person.set_assigned_location(mio::abm::LocationType::School, school_w1, world1.get_id()); + } + if (person.get_age() == age_group_adults) { + //10% of adults in world 0 work in world 1 + size_t work_world = mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), + std::vector{0.9, 0.1}); + if (work_world == 1) { //person works in other world + person.set_assigned_location(mio::abm::LocationType::Work, work_w2, world2.get_id()); + //add person to edge parameters + params_e1.push_back(person.get_id().get()); + } + else { //person works in same world + person.set_assigned_location(mio::abm::LocationType::Work, work_w1, world1.get_id()); + } + } + } + else { + person.set_assigned_location(mio::abm::LocationType::SocialEvent, event_w2, world2.get_id()); + person.set_assigned_location(mio::abm::LocationType::BasicsShop, shop_w2, world2.get_id()); + person.set_assigned_location(mio::abm::LocationType::Hospital, hospital_w2, world2.get_id()); + person.set_assigned_location(mio::abm::LocationType::ICU, icu_w2, world2.get_id()); + if (person.get_age() == age_group_children) { + person.set_assigned_location(mio::abm::LocationType::School, school_w2, world2.get_id()); + } + if (person.get_age() == age_group_adults) { + //20% of adults in world 1 work in world 0 + size_t work_world = mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), + std::vector{0.2, 0.8}); + if (work_world == 0) { //person works in other world + person.set_assigned_location(mio::abm::LocationType::Work, work_w1, world1.get_id()); + //add person to edge parameters + params_e2.push_back(person.get_id().get()); + } + else { //person works in same world + person.set_assigned_location(mio::abm::LocationType::Work, work_w2, world2.get_id()); + } + } + } + } + + //copy persons to both worlds + world1.set_persons(persons); + world2.set_persons(persons); + + using HistoryType = mio::History; + mio::Graph, mio::ABMMobilityEdge> graph; + graph.add_node(world1.get_id(), HistoryType{}, start_date, std::move(world1)); + graph.add_node(world2.get_id(), HistoryType{}, start_date, std::move(world2)); + graph.add_edge(0, 1, params_e1, + std::vector::MobilityRuleType>{&mio::apply_commuting}); + graph.add_edge(1, 0, params_e2, + std::vector::MobilityRuleType>{&mio::apply_commuting}); + + auto sim = mio::make_abm_graph_sim(start_date, mio::abm::hours(12), std::move(graph)); + sim.advance(end_date); + + auto& log_n1 = std::get<0>(sim.get_graph().nodes()[0].property.get_history()).get_log(); + auto& log_n2 = std::get<0>(sim.get_graph().nodes()[1].property.get_history()).get_log(); + + return 0; +} \ No newline at end of file diff --git a/cpp/memilio/mobility/graph_simulation.h b/cpp/memilio/mobility/graph_simulation.h index 003ae08793..557dc1ffac 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 GraphSimulationBase::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 2cea9c59ad..a242e8060e 100644 --- a/cpp/memilio/mobility/metapopulation_mobility_instant.h +++ b/cpp/memilio/mobility/metapopulation_mobility_instant.h @@ -525,7 +525,9 @@ void apply_migration(FP t, FP dt, MigrationEdge& migrationEdge, SimulationNo * @{ */ template -GraphSimulation, MigrationEdge>> +GraphSimulation, MigrationEdge>, FP, FP, + void (*)(double, double, mio::MigrationEdge<>&, mio::SimulationNode&, mio::SimulationNode&), + void (*)(double, double, mio::SimulationNode&)> make_migration_sim(FP t0, FP dt, const Graph, MigrationEdge>& graph) { return make_graph_sim(t0, dt, graph, &evolve_model, @@ -534,7 +536,9 @@ make_migration_sim(FP t0, FP dt, const Graph, MigrationEdge< } template -GraphSimulation, MigrationEdge>> +GraphSimulation, MigrationEdge>, FP, FP, + void (*)(double, double, mio::MigrationEdge<>&, mio::SimulationNode&, mio::SimulationNode&), + void (*)(double, double, mio::SimulationNode&)> make_migration_sim(FP t0, FP dt, Graph, MigrationEdge>&& graph) { return make_graph_sim(t0, dt, std::move(graph), &evolve_model, diff --git a/cpp/models/abm/location.h b/cpp/models/abm/location.h index 921cca01a0..aa10ddb11a 100644 --- a/cpp/models/abm/location.h +++ b/cpp/models/abm/location.h @@ -280,6 +280,24 @@ class Location m_geographical_location = location; } + /** + * @brief Get the world id the location is in. Is only relevant for graph ABM or hybrid model. + * @return World id of the location + */ + int get_world_id() const + { + return m_world_id; + } + + /** + * @brief Set world id of the Location. Is only relevant for graph ABM or hybrid model. + * @param[in] world_id The world id of the location. + */ + void set_world_id(int world_id) + { + m_world_id = world_id; + } + private: LocationType m_type; ///< Type of the Location. LocationId m_id; ///< Unique identifier for the Location in the World owning it. @@ -288,6 +306,7 @@ class Location MaskType m_required_mask; ///< Least secure type of Mask that is needed to enter the Location. bool m_npi_active; ///< If true requires e.g. Mask%s to enter the Location. GeographicalLocation m_geographical_location; ///< Geographical location (longitude and latitude) of the Location. + int m_world_id = 0; ///< World id the location is in. Only used for ABM graph model or hybrid graph model. }; } // namespace abm diff --git a/cpp/models/abm/model_functions.cpp b/cpp/models/abm/model_functions.cpp index ae97a2b51f..ecf7790f97 100644 --- a/cpp/models/abm/model_functions.cpp +++ b/cpp/models/abm/model_functions.cpp @@ -133,7 +133,7 @@ bool migrate(Person& person, const Location& destination, const TransportMode mo })); // 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_world_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 6e5938cff3..24b68c6d55 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -31,10 +31,11 @@ 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_world_id, AgeGroup age, PersonId person_id) : m_location(location_id) , m_location_type(location_type) + , m_location_world_id(location_world_id) , m_assigned_locations((uint32_t)LocationType::Count, LocationId::invalid_id()) , m_quarantine_start(TimePoint(-(std::numeric_limits::max() / 2))) , m_age(age) @@ -46,6 +47,7 @@ Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, Loca , m_person_id(person_id) , m_cells{0} , m_last_transport_mode(TransportMode::Unknown) + , m_assigned_location_world_ids((int)LocationType::Count) { m_random_workgroup = UniformDistribution::get_instance()(rng); m_random_schoolgroup = UniformDistribution::get_instance()(rng); @@ -92,11 +94,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 world_id) { - m_location = id; - m_location_type = type; - m_time_at_location = TimeSpan(0); + m_location = id; + m_location_type = type; + m_location_world_id = world_id; + m_time_at_location = TimeSpan(0); } const Infection& Person::get_infection() const @@ -109,9 +112,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 world_id) { - m_assigned_locations[static_cast(type)] = id; + m_assigned_locations[static_cast(type)] = id; + m_assigned_location_world_ids[static_cast(type)] = world_id; } LocationId Person::get_assigned_location(LocationType type) const @@ -119,6 +123,11 @@ LocationId Person::get_assigned_location(LocationType type) const return m_assigned_locations[static_cast(type)]; } +int Person::get_assigned_location_world_id(LocationType type) const +{ + return m_assigned_location_world_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]; diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 7810eede7f..2cec136d55 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -53,8 +53,8 @@ class Person * @param[in] age The AgeGroup of the Person. * @param[in] person_id 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_world_id, AgeGroup age, PersonId person_id = PersonId::invalid_id()); explicit Person(const Person& other, PersonId id); @@ -129,11 +129,18 @@ class Person return m_location_type; } + int get_location_world_id() const + { + return m_location_world_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] world_id The world id of the new Location. */ - void set_location(LocationType type, LocationId id); + void set_location(LocationType type, LocationId id, int world_id); /** * @brief Get the time the Person has been at its current Location. @@ -172,8 +179,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] world_id The world id of the Location. */ - void set_assigned_location(LocationType type, LocationId id); + void set_assigned_location(LocationType type, LocationId id, int world_id); /** * @brief Returns the index of an assigned Location of the Person. @@ -192,6 +200,23 @@ class Person return m_assigned_locations; } + /** + * @brief Returns the world 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 world id of the assigned Location. + */ + int get_assigned_location_world_id(LocationType type) const; + + /** + * @brief Get the assigned locations' world ids of the Person. + * @return A vector with the world ids of the assigned locations of the Person + */ + const std::vector& get_assigned_location_world_ids() const + { + return m_assigned_location_world_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 @@ -264,6 +289,12 @@ class Person */ PersonId get_id() const; + /** + * @brief Set the PersonId of the Person. + * The PersonID should correspond to the index in m_persons in world. + */ + void set_id(PersonId id); + /** * @brief Get index of Cell%s of the Person. * @return A vector of all Cell indices the Person visits at the current Location. @@ -428,6 +459,7 @@ class Person private: LocationId m_location; ///< Current Location of the Person. LocationType m_location_type; ///< Type of the current Location. + int m_location_world_id; ///< World 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 Vaccination%s the Person has received. @@ -447,6 +479,8 @@ class Person std::vector m_cells; ///< Vector with all Cell%s the Person visits at its current Location. mio::abm::TransportMode m_last_transport_mode; ///< TransportMode the Person used to get to its current Location. Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator + std::vector + m_assigned_location_world_ids; ///< Vector with world ids of the assigned locations. Only used in graph abm. }; } // namespace abm diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index e3654b69b2..c9ed70e5bd 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -39,6 +39,7 @@ LocationId World::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_has_locations[size_t(type)] = true; + m_locations[id.get()].set_world_id(m_id); // mark caches for rebuild m_is_local_population_cache_valid = false; @@ -50,7 +51,7 @@ LocationId World::add_location(LocationType type, uint32_t num_cells) PersonId World::add_person(const LocationId id, AgeGroup age) { - return add_person(Person(m_rng, get_location(id).get_type(), id, age)); + return add_person(Person(m_rng, get_location(id).get_type(), id, m_id, age)); } PersonId World::add_person(Person&& person) @@ -62,8 +63,9 @@ PersonId World::add_person(Person&& person) PersonId new_id{static_cast(m_persons.size())}; m_persons.emplace_back(person, new_id); + m_activeness_statuses.push_back(true); auto& new_person = m_persons.back(); - new_person.set_assigned_location(LocationType::Cemetery, m_cemetery_id); + new_person.set_assigned_location(LocationType::Cemetery, m_cemetery_id, m_id); if (m_is_local_population_cache_valid) { ++m_local_population_cache[new_person.get_location().get()]; @@ -85,7 +87,9 @@ void World::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); + if (m_activeness_statuses[person_id]) { + interact(person_id, t, dt); + } } } @@ -94,47 +98,53 @@ void World::migration(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_migration_rule = [&](auto rule) -> bool { - //run migration rule and check if migration can actually happen - auto target_type = rule(personal_rng, person, t, dt, parameters); - const Location& target_location = get_location(find_location(target_type, person_id)); - const LocationId current_location = person.get_location(); - if (m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { - if (target_location.get_id() != current_location && - get_number_persons(target_location.get_id()) < target_location.get_capacity().persons) { - bool wears_mask = person.apply_mask_intervention(personal_rng, target_location); - if (wears_mask) { - migrate(person_id, target_location.get_id()); + if (m_activeness_statuses[person_id]) { + Person& person = m_persons[person_id]; + auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); + + auto try_migration_rule = [&](auto rule) -> bool { + //run migration rule and check if migration can actually happen + auto target_type = rule(personal_rng, person, t, dt, parameters); + if (person.get_assigned_location_world_id(target_type) == m_id) { + const Location& target_location = get_location(find_location(target_type, person_id)); + const LocationId current_location = person.get_location(); + if (m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + if (target_location.get_id() != current_location && + get_number_persons(target_location.get_id()) < target_location.get_capacity().persons) { + bool wears_mask = person.apply_mask_intervention(personal_rng, target_location); + if (wears_mask) { + migrate(person_id, target_location.get_id()); + } + return true; + } } - return true; } + return false; + }; + + //run migration rules one after the other if the corresponding location type exists + //shortcutting of bool operators ensures the rules stop after the first rule is applied + if (m_use_migration_rules) { + (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || + (has_locations({LocationType::School, LocationType::Home}) && try_migration_rule(&go_to_school)) || + (has_locations({LocationType::Work, LocationType::Home}) && try_migration_rule(&go_to_work)) || + (has_locations({LocationType::BasicsShop, LocationType::Home}) && + try_migration_rule(&go_to_shop)) || + (has_locations({LocationType::SocialEvent, LocationType::Home}) && + try_migration_rule(&go_to_event)) || + (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); + } + else { + //no daily routine migration, just infection related + (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || + (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || + (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || + (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || + (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); } - return false; - }; - - //run migration rules one after the other if the corresponding location type exists - //shortcutting of bool operators ensures the rules stop after the first rule is applied - if (m_use_migration_rules) { - (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || - (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || - (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || - (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || - (has_locations({LocationType::School, LocationType::Home}) && try_migration_rule(&go_to_school)) || - (has_locations({LocationType::Work, LocationType::Home}) && try_migration_rule(&go_to_work)) || - (has_locations({LocationType::BasicsShop, LocationType::Home}) && try_migration_rule(&go_to_shop)) || - (has_locations({LocationType::SocialEvent, LocationType::Home}) && try_migration_rule(&go_to_event)) || - (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); - } - else { - //no daily routine migration, just infection related - (has_locations({LocationType::Cemetery}) && try_migration_rule(&get_buried)) || - (has_locations({LocationType::Home}) && try_migration_rule(&return_home_when_recovered)) || - (has_locations({LocationType::Hospital}) && try_migration_rule(&go_to_hospital)) || - (has_locations({LocationType::ICU}) && try_migration_rule(&go_to_icu)) || - (has_locations({LocationType::Home}) && try_migration_rule(&go_to_quarantine)); } } @@ -176,7 +186,9 @@ void World::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_persons[i].get_location_world_id() == m_id) { + ++m_local_population_cache[m_persons[i].get_location().get()]; + } } // implicit taskloop barrier } // implicit single barrier } @@ -232,9 +244,11 @@ void World::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 (person.get_location_world_id() == m_id) { + mio::abm::add_exposure_contribution(m_air_exposure_rates_cache[location], + m_contact_exposure_rates_cache[location], person, + get_location(person.get_id()), t, dt); + } } // implicit taskloop barrier } // implicit single barrier } diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 853dff4960..0af2a7402d 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -59,8 +59,9 @@ class World * @brief Create a World. * @param[in] num_agegroups The number of AgeGroup%s in the simulated World. Must be less than MAX_NUM_AGE_GROUPS. */ - World(size_t num_agegroups) + World(size_t num_agegroups, int id = 0) : parameters(num_agegroups) + , m_id(id) , m_trip_list() , m_use_migration_rules(true) , m_cemetery_id(add_location(LocationType::Cemetery)) @@ -72,8 +73,9 @@ class World * @brief Create a World. * @param[in] params Initial simulation parameters. */ - World(const Parameters& params) + World(const Parameters& params, int world_id = 0) : parameters(params.get_num_groups()) + , m_id(world_id) , m_trip_list() , m_use_migration_rules(true) , m_cemetery_id(add_location(LocationType::Cemetery)) @@ -81,7 +83,7 @@ class World parameters = params; } - World(const World& other) + World(const World& other, int world_id = 0) : parameters(other.parameters) , m_local_population_cache() , m_air_exposure_rates_cache() @@ -89,8 +91,10 @@ class World , m_is_local_population_cache_valid(false) , m_are_exposure_caches_valid(false) , m_exposure_caches_need_rebuild(true) + , m_id(world_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) @@ -223,7 +227,7 @@ class World */ void assign_location(PersonId person, LocationId location) { - get_person(person).set_assigned_location(get_location(location).get_type(), location); + get_person(person).set_assigned_location(get_location(location).get_type(), location, m_id); } /** @@ -304,6 +308,24 @@ class World return m_rng; } + /** + * Get the world id. Is only relevant for graph abm or hybrid model. + * @return The world id + */ + int get_id() const + { + return m_id; + } + + /** + * Get activeness status of all persons in the world. + * @return Activeness vector + */ + std::vector& get_activeness_statuses() + { + return m_activeness_statuses; + } + /** * @brief Add a TestingScheme to the set of schemes that are checked for testing at all Locations that have * the LocationType. @@ -349,7 +371,8 @@ class World 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_world_id() == m_id && p.get_location() == location && + p.get_infection_state(t) == state; }); } @@ -447,6 +470,80 @@ class World } /** @} */ + /** + * @brief Flip activeness status of a person in the world. + * @param[in] person_id PersonId of Person whose activeness status is fipped. + */ + void change_activeness(PersonId person_id) + { + m_activeness_statuses[person_id.get()] = !m_activeness_statuses[person_id.get()]; + } + + void set_activeness(PersonId person_id) + { + m_activeness_statuses[person_id.get()] = false; + } + + /** + * @brief Copy the persons from another World to this World. + * If the persons are at a location in this world they are activated, otherwise they are deactivated. + * If necessary the person ids are changed such that they correspond to the index in this world's m_persons vector. + * @param[in] other The World the Person%s are copied from. + */ + void copy_persons_from_other_world(const World& other) + { + for (auto& p : other.get_persons()) { + if (p.get_id() != static_cast(m_persons.size())) { + mio::log_debug("In world.copy_persons_from_other_world: PersonId does not correspond to index in " + "m_persons vector. Person is copied with adapted Id"); + } + PersonId new_id{static_cast(m_persons.size())}; + m_persons.emplace_back(p, new_id); + if (p.get_location_world_id() == m_id) { + m_activeness_statuses.push_back(true); + } + else { + m_activeness_statuses.push_back(false); + } + } + } + + /** + * @brief Set the Person%s of the World. + * @param[in] persons The Person%s of the World. + */ + void set_persons(std::vector& persons) + { + m_is_local_population_cache_valid = false; + m_are_exposure_caches_valid = false; + //first clear old person vector and corresponding activeness vector + m_persons.clear(); + m_activeness_statuses.clear(); + for (auto& p : persons) { + if (p.get_id() != static_cast(m_persons.size())) { + mio::log_debug("In world.copy_persons_from_other_world: PersonId does not correspond to index in " + "m_persons vector. Person is copied with adapted Id"); + } + PersonId new_id{static_cast(m_persons.size())}; + m_persons.emplace_back(p, new_id); + if (p.get_location_world_id() == m_id) { + m_activeness_statuses.push_back(true); + } + else { + m_activeness_statuses.push_back(false); + } + } + } + + /** + * @brief Invalidate local population and exposure rate cache. + */ + void invalidate_cache() + { + m_are_exposure_caches_valid = false; + m_is_local_population_cache_valid = false; + } + private: /** * @brief Person%s interact at their Location and may become infected. @@ -484,8 +581,10 @@ class World bool m_are_exposure_caches_valid = false; bool m_exposure_caches_need_rebuild = true; + int m_id; ///< World 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 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. diff --git a/cpp/models/graph_abm/CMakeLists.txt b/cpp/models/graph_abm/CMakeLists.txt new file mode 100644 index 0000000000..866bc85b29 --- /dev/null +++ b/cpp/models/graph_abm/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(graph_abm + graph_abm_mobility.cpp + graph_abm_mobility.h + mobility_rules.cpp + mobility_rules.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..7acf3c2b64 --- /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/world.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..deef17d8f8 --- /dev/null +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -0,0 +1,316 @@ +/* +* 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 "memilio/mobility/graph_simulation.h" +#include "memilio/mobility/graph.h" +#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 evolve(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; +}; + +/** + * @brief Parameters influencing the mobility between two abm graph nodes. + */ +class ABMMobilityParameters +{ + +public: + using MobilityRuleType = abm::LocationType (*)(const abm::Person&, abm::TimePoint, const abm::Parameters&); + + /** + * Constructor for initializing commuting persons + * @param commuting_persons Vector holding commuting persons' ids + */ + ABMMobilityParameters(const std::vector& commuting_persons, + const std::vector& mobility_rules) + : m_commuting_persons(commuting_persons) + , m_mobility_rules(mobility_rules) + { + } + + /** + * Equality comparison operators + */ + bool operator==(const ABMMobilityParameters& other) const + { + return m_commuting_persons == other.m_commuting_persons; + } + bool operator!=(const ABMMobilityParameters& other) const + { + return m_commuting_persons != other.m_commuting_persons; + } + + /** + * Get/Set the commuting persons vector. + * The vector represents the persons (by their id) that commute from one node to another + * according to mobility rules. + */ + const std::vector& get_commuting_persons() const + { + return m_commuting_persons; + } + + std::vector& get_commuting_persons() + { + return m_commuting_persons; + } + /** + * @param[in] commuting_persons Vector with commuting person ids. + */ + void set_commuting_persons(const std::vector& commuting_persons) + { + m_commuting_persons = commuting_persons; + } + + /** + * Get/ the mobility rules. + * The rules are applied to the persons in m_commuting_persons every time exchange betwen two nodes is triggered. + */ + const std::vector& get_mobility_rules() const + { + return m_mobility_rules; + } + std::vector& get_mobility_rules() + { + return m_mobility_rules; + } + /** + * @brief Add mobility rule to member vector. + * @param[in] mobility_rule Rule to be added for mobility between nodes. + */ + void add_mobility_rule(const MobilityRuleType& mobility_rule) + { + m_mobility_rules.push_back(mobility_rule); + } + +private: + std::vector m_commuting_persons; ///< Person ids that are commuting via an edge + std::vector m_mobility_rules; ///< Rules for moving persons from one node to another +}; + +/** + * Represents the mobility between two nodes. + */ +template +class ABMMobilityEdge +{ + +public: + using MobilityRuleType = abm::LocationType (*)(const abm::Person&, abm::TimePoint, const abm::Parameters&); + /** + * Creates edge with mobility parameters + * @param params mobility parameters including people commuting via the edge and mobility rules + */ + ABMMobilityEdge(const ABMMobilityParameters& params) + : m_parameters(params) + { + } + + ABMMobilityEdge(const std::vector& commuting_persons, + const std::vector& mobility_rules = {}) + : m_parameters(commuting_persons, mobility_rules) + { + } + + /** + * @brief Get mobility paramters. + */ + const ABMMobilityParameters& get_parameters() const + { + return m_parameters; + } + + /** + * @brief Exchanges persons via the edge. + * Commuters are given by the ABMMobilityParameters and exchanged via mobility rules also given ABMMobilityParameters. + * @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) + { + // iterate over all persons that could commute via the edge + for (auto p : m_parameters.get_commuting_persons()) { + auto& person_n1 = node_from.get_simulation().get_world().get_person(mio::abm::PersonId(p)); + auto& person_n2 = node_to.get_simulation().get_world().get_person(mio::abm::PersonId(p)); + auto& params = node_from.get_simulation().get_world().parameters; + // as all nodes have all person it doesn't matter which node's persons we take here + auto current_location_type = person_n1.get_location_type(); + auto current_id = person_n1.get_location(); + auto current_world_id = person_n1.get_location_world_id(); + for (auto& rule : m_parameters.get_mobility_rules()) { + auto target_type = rule(person_n1, t, params); + auto target_world_id = person_n1.get_assigned_location_world_id(target_type); + auto target_id = person_n1.get_assigned_location(target_type); + auto& target_world = (target_world_id == node_from.get_simulation().get_world().get_id()) + ? node_from.get_simulation().get_world() + : node_to.get_simulation().get_world(); + auto& target_location = target_world.get_location(target_id); + assert((node_from.get_simulation().get_world().get_id() == target_location.get_world_id() || + node_to.get_simulation().get_world().get_id() == target_location.get_world_id()) && + "Wrong graph edge. Target location is no edge node."); + if (target_type == current_location_type && + (target_id != current_id || target_world_id != current_world_id)) { + mio::log_error("Person with index {} has two assigned locations of the same type.", + person_n1.get_id().get()); + } + if (target_type != current_location_type && + target_world.get_number_persons(target_id) < target_location.get_capacity().persons) { + //change person's location in all nodes + mio::abm::migrate(person_n1, target_location); + mio::abm::migrate(person_n2, target_location); + // invalidate both worlds' cache + node_to.get_simulation().get_world().invalidate_cache(); + node_from.get_simulation().get_world().invalidate_cache(); + if (target_world_id != current_world_id) { + // change activeness status for commuted person + node_to.get_simulation().get_world().change_activeness(p); + node_from.get_simulation().get_world().change_activeness(p); + } + // only one mobility rule per person can be applied + break; + } + } + } + } + +private: + ABMMobilityParameters m_parameters; ///< Mobility parameters +}; + +/** + * @brief Edge functor for abm graph simulation. + * @see 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::evolve + * @param[in] t Time point the functor is applied. + * @param[in] dt Time interval the node is evolved. + * @param[in] node ABMSimulationNode to which the functor is applied. + */ +template +void evolve_model(abm::TimePoint t, abm::TimeSpan dt, ABMSimulationNode& node) +{ + node.evolve(t, dt); +} + +/** + * @brief Creates an abm graph simulation. + * Every dt time step for each edge the persons given in ABMMobilityParameters move from one node to the other + * according to mobility rules also given by ABMMobilityParameters. + * @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), &evolve_model, &apply_mobility); +} + +} // namespace mio + +#endif // MIO_ABM_GRAPH_MOBILITY_H \ No newline at end of file diff --git a/cpp/models/graph_abm/mobility_rules.cpp b/cpp/models/graph_abm/mobility_rules.cpp new file mode 100644 index 0000000000..cab23286c8 --- /dev/null +++ b/cpp/models/graph_abm/mobility_rules.cpp @@ -0,0 +1,63 @@ +/* +* 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/mobility_rules.h" +#include "abm/location.h" +#include "abm/infection_state.h" + +namespace mio +{ + +abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, const abm::Parameters& params) +{ + abm::LocationType current_loc = person.get_location_type(); + + if (current_loc == abm::LocationType::Home && params.get()[person.get_age()] && + t < params.get() && t.day_of_week() < 5 && person.goes_to_work(t, params) && + !person.is_in_quarantine(t, params)) { + return abm::LocationType::Work; + } + + //person is at hospital in non-home world + if (current_loc == abm::LocationType::Hospital && + person.get_location_world_id() != person.get_assigned_location_world_id(abm::LocationType::Home)) { + //if person is still infected it stays at hospital + if (person.is_infected(t)) { + return current_loc; + } + //if person has recovered it goes home + else if (person.get_infection_state(t) == abm::InfectionState::Recovered) { + return abm::LocationType::Home; + } + //if person is dead it is sent to cementary + else { + return abm::LocationType::Cemetery; + } + } + //person is at location in Home world (and should not go to work) it stays at that location + if (person.get_location_world_id() == person.get_assigned_location_world_id(abm::LocationType::Home)) { + return current_loc; + } + + // agents are sent home or to work (if none of the above cases occurs) every time this function is called + // i.e. if it is called too often they will be sent to work multiple times + return abm::LocationType::Home; +} +} // namespace mio \ No newline at end of file diff --git a/cpp/models/graph_abm/mobility_rules.h b/cpp/models/graph_abm/mobility_rules.h new file mode 100644 index 0000000000..68a226ad8d --- /dev/null +++ b/cpp/models/graph_abm/mobility_rules.h @@ -0,0 +1,43 @@ +/* +* 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_RULES_H +#define MIO_ABM_GRAPH_MOBILITY_RULES_H + +#include "abm/location_type.h" +#include "abm/time.h" +#include "abm/parameters.h" +#include "abm/person.h" + +namespace mio +{ + +/** + * @brief Once a day commuters go to work in another node. + * @param[in] person Person the rule is applies to + * @param[in] t Current time point + * @param[in] params Parameters of person's Home world + * @return LocationType the person is going to + */ +abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, const abm::Parameters& params); + +} // namespace mio + +#endif //MIO_ABM_GRAPH_MOBILITY_RULES_H \ No newline at end of file diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 4e6c389fe8..4620468e6d 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -72,27 +72,28 @@ set(TESTSOURCES test_lct_secir.cpp test_lct_initializer_flows.cpp test_ad.cpp + test_graph_abm.cpp ) if(MEMILIO_HAS_JSONCPP) -set(TESTSOURCES ${TESTSOURCES} -test_json_serializer.cpp -test_epi_data_io.cpp -test_lct_parameters_io.cpp -test_ide_parameters_io.cpp -) + set(TESTSOURCES ${TESTSOURCES} + test_json_serializer.cpp + test_epi_data_io.cpp + test_lct_parameters_io.cpp + test_ide_parameters_io.cpp + ) endif() if(MEMILIO_HAS_JSONCPP AND MEMILIO_HAS_HDF5) -set(TESTSOURCES ${TESTSOURCES} -test_save_parameters.cpp -test_save_results.cpp -) + set(TESTSOURCES ${TESTSOURCES} + test_save_parameters.cpp + test_save_results.cpp + ) 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_seair ide_seir ide_secir lct_secir abm gtest_main AD::AD) +target_link_libraries(memilio-test PRIVATE memilio ode_secir ode_seir ode_secirvvs ode_seair ide_seir ide_secir lct_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 85f9edf765..58298eb942 100644 --- a/cpp/tests/abm_helpers.cpp +++ b/cpp/tests/abm_helpers.cpp @@ -27,7 +27,7 @@ mio::abm::Person make_test_person(mio::abm::Location& location, mio::AgeGroup ag { assert(age.get() < params.get_num_groups()); auto rng = mio::RandomNumberGenerator(); - 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_world_id(), age); if (infection_state != mio::abm::InfectionState::Susceptible) { auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); p.add_new_infection( diff --git a/cpp/tests/test_abm_infection.cpp b/cpp/tests/test_abm_infection.cpp index 3ee2369000..1121e05b64 100644 --- a/cpp/tests/test_abm_infection.cpp +++ b/cpp/tests/test_abm_infection.cpp @@ -177,7 +177,8 @@ TEST(TestInfection, getPersonalProtectiveFactor) auto rng = mio::RandomNumberGenerator(); auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_15_to_34); + auto person = + mio::abm::Person(rng, location.get_type(), location.get_id(), location.get_world_id(), age_group_15_to_34); person.add_new_vaccination(mio::abm::ExposureType::GenericVaccine, mio::abm::TimePoint(0)); auto latest_protection = person.get_latest_protection(); diff --git a/cpp/tests/test_abm_location.cpp b/cpp/tests/test_abm_location.cpp index 2e95746ca9..b0618cceb2 100644 --- a/cpp/tests/test_abm_location.cpp +++ b/cpp/tests/test_abm_location.cpp @@ -74,10 +74,10 @@ TEST(TestLocation, reachCapacity) auto p1 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::InfectedNoSymptoms); auto p2 = add_test_person(world, home_id, age_group_5_to_14, mio::abm::InfectionState::Susceptible); - world.get_person(p1).set_assigned_location(mio::abm::LocationType::School, school_id); - world.get_person(p2).set_assigned_location(mio::abm::LocationType::School, school_id); - world.get_person(p1).set_assigned_location(mio::abm::LocationType::Home, home_id); - world.get_person(p2).set_assigned_location(mio::abm::LocationType::Home, home_id); + world.get_person(p1).set_assigned_location(mio::abm::LocationType::School, school_id, world.get_id()); + world.get_person(p2).set_assigned_location(mio::abm::LocationType::School, school_id, world.get_id()); + world.get_person(p1).set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + world.get_person(p2).set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); world.get_location(school_id).set_capacity(1, 66); diff --git a/cpp/tests/test_abm_lockdown_rules.cpp b/cpp/tests/test_abm_lockdown_rules.cpp index fff125d1e4..2c4836c9f9 100644 --- a/cpp/tests/test_abm_lockdown_rules.cpp +++ b/cpp/tests/test_abm_lockdown_rules.cpp @@ -47,12 +47,12 @@ TEST(TestLockdownRules, school_closure) .WillOnce(testing::Return(0.2)) .WillRepeatedly(testing::Return(1.0)); - auto p1 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); - p1.set_assigned_location(home.get_type(), home.get_id()); - p1.set_assigned_location(school.get_type(), school.get_id()); - auto p2 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); - p2.set_assigned_location(home.get_type(), home.get_id()); - p2.set_assigned_location(school.get_type(), school.get_id()); + auto p1 = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + p1.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p1.set_assigned_location(school.get_type(), school.get_id(), school.get_world_id()); + auto p2 = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + p2.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p2.set_assigned_location(school.get_type(), school.get_id(), school.get_world_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; @@ -88,9 +88,9 @@ TEST(TestLockdownRules, school_opening) .WillOnce(testing::Return(0.6)) .WillOnce(testing::Return(0.6)) .WillRepeatedly(testing::Return(1.0)); - auto p = mio::abm::Person(rng, home.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(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p.set_assigned_location(school.get_type(), school.get_id(), school.get_world_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; @@ -137,12 +137,12 @@ TEST(TestLockdownRules, home_office) .WillOnce(testing::Return(0.7)) .WillRepeatedly(testing::Return(1.0)); - auto person1 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); - auto person2 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); - person1.set_assigned_location(home.get_type(), home.get_id()); - person1.set_assigned_location(work.get_type(), work.get_id()); - person2.set_assigned_location(home.get_type(), home.get_id()); - person2.set_assigned_location(work.get_type(), work.get_id()); + auto person1 = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); + auto person2 = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); + person1.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + person1.set_assigned_location(work.get_type(), work.get_id(), work.get_world_id()); + person2.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + person2.set_assigned_location(work.get_type(), work.get_id(), work.get_world_id()); auto p1_rng = mio::abm::PersonalRandomNumberGenerator(rng, person1); ASSERT_EQ(mio::abm::go_to_work(p1_rng, person1, t_morning, dt, params), mio::abm::LocationType::Work); @@ -170,9 +170,9 @@ TEST(TestLockdownRules, no_home_office) .WillOnce(testing::Return(0.7)) .WillRepeatedly(testing::Return(1.0)); - auto p = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); - p.set_assigned_location(home.get_type(), home.get_id()); - p.set_assigned_location(work.get_type(), work.get_id()); + auto p = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p.set_assigned_location(work.get_type(), work.get_id(), work.get_world_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; @@ -198,9 +198,9 @@ TEST(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(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(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p.set_assigned_location(event.get_type(), event.get_id(), event.get_world_id()); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); mio::abm::close_social_events(t, 1, params); @@ -219,9 +219,9 @@ TEST(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(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(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + p.set_assigned_location(event.get_type(), event.get_id(), event.get_world_id()); + p.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); mio::abm::close_social_events(t_closing, 1, params); diff --git a/cpp/tests/test_abm_masks.cpp b/cpp/tests/test_abm_masks.cpp index 5266d34f7a..0f707bfc5e 100644 --- a/cpp/tests/test_abm_masks.cpp +++ b/cpp/tests/test_abm_masks.cpp @@ -65,12 +65,12 @@ TEST(TestMasks, maskProtection) //setup location with some chance of exposure auto t = mio::abm::TimePoint(0); mio::abm::Location infection_location(mio::abm::LocationType::School, 0, num_age_groups); - auto susc_person1 = - mio::abm::Person(rng, infection_location.get_type(), infection_location.get_id(), age_group_15_to_34); - auto susc_person2 = - mio::abm::Person(rng, infection_location.get_type(), infection_location.get_id(), age_group_15_to_34); - auto infected1 = make_test_person(infection_location, age_group_15_to_34, - mio::abm::InfectionState::InfectedSymptoms, t, params); // infected 7 days prior + auto susc_person1 = mio::abm::Person(rng, infection_location.get_type(), infection_location.get_id(), + infection_location.get_world_id(), age_group_15_to_34); + auto susc_person2 = mio::abm::Person(rng, infection_location.get_type(), infection_location.get_id(), + infection_location.get_world_id(), age_group_15_to_34); + auto infected1 = make_test_person(infection_location, age_group_15_to_34, + mio::abm::InfectionState::InfectedSymptoms, t, params); // infected 7 days prior //cache precomputed results auto dt = mio::abm::days(1); diff --git a/cpp/tests/test_abm_migration_rules.cpp b/cpp/tests/test_abm_migration_rules.cpp index 592897359d..48bdb22ea3 100644 --- a/cpp/tests/test_abm_migration_rules.cpp +++ b/cpp/tests/test_abm_migration_rules.cpp @@ -28,7 +28,7 @@ TEST(TestMigrationRules, random_migration) int t = 0, dt = 1; auto rng = mio::RandomNumberGenerator(); auto default_type = mio::abm::LocationType::Cemetery; - auto person = mio::abm::Person(rng, default_type, 0, age_group_15_to_34); + auto person = mio::abm::Person(rng, default_type, 0, 0, age_group_15_to_34); auto p_rng = mio::abm::PersonalRandomNumberGenerator(rng, person); auto params = mio::abm::Parameters(num_age_groups); @@ -76,8 +76,8 @@ TEST(TestMigrationRules, student_goes_to_school) .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_child = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); - auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto p_child = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_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); @@ -119,9 +119,11 @@ TEST(TestMigrationRules, students_go_to_school_in_different_times) .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_child_goes_to_school_at_6 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto p_child_goes_to_school_at_6 = + mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_6); - auto p_child_goes_to_school_at_8 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto p_child_goes_to_school_at_8 = + mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); auto rng_child_goes_to_school_at_8 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_8); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); @@ -175,9 +177,11 @@ TEST(TestMigrationRules, students_go_to_school_in_different_times_with_smaller_t .WillRepeatedly(testing::Return(1.0)); mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); - auto p_child_goes_to_school_at_6 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_6); - auto p_child_goes_to_school_at_8_30 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_5_to_14); + auto p_child_goes_to_school_at_6 = + mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); + auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_6); + auto p_child_goes_to_school_at_8_30 = + mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_5_to_14); auto rng_child_goes_to_school_at_8_30 = mio::abm::PersonalRandomNumberGenerator(rng, p_child_goes_to_school_at_8_30); @@ -211,7 +215,7 @@ TEST(TestMigrationRules, school_return) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); - auto p_child = mio::abm::Person(rng, school.get_type(), school.get_id(), age_group_5_to_14); + auto p_child = mio::abm::Person(rng, school.get_type(), school.get_id(), school.get_world_id(), age_group_5_to_14); auto rng_child = mio::abm::PersonalRandomNumberGenerator(rng, p_child); auto t = mio::abm::TimePoint(0) + mio::abm::hours(15); @@ -238,9 +242,9 @@ TEST(TestMigrationRules, worker_goes_to_work) .WillOnce(testing::Return(0.)) .WillRepeatedly(testing::Return(1.0)); - auto p_retiree = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto p_retiree = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_60_to_79); auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(rng, p_retiree); - auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); auto rng_adult = mio::abm::PersonalRandomNumberGenerator(rng, p_adult); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(8); @@ -278,9 +282,9 @@ TEST(TestMigrationRules, worker_goes_to_work_with_non_dividable_timespan) .WillOnce(testing::Return(0.)) .WillRepeatedly(testing::Return(1.0)); - auto p_retiree = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto p_retiree = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_60_to_79); auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(rng, p_retiree); - auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto p_adult = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); auto rng_adult = mio::abm::PersonalRandomNumberGenerator(rng, p_adult); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(8); @@ -319,9 +323,11 @@ TEST(TestMigrationRules, workers_go_to_work_in_different_times) .WillRepeatedly(testing::Return(1.0)); - auto p_adult_goes_to_work_at_6 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto p_adult_goes_to_work_at_6 = + mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); auto rng_adult_goes_to_work_at_6 = mio::abm::PersonalRandomNumberGenerator(rng, p_adult_goes_to_work_at_6); - auto p_adult_goes_to_work_at_8 = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_15_to_34); + auto p_adult_goes_to_work_at_8 = + mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_15_to_34); auto rng_adult_goes_to_work_at_8 = mio::abm::PersonalRandomNumberGenerator(rng, p_adult_goes_to_work_at_8); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); @@ -355,7 +361,7 @@ TEST(TestMigrationRules, work_return) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); - auto p_adult = mio::abm::Person(rng, work.get_type(), work.get_id(), age_group_35_to_59); + auto p_adult = mio::abm::Person(rng, work.get_type(), work.get_id(), work.get_world_id(), age_group_35_to_59); auto rng_adult = mio::abm::PersonalRandomNumberGenerator(rng, p_adult); auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); auto dt = mio::abm::hours(1); @@ -423,7 +429,7 @@ TEST(TestMigrationRules, go_shopping) auto p_hosp = make_test_person(hospital, age_group_0_to_4, mio::abm::InfectionState::InfectedSymptoms, t_weekday); auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(rng, p_hosp); - auto p_home = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto p_home = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_60_to_79); auto rng_home = mio::abm::PersonalRandomNumberGenerator(rng, p_home); EXPECT_EQ(mio::abm::go_to_shop(rng_hosp, p_hosp, t_weekday, dt, mio::abm::Parameters(num_age_groups)), @@ -461,10 +467,10 @@ TEST(TestMigrationRules, go_event) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); - auto p_work = mio::abm::Person(rng, work.get_type(), work.get_id(), age_group_35_to_59); + auto p_work = mio::abm::Person(rng, work.get_type(), work.get_id(), work.get_world_id(), age_group_35_to_59); auto rng_work = mio::abm::PersonalRandomNumberGenerator(rng, p_work); mio::abm::Location home(mio::abm::LocationType::Home, 1, num_age_groups); - auto p_home = mio::abm::Person(rng, home.get_type(), home.get_id(), age_group_60_to_79); + auto p_home = mio::abm::Person(rng, home.get_type(), home.get_id(), home.get_world_id(), age_group_60_to_79); auto rng_home = mio::abm::PersonalRandomNumberGenerator(rng, p_home); auto t_weekday = mio::abm::TimePoint(0) + mio::abm::days(4) + mio::abm::hours(20); @@ -497,7 +503,8 @@ TEST(TestMigrationRules, 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); - auto p = mio::abm::Person(rng, social_event.get_type(), social_event.get_id(), age_group_15_to_34); + auto p = mio::abm::Person(rng, social_event.get_type(), social_event.get_id(), social_event.get_world_id(), + age_group_15_to_34); auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); p.add_time_at_location(dt); diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index 625c3080fb..6ddffca93e 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -33,8 +33,9 @@ TEST(TestPerson, init) auto rng = mio::RandomNumberGenerator(); mio::abm::Location location(mio::abm::LocationType::Work, 7, num_age_groups); - auto t = mio::abm::TimePoint(0); - auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_60_to_79); + auto t = mio::abm::TimePoint(0); + auto person = + mio::abm::Person(rng, location.get_type(), location.get_id(), location.get_world_id(), age_group_60_to_79); EXPECT_EQ(person.get_infection_state(t), mio::abm::InfectionState::Susceptible); EXPECT_EQ(person.get_location(), location.get_id()); @@ -81,11 +82,12 @@ TEST(TestPerson, setGetAssignedLocation) { auto rng = mio::RandomNumberGenerator(); mio::abm::Location location(mio::abm::LocationType::Work, 2, num_age_groups); - auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_35_to_59); - person.set_assigned_location(location.get_type(), location.get_id()); + auto person = + mio::abm::Person(rng, location.get_type(), location.get_id(), location.get_world_id(), age_group_35_to_59); + person.set_assigned_location(location.get_type(), location.get_id(), location.get_world_id()); EXPECT_EQ(person.get_assigned_location(mio::abm::LocationType::Work), mio::abm::LocationId(2)); - 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)); } @@ -142,7 +144,7 @@ TEST(TestPerson, get_tested) mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); auto infected = make_test_person(loc, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); auto rng_infected = mio::abm::PersonalRandomNumberGenerator(rng, infected); - auto susceptible = mio::abm::Person(rng, loc.get_type(), loc.get_id(), age_group_15_to_34); + auto susceptible = mio::abm::Person(rng, loc.get_type(), loc.get_id(), loc.get_world_id(), age_group_15_to_34); auto rng_suscetible = mio::abm::PersonalRandomNumberGenerator(rng, susceptible); auto pcr_parameters = params.get()[mio::abm::TestType::PCR]; @@ -204,7 +206,7 @@ TEST(TestPerson, interact) auto infection_parameters = mio::abm::Parameters(num_age_groups); mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); mio::abm::TimePoint t(0); - auto person = mio::abm::Person(rng, loc.get_type(), loc.get_id(), age_group_15_to_34); + auto person = mio::abm::Person(rng, loc.get_type(), loc.get_id(), loc.get_world_id(), age_group_15_to_34); auto rng_person = mio::abm::PersonalRandomNumberGenerator(rng, person); auto dt = mio::abm::seconds(8640); //0.1 days interact_testing(rng_person, person, loc, {person}, t, dt, infection_parameters); @@ -287,9 +289,10 @@ TEST(TestPerson, getMaskProtectiveFactor) TEST(TestPerson, getLatestProtection) { - auto rng = mio::RandomNumberGenerator(); - auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); - auto person = mio::abm::Person(rng, location.get_type(), location.get_id(), age_group_15_to_34); + auto rng = mio::RandomNumberGenerator(); + auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); + auto person = + mio::abm::Person(rng, location.get_type(), location.get_id(), location.get_world_id(), age_group_15_to_34); auto prng = mio::abm::PersonalRandomNumberGenerator(rng, person); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); @@ -310,7 +313,7 @@ TEST(TestPerson, getLatestProtection) TEST(Person, rng) { auto rng = mio::RandomNumberGenerator(); - auto p = mio::abm::Person(rng, mio::abm::LocationType::Home, 0, age_group_35_to_59, mio::abm::PersonId(13)); + auto p = mio::abm::Person(rng, mio::abm::LocationType::Home, 0, 0, age_group_35_to_59, mio::abm::PersonId(13)); EXPECT_EQ(p.get_rng_counter(), mio::Counter(0)); diff --git a/cpp/tests/test_abm_world.cpp b/cpp/tests/test_abm_world.cpp index 3ab8a7c3e7..51783964c0 100644 --- a/cpp/tests/test_abm_world.cpp +++ b/cpp/tests/test_abm_world.cpp @@ -106,9 +106,9 @@ TEST(TestWorld, findLocation) auto person_id = add_test_person(world, home_id); auto& person = world.get_person(person_id); - person.set_assigned_location(mio::abm::LocationType::Home, home_id); - person.set_assigned_location(mio::abm::LocationType::Work, work_id); - person.set_assigned_location(mio::abm::LocationType::School, school_id); + person.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + person.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + person.set_assigned_location(mio::abm::LocationType::School, school_id, world.get_id()); EXPECT_EQ(world.find_location(mio::abm::LocationType::Work, person_id), work_id); EXPECT_EQ(world.find_location(mio::abm::LocationType::School, person_id), school_id); @@ -154,9 +154,9 @@ TEST(TestWorld, evolveStateTransition) auto& p2 = world.get_persons()[1]; auto& p3 = world.get_persons()[2]; - p1.set_assigned_location(mio::abm::LocationType::School, location1); - p2.set_assigned_location(mio::abm::LocationType::School, location1); - p3.set_assigned_location(mio::abm::LocationType::Work, location2); + p1.set_assigned_location(mio::abm::LocationType::School, location1, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::School, location1, world.get_id()); + p3.set_assigned_location(mio::abm::LocationType::Work, location2, world.get_id()); //setup mock so p2 becomes infected ScopedMockDistribution>>> @@ -213,12 +213,12 @@ TEST(TestWorld, evolveMigration) auto& p1 = world.get_person(pid1); auto& p2 = world.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, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::School, school_id, world.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); ScopedMockDistribution>>> mock_exponential_dist; @@ -280,19 +280,19 @@ TEST(TestWorld, evolveMigration) auto& p4 = world.get_person(pid4); auto& p5 = world.get_person(pid5); - p1.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id); - p2.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id); - p1.set_assigned_location(mio::abm::LocationType::Work, work_id); - p2.set_assigned_location(mio::abm::LocationType::Work, work_id); - p1.set_assigned_location(mio::abm::LocationType::Home, home_id); - p2.set_assigned_location(mio::abm::LocationType::Home, home_id); - p3.set_assigned_location(mio::abm::LocationType::Home, home_id); - p4.set_assigned_location(mio::abm::LocationType::Home, home_id); - p3.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id); - p4.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id); - p5.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id); - p5.set_assigned_location(mio::abm::LocationType::Work, work_id); - p5.set_assigned_location(mio::abm::LocationType::Home, home_id); + p1.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id, world.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + p1.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + p2.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + p3.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + p4.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + p3.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, world.get_id()); + p4.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, world.get_id()); + p5.set_assigned_location(mio::abm::LocationType::SocialEvent, event_id, world.get_id()); + p5.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + p5.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); mio::abm::TripList& data = world.get_trip_list(); mio::abm::Trip trip1(p1.get_id(), mio::abm::TimePoint(0) + mio::abm::hours(9), work_id, home_id); @@ -387,12 +387,12 @@ TEST(TestWorld, evolveMigration) auto& p_dead = world.get_persons()[0]; auto& p_severe = world.get_persons()[1]; - p_dead.set_assigned_location(mio::abm::LocationType::ICU, icu_id); - p_dead.set_assigned_location(mio::abm::LocationType::Work, work_id); - p_dead.set_assigned_location(mio::abm::LocationType::Home, home_id); - p_severe.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id); - p_severe.set_assigned_location(mio::abm::LocationType::ICU, icu_id); - p_severe.set_assigned_location(mio::abm::LocationType::Home, home_id); + p_dead.set_assigned_location(mio::abm::LocationType::ICU, icu_id, world.get_id()); + p_dead.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); + p_dead.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::Hospital, hospital_id, world.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::ICU, icu_id, world.get_id()); + p_severe.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); // Add trip to see if a dead person can move outside of cemetery by scheduled mio::abm::TripList& trip_list = world.get_trip_list(); @@ -436,8 +436,8 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) add_test_person(world, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, current_time); auto& person = world.get_person(pid); auto rng_person = mio::abm::PersonalRandomNumberGenerator(rng, person); - person.set_assigned_location(mio::abm::LocationType::Home, home_id); - person.set_assigned_location(mio::abm::LocationType::Work, work_id); + person.set_assigned_location(mio::abm::LocationType::Home, home_id, world.get_id()); + person.set_assigned_location(mio::abm::LocationType::Work, work_id, world.get_id()); auto testing_criteria = mio::abm::TestingCriteria(); testing_criteria.add_infection_state(mio::abm::InfectionState::InfectedSymptoms); @@ -447,10 +447,10 @@ TEST(TestWorldTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) const auto start_date = mio::abm::TimePoint(20); const auto end_date = mio::abm::TimePoint(60 * 60 * 24 * 3); const auto probability = 1.0; - const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99}; + const auto test_params_pcr = mio::abm::TestParameters{0.9, 0.99}; - auto testing_scheme = - mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, test_params_pcr, probability); + auto testing_scheme = mio::abm::TestingScheme(testing_criteria, testing_frequency, start_date, end_date, + test_params_pcr, probability); world.get_testing_strategy().add_testing_scheme(mio::abm::LocationType::Work, testing_scheme); ASSERT_EQ(world.get_testing_strategy().run_strategy(rng_person, person, work, current_time), diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp new file mode 100644 index 0000000000..8d26e21648 --- /dev/null +++ b/cpp/tests/test_graph_abm.cpp @@ -0,0 +1,135 @@ +/* +* 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/world.h" +#include "abm/location_type.h" +#include "abm/time.h" +#include "graph_abm/graph_abm_mobility.h" +#include "graph_abm/mobility_rules.h" +#include "memilio/epidemiology/age_group.h" +#include +#include + +TEST(TestGraphAbm, test_activessness) +{ + auto world = mio::abm::World(size_t(1)); + world.parameters.get()[mio::AgeGroup(0)] = true; + auto work_id = world.add_location(mio::abm::LocationType::Work); + auto home_id = world.add_location(mio::abm::LocationType::Home); + auto& home = world.get_location(home_id); + auto& work = world.get_location(work_id); + auto p1_id = world.add_person(home_id, mio::AgeGroup(0)); + auto p2_id = world.add_person(home_id, mio::AgeGroup(0)); + auto& p1 = world.get_person(p1_id); + auto& p2 = world.get_person(p2_id); + p1.set_assigned_location(work.get_type(), work.get_id(), work.get_world_id()); + p2.set_assigned_location(work.get_type(), work.get_id(), work.get_world_id()); + p1.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p2.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + + EXPECT_EQ(p1.get_location(), home_id); + EXPECT_EQ(p2.get_location(), home_id); + EXPECT_EQ(p1.get_location_world_id(), world.get_id()); + EXPECT_EQ(p2.get_location_world_id(), world.get_id()); + EXPECT_EQ(world.get_activeness_statuses().size(), 2); + + world.change_activeness(p1.get_id()); + EXPECT_EQ(world.get_activeness_statuses()[p1.get_id().get()], false); + EXPECT_EQ(world.get_activeness_statuses()[p2.get_id().get()], true); + + auto t = mio::abm::TimePoint(0) + mio::abm::hours(6); + auto dt = mio::abm::hours(3); + + world.evolve(t, dt); + + //inactive persons do not move + EXPECT_EQ(p1.get_location(), home_id); + EXPECT_EQ(p2.get_location(), work_id); +} + +struct MockHistory { + + template + void log(const T& t) + { + mio::unused(t); + } +}; + +TEST(TestGraphAbm, test_evolve_node) +{ + auto t = mio::abm::TimePoint(0); + auto dt = mio::abm::hours(2); + mio::ABMSimulationNode node(MockHistory{}, t, size_t(1)); + node.evolve(t, dt); + + EXPECT_EQ(node.get_simulation().get_time(), mio::abm::TimePoint(dt.seconds())); +} + +TEST(TestGraphAbm, test_apply_mobility) +{ + auto world_1 = mio::abm::World(size_t(1), 1); + auto world_2 = mio::abm::World(size_t(1), 2); + world_1.parameters.get()[mio::AgeGroup(0)] = true; + world_2.parameters.get()[mio::AgeGroup(0)] = true; + auto work_id_1 = world_1.add_location(mio::abm::LocationType::Work); + auto home_id = world_1.add_location(mio::abm::LocationType::Home); + auto work_id_2 = world_2.add_location(mio::abm::LocationType::Work); + auto& work_1 = world_1.get_location(work_id_1); + auto& work_2 = world_2.get_location(work_id_2); + auto& home = world_1.get_location(home_id); + + EXPECT_EQ(work_1.get_world_id(), 1); + EXPECT_EQ(work_2.get_world_id(), 2); + + auto p1_id = world_1.add_person(home_id, mio::AgeGroup(0)); + auto p2_id = world_1.add_person(home_id, mio::AgeGroup(0)); + auto& p1 = world_1.get_person(p1_id); + auto& p2 = world_1.get_person(p2_id); + p1.set_assigned_location(work_1.get_type(), work_1.get_id(), work_1.get_world_id()); + p2.set_assigned_location(work_2.get_type(), work_2.get_id(), work_2.get_world_id()); + p1.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + p2.set_assigned_location(home.get_type(), home.get_id(), home.get_world_id()); + + //copy persons to world 2 + world_2.copy_persons_from_other_world(world_1); + + auto t = mio::abm::TimePoint(0) + mio::abm::hours(6); + mio::ABMSimulationNode node1(MockHistory{}, t, std::move(world_1)); + mio::ABMSimulationNode node2(MockHistory{}, t, std::move(world_2)); + + mio::ABMMobilityEdge edge({p2.get_id().get()}, {&mio::apply_commuting}); + edge.apply_mobility(node1, node2, t); + + EXPECT_EQ(node2.get_simulation().get_world().get_number_persons(work_id_2), 1); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_id().get()], true); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_id().get()], false); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_id().get()], false); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_id().get()], true); + + //return home + t += mio::abm::hours(12); + edge.apply_mobility(node1, node2, t); + EXPECT_EQ(node2.get_simulation().get_world().get_number_persons(work_id_2), 0); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_id().get()], true); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_id().get()], true); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_id().get()], false); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_id().get()], false); +} \ No newline at end of file diff --git a/cpp/thirdparty/CMakeLists.txt b/cpp/thirdparty/CMakeLists.txt index 582cdb81ba..d6b8eba801 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,8 +98,8 @@ if(MEMILIO_USE_BUNDLED_BOOST) add_dependencies(boost boost-bootstrap) add_library(Boost::boost ALIAS boost) target_include_directories(boost SYSTEM INTERFACE $) - - if (NOT MSVC) + + if(NOT MSVC) target_compile_options(boost INTERFACE "-Wno-c++20-attribute-extensions") endif() @@ -118,13 +119,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() @@ -189,15 +192,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/abm.cpp b/pycode/memilio-simulation/memilio/simulation/abm.cpp index 079b5c1a8d..8a9af03883 100644 --- a/pycode/memilio-simulation/memilio/simulation/abm.cpp +++ b/pycode/memilio-simulation/memilio/simulation/abm.cpp @@ -144,8 +144,8 @@ PYBIND11_MODULE(_simulation_abm, m) .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);