From f272d9c59f687b8376107c8c794b4f2866f101cb Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 7 Jun 2024 10:02:15 +0200 Subject: [PATCH 01/23] add ABM simulation node and edge --- cpp/models/abm/graph/mobility.cpp | 6 +++ cpp/models/abm/graph/mobility.h | 71 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 cpp/models/abm/graph/mobility.cpp create mode 100644 cpp/models/abm/graph/mobility.h diff --git a/cpp/models/abm/graph/mobility.cpp b/cpp/models/abm/graph/mobility.cpp new file mode 100644 index 0000000000..f5bc41eebc --- /dev/null +++ b/cpp/models/abm/graph/mobility.cpp @@ -0,0 +1,6 @@ +#include "abm/graph/mobility.h" + +namespace mio +{ + +} // namespace mio \ No newline at end of file diff --git a/cpp/models/abm/graph/mobility.h b/cpp/models/abm/graph/mobility.h new file mode 100644 index 0000000000..3b82d30011 --- /dev/null +++ b/cpp/models/abm/graph/mobility.h @@ -0,0 +1,71 @@ +#ifndef ABM_GRAPH_MOBILITY_H +#define ABM_GRAPH_MOBILITY_H + +#include "abm/simulation.h" +#include "abm/time.h" +#include + +namespace mio +{ +/** +* @brief ABM simulation in one node of the abm graph model +*/ +class ABMSimulationNode +{ + +public: + using Sim = mio::abm::Simulation; + + template ::value, void>> + ABMSimulationNode(Args&&... args) + : m_simulation(std::forward(args)...) + { + } + + /** + *@brief get abm simulation in this node. + */ + Sim& get_simulation() + { + return m_simulation; + } + const Sim& get_simulation() const + { + return m_simulation; + } + + /** + * @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 + */ + template + void evolve(mio::abm::TimePoint t, mio::abm::TimeSpan dt, History&... history) + { + m_simulation.advance(t + dt, history...); + } + +private: + Sim m_simulation; +}; + +class ABMMobilityEdge +{ + +public: + ABMMobilityEdge() + { + } + + void apply_migration(ABMSimulationNode& node_from, ABMSimulationNode& node_to); +}; + +void ABMMobilityEdge::apply_migration(ABMSimulationNode& node_from, ABMSimulationNode& node_to) +{ +} + +} // namespace mio + +#endif // ABM_GRAPH_MOBILITY_H \ No newline at end of file From 1f29e1c9d98c30ab3ed49a655999b7834c8b1911 Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 11 Jul 2024 16:01:47 +0200 Subject: [PATCH 02/23] graph abm implementation --- cpp/CMakeLists.txt | 1 + cpp/memilio/mobility/graph_simulation.h | 85 ++++--- cpp/models/abm/graph/mobility.cpp | 6 - cpp/models/abm/graph/mobility.h | 71 ------ cpp/models/abm/location.h | 10 + cpp/models/abm/person.cpp | 2 +- cpp/models/abm/person.h | 4 +- cpp/models/abm/world.cpp | 89 ++++--- cpp/models/abm/world.h | 23 ++ cpp/models/graph_abm/graph_abm_mobility.cpp | 31 +++ cpp/models/graph_abm/graph_abm_mobility.h | 264 ++++++++++++++++++++ cpp/models/graph_abm/mobility_rules.cpp | 40 +++ cpp/models/graph_abm/mobility_rules.h | 43 ++++ cpp/thirdparty/CMakeLists.txt | 35 +-- 14 files changed, 528 insertions(+), 176 deletions(-) delete mode 100644 cpp/models/abm/graph/mobility.cpp delete mode 100644 cpp/models/abm/graph/mobility.h create mode 100644 cpp/models/graph_abm/graph_abm_mobility.cpp create mode 100644 cpp/models/graph_abm/graph_abm_mobility.h create mode 100644 cpp/models/graph_abm/mobility_rules.cpp create mode 100644 cpp/models/graph_abm/mobility_rules.h diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 405b7cd17d..31f23b169c 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -130,6 +130,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/memilio/mobility/graph_simulation.h b/cpp/memilio/mobility/graph_simulation.h index 003ae08793..4931662282 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 node_f = std::function> +class GraphSimulation : public GraphSimulationBase { - using GraphSimulationBase::GraphSimulationBase; + using Base = GraphSimulationBase; + using Base::GraphSimulationBase; + +public: + void advance(Timepoint t_max = 1.0) + { + auto dt = Base::m_dt; + while (Base::m_t < t_max) { + if (Base::m_t + dt > t_max) { + dt = t_max - Base::m_t; + } + + for (auto& n : Base::m_graph.nodes()) { + Base::m_node_func(Base::m_t, dt, n.property); + } + + Base::m_t += dt; + + for (auto& e : Base::m_graph.edges()) { + Base::m_edge_func(Base::m_t, dt, e.property, Base::m_graph.nodes()[e.start_node_idx].property, + Base::m_graph.nodes()[e.end_node_idx].property); + } + } + } }; template class GraphSimulationStochastic - : public GraphSimulationBase> + typename Graph::NodeProperty&, typename Graph::NodeProperty&)>, + std::function> { - using Base = - GraphSimulationBase>; + using Base = GraphSimulationBase, + std::function>; using node_function = typename Base::node_function; using edge_function = typename Base::edge_function; @@ -247,8 +252,8 @@ 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)); diff --git a/cpp/models/abm/graph/mobility.cpp b/cpp/models/abm/graph/mobility.cpp deleted file mode 100644 index f5bc41eebc..0000000000 --- a/cpp/models/abm/graph/mobility.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "abm/graph/mobility.h" - -namespace mio -{ - -} // namespace mio \ No newline at end of file diff --git a/cpp/models/abm/graph/mobility.h b/cpp/models/abm/graph/mobility.h deleted file mode 100644 index 3b82d30011..0000000000 --- a/cpp/models/abm/graph/mobility.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef ABM_GRAPH_MOBILITY_H -#define ABM_GRAPH_MOBILITY_H - -#include "abm/simulation.h" -#include "abm/time.h" -#include - -namespace mio -{ -/** -* @brief ABM simulation in one node of the abm graph model -*/ -class ABMSimulationNode -{ - -public: - using Sim = mio::abm::Simulation; - - template ::value, void>> - ABMSimulationNode(Args&&... args) - : m_simulation(std::forward(args)...) - { - } - - /** - *@brief get abm simulation in this node. - */ - Sim& get_simulation() - { - return m_simulation; - } - const Sim& get_simulation() const - { - return m_simulation; - } - - /** - * @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 - */ - template - void evolve(mio::abm::TimePoint t, mio::abm::TimeSpan dt, History&... history) - { - m_simulation.advance(t + dt, history...); - } - -private: - Sim m_simulation; -}; - -class ABMMobilityEdge -{ - -public: - ABMMobilityEdge() - { - } - - void apply_migration(ABMSimulationNode& node_from, ABMSimulationNode& node_to); -}; - -void ABMMobilityEdge::apply_migration(ABMSimulationNode& node_from, ABMSimulationNode& node_to) -{ -} - -} // namespace mio - -#endif // ABM_GRAPH_MOBILITY_H \ No newline at end of file diff --git a/cpp/models/abm/location.h b/cpp/models/abm/location.h index 1c8f2d027c..f78bd09a23 100644 --- a/cpp/models/abm/location.h +++ b/cpp/models/abm/location.h @@ -395,6 +395,15 @@ class Location m_geographical_location = location; } + /** + * @brief Get the world id the location is in. Is only relevant for graph ABM. + * @return World id of the location + */ + int get_world_id() const + { + return m_world_id; + } + private: std::mutex m_mut; ///< Mutex to protect the list of persons from concurrent modification. LocationId m_id; ///< Id of the Location including type and index. @@ -406,6 +415,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/person.cpp b/cpp/models/abm/person.cpp index e2dcd5f848..32a1509935 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -149,7 +149,7 @@ uint32_t Person::get_assigned_location_index(LocationType type) const bool Person::goes_to_work(TimePoint t, const Parameters& params) const { - return m_random_workgroup < params.get().get_matrix_at(t.days())[0]; + return (m_random_workgroup < params.get().get_matrix_at(t.days())[0] && !m_is_commuter); } TimeSpan Person::get_go_to_work_time(const Parameters& params) const diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index b80430a1b0..4076701d9b 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -329,7 +329,7 @@ class Person { return t < m_quarantine_start + params.get(); } - + /** * @brief Removes the quarantine status of the Person. */ @@ -526,6 +526,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 + bool m_is_commuter = + false; ///< Whether the Person commutes i.e. has work in another graph node. Only used for ABM graph model or hybrid graph model. }; } // namespace abm diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index e534b8cec0..82a060ded0 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -48,6 +48,7 @@ Person& World::add_person(const LocationId id, AgeGroup age) assert(age.get() < parameters.get_num_groups()); uint32_t person_id = static_cast(m_persons.size()); m_persons.push_back(std::make_unique(m_rng, get_individualized_location(id), age, person_id)); + m_activeness_statuses.push_back(true); auto& person = *m_persons.back(); person.set_assigned_location(m_cemetery_id); get_individualized_location(id).add_person(person); @@ -67,9 +68,11 @@ void World::interaction(TimePoint t, TimeSpan dt) { PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { - auto&& person = m_persons[i]; - auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - person->interact(personal_rng, t, dt, parameters); + if (m_activeness_statuses[i]) { + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + person->interact(personal_rng, t, dt, parameters); + } } } @@ -77,47 +80,51 @@ void World::migration(TimePoint t, TimeSpan dt) { PRAGMA_OMP(parallel for) for (auto i = size_t(0); i < m_persons.size(); ++i) { - auto&& person = m_persons[i]; - auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); - - auto try_migration_rule = [&](auto rule) -> bool { - //run migration rule and check if migration can actually happen - auto target_type = rule(personal_rng, *person, t, dt, parameters); - auto& target_location = find_location(target_type, *person); - auto& current_location = person->get_location(); - if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { - if (target_location != current_location && - target_location.get_number_persons() < target_location.get_capacity().persons) { - bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); - if (wears_mask) { - person->migrate_to(target_location); + if (m_activeness_statuses[i]) { + auto&& person = m_persons[i]; + auto personal_rng = Person::RandomNumberGenerator(m_rng, *person); + + auto try_migration_rule = [&](auto rule) -> bool { + //run migration rule and check if migration can actually happen + auto target_type = rule(personal_rng, *person, t, dt, parameters); + auto& target_location = find_location(target_type, *person); + auto& current_location = person->get_location(); + if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + if (target_location != current_location && + target_location.get_number_persons() < target_location.get_capacity().persons) { + bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); + if (wears_mask) { + person->migrate_to(target_location); + } + 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)); } } diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 8aa65ac5de..8819aaa509 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -33,6 +33,7 @@ #include "memilio/utils/stl_util.h" #include +#include #include #include #include @@ -90,6 +91,8 @@ class World LocationId origin_id = {origin_loc.get_index(), origin_loc.get_type()}; m_persons.push_back( std::make_unique(person.copy_person(get_individualized_location(origin_id)))); + // The default value for a person that is added to a world is active + m_activeness_statuses.push_back(true); } } } @@ -287,6 +290,15 @@ 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; + } + /** * @brief Add a TestingScheme to the set of schemes that are checked for testing at all Locations that have * the LocationType. @@ -303,6 +315,15 @@ class World */ void remove_testing_scheme(const LocationType& loc_type, const TestingScheme& scheme); + /** + * @brief Flip activeness status of a person in the world. + * @param[in] person_id Person whose activeness status is fipped. + */ + void change_activeness(uint32_t person_id) + { + m_activeness_statuses[person_id] = !m_activeness_statuses[person_id]; + } + private: /** * @brief Person%s interact at their Location and may become infected. @@ -318,6 +339,7 @@ class World void migration(TimePoint t, TimeSpan dt); std::vector> m_persons; ///< Vector with pointers to every Person. + std::vector m_activeness_statuses; ///< Vector with activeness status for every person std::vector> m_locations; ///< Vector with pointers to every Location. std::bitset m_has_locations; ///< Flags for each LocationType, set if a Location of that type exists. @@ -330,6 +352,7 @@ class World m_migration_rules; ///< Rules that govern the migration between Location%s. LocationId m_cemetery_id; // Central cemetery for all dead persons. RandomNumberGenerator m_rng; ///< Global random number generator + int m_id; ///< World id. Is only used for abm graph model or hybrid model. }; } // namespace abm 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..c1d6433155 --- /dev/null +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -0,0 +1,264 @@ +/* +* 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 "memilio/mobility/graph_simulation.h" +#include "memilio/mobility/graph.h" +#include +#include +#include +#include + +namespace mio +{ +/** +* @brief Represents the ABM simulation in one node of the abm graph model. +*/ +template +class ABMSimulationNode +{ + +public: + using Sim = abm::Simulation; + + template ::value, void>> + ABMSimulationNode(Args&&... args) + : m_simulation(std::forward(args)...) + { + } + + /** + *@brief get abm simulation in this node. + */ + Sim& get_simulation() + { + return m_simulation; + } + const Sim& get_simulation() const + { + return m_simulation; + } + + /** + * @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, 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 MobilityRulesVec = + std::vector; + + /** + * Constructor for initializing commuting persons + * @param commuting_persons Vector holding commuting persons' ids + */ + ABMMobilityParameters(const std::vector& commuting_persons) + : m_commuting_persons(commuting_persons) + { + } + + /** + * 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/Set the mobility rules. + * The rules are applied to the persons in m_commuting_persons every time exchange betwen two nodes is triggered. + */ + const MobilityRulesVec& get_mobility_rules() const + { + return m_mobility_rules; + } + MobilityRulesVec& get_mobility_rules() + { + return m_mobility_rules; + } + /** + * @param[in] mobility_rules Vector with rules for mobility between nodes. + */ + void set_mobility_rules(const MobilityRulesVec& mobility_rules) + { + m_mobility_rules = mobility_rules; + } + +private: + std::vector m_commuting_persons; ///< Person ids that are commuting via an edge + MobilityRulesVec m_mobility_rules; ///< Rules for moving persons from one node to another +}; + +/** + * Represents the mobility between two nodes. + */ +template +class ABMMobilityEdge +{ + +public: + /** + * 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) + { + } + + /** + * @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()) { + // as all nodes have all person it doesn't matter which node's persons we take here + auto& person = node_from.get_simulation().get_world().get_persons()[p]; + auto& params = node_from.get_simulation().get_world().parameters; + auto& current_location = person.get_location(); + for (auto& rule : m_parameters.get_mobility_rules()) { + auto target_type = rule(person, t, params); + abm::Location& target_location = + node_from.get_simulation().get_world().find_location(target_type, person); + 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_location != current_location && + target_location.get_number_persons() < target_location.get_capacity().persons) { + person.migrate_to(target_location); + // 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> +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..31c7ac4a98 --- /dev/null +++ b/cpp/models/graph_abm/mobility_rules.cpp @@ -0,0 +1,40 @@ +/* +* 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" + +namespace mio +{ + +abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, const abm::Parameters& params) +{ + abm::LocationType current_loc = person.get_location().get_type(); + + if (current_loc == abm::LocationType::Home && t < params.get() && t.day_of_week() < 5 && + person.goes_to_work(t, params) && !person.is_in_quarantine(t, params)) { + return abm::LocationType::Home; + } + + // agents are sent home or to work 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/thirdparty/CMakeLists.txt b/cpp/thirdparty/CMakeLists.txt index d75fd27f33..104760b28f 100644 --- a/cpp/thirdparty/CMakeLists.txt +++ b/cpp/thirdparty/CMakeLists.txt @@ -70,17 +70,17 @@ if(MEMILIO_USE_BUNDLED_BOOST) include(FetchContent) FetchContent_Declare(boost - GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-${MEMILIO_BOOST_VERSION}) + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-${MEMILIO_BOOST_VERSION}) FetchContent_GetProperties(boost) if(NOT boost_POPULATED) FetchContent_Populate(boost) endif() - - add_custom_target(boost-bootstrap ALL - DEPENDS "${boost_SOURCE_DIR}/boost" - ) + + add_custom_target(boost-bootstrap ALL + DEPENDS "${boost_SOURCE_DIR}/boost" + ) if(MSVC) add_custom_command( @@ -89,23 +89,23 @@ if(MEMILIO_USE_BUNDLED_BOOST) COMMAND "b2.exe" headers WORKING_DIRECTORY ${boost_SOURCE_DIR} VERBATIM - ) + ) else() add_custom_command( OUTPUT "${boost_SOURCE_DIR}/boost" COMMAND ./bootstrap.sh - COMMAND ./b2 headers + COMMAND ./b2 headers WORKING_DIRECTORY ${boost_SOURCE_DIR} VERBATIM - ) + ) endif() - + add_library(boost INTERFACE) 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() @@ -125,16 +125,19 @@ 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() + target_compile_options(boost_filesystem PUBLIC /wd4127 /wd4459) set(Boost_LIBRARIES Boost::boost Boost::filesystem) set(Boost_FOUND ON) @@ -196,15 +199,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) From 5e91137d6cde2e09f28f0e970a0710307bddffd8 Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 11 Jul 2024 16:04:01 +0200 Subject: [PATCH 03/23] remove warning surpression for boost --- cpp/thirdparty/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/thirdparty/CMakeLists.txt b/cpp/thirdparty/CMakeLists.txt index 104760b28f..d45524af33 100644 --- a/cpp/thirdparty/CMakeLists.txt +++ b/cpp/thirdparty/CMakeLists.txt @@ -137,7 +137,6 @@ if(MEMILIO_USE_BUNDLED_BOOST) target_compile_definitions(boost_filesystem PUBLIC BOOST_NO_CXX98_FUNCTION_BASE) endif() - target_compile_options(boost_filesystem PUBLIC /wd4127 /wd4459) set(Boost_LIBRARIES Boost::boost Boost::filesystem) set(Boost_FOUND ON) From 4368cb512e89167b7cf23176f6e92a76cc1cbf48 Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 12 Jul 2024 08:50:03 +0200 Subject: [PATCH 04/23] add CMakeList --- cpp/models/graph_abm/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 cpp/models/graph_abm/CMakeLists.txt diff --git a/cpp/models/graph_abm/CMakeLists.txt b/cpp/models/graph_abm/CMakeLists.txt new file mode 100644 index 0000000000..780cdac99f --- /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.h + mobility_rules.cpp +) + +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}) From 81df1a80ea53948222df7f5bc9689a82b483e2e9 Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 12 Jul 2024 09:03:25 +0200 Subject: [PATCH 05/23] CMake List Bug fix --- cpp/thirdparty/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/thirdparty/CMakeLists.txt b/cpp/thirdparty/CMakeLists.txt index 13ceab4ca7..d6b8eba801 100644 --- a/cpp/thirdparty/CMakeLists.txt +++ b/cpp/thirdparty/CMakeLists.txt @@ -84,8 +84,10 @@ if(MEMILIO_USE_BUNDLED_BOOST) include(FetchContent) FetchContent_Declare(boost - GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-${MEMILIO_BOOST_VERSION}) + + # 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) if(NOT boost_POPULATED) From 5b2c64ab2c7fffee71f7ef6ee53a9d2c9236ead5 Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 12 Jul 2024 09:06:44 +0200 Subject: [PATCH 06/23] fix include --- cpp/models/graph_abm/graph_abm_mobility.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index c1d6433155..eef17dcd7f 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -28,10 +28,6 @@ #include "abm/person.h" #include "memilio/mobility/graph_simulation.h" #include "memilio/mobility/graph.h" -#include -#include -#include -#include namespace mio { From 414c3f490d2db2f566a63ae077cc4c627886dec0 Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 12 Jul 2024 09:11:20 +0200 Subject: [PATCH 07/23] bug fix --- cpp/models/graph_abm/graph_abm_mobility.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index eef17dcd7f..f79d9c2518 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -252,7 +252,7 @@ GraphSimulation, ABMMobilityEdge 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) + return make_graph_sim(t0, dt, std::move(graph), &evolve_model, &apply_mobility); } } // namespace mio From 4e2a73398fd6b0654aa0803f5c6ddc9b5cba8aaf Mon Sep 17 00:00:00 2001 From: jubicker Date: Mon, 15 Jul 2024 13:43:39 +0200 Subject: [PATCH 08/23] test --- cpp/models/abm/location.h | 11 ++- cpp/models/abm/world.h | 15 ++- cpp/models/graph_abm/graph_abm_mobility.h | 46 ++++++--- cpp/models/graph_abm/mobility_rules.cpp | 5 +- cpp/tests/CMakeLists.txt | 21 ++-- cpp/tests/test_graph_abm.cpp | 115 ++++++++++++++++++++++ 6 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 cpp/tests/test_graph_abm.cpp diff --git a/cpp/models/abm/location.h b/cpp/models/abm/location.h index f78bd09a23..19a3c4c182 100644 --- a/cpp/models/abm/location.h +++ b/cpp/models/abm/location.h @@ -396,7 +396,7 @@ class Location } /** - * @brief Get the world id the location is in. Is only relevant for graph ABM. + * @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 @@ -404,6 +404,15 @@ class Location 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: std::mutex m_mut; ///< Mutex to protect the list of persons from concurrent modification. LocationId m_id; ///< Id of the Location including type and index. diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 8819aaa509..2a97a0a724 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -59,11 +59,12 @@ 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_trip_list() , m_use_migration_rules(true) , m_cemetery_id(add_location(LocationType::Cemetery)) + , m_id(id) { assert(num_agegroups < MAX_NUM_AGE_GROUPS && "MAX_NUM_AGE_GROUPS exceeded."); } @@ -72,12 +73,13 @@ class World * @brief Create a copied World. * @param[in] other The World that needs to be copied. */ - World(const World& other) + World(const World& other, int id = 0) : parameters(other.parameters) , m_persons() , m_locations() , m_trip_list(other.m_trip_list) , m_cemetery_id(add_location(LocationType::Cemetery)) + , m_id(id) { for (auto& origin_loc : other.get_locations()) { if (origin_loc.get_type() != LocationType::Cemetery) { @@ -299,6 +301,15 @@ class World 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. diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index f79d9c2518..61a14d2ce8 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -28,6 +28,8 @@ #include "abm/person.h" #include "memilio/mobility/graph_simulation.h" #include "memilio/mobility/graph.h" +#include +#include namespace mio { @@ -42,8 +44,9 @@ class ABMSimulationNode using Sim = abm::Simulation; template ::value, void>> - ABMSimulationNode(Args&&... args) - : m_simulation(std::forward(args)...) + ABMSimulationNode(std::tuple history, Args&&... args) + : m_history(history) + , m_simulation(std::forward(args)...) { } @@ -68,7 +71,7 @@ class ABMSimulationNode */ void evolve(mio::abm::TimePoint t, mio::abm::TimeSpan dt) { - m_simulation.advance(t + dt, m_history); + m_simulation.advance(t + dt, std::get<0>(m_history)); } private: @@ -83,15 +86,16 @@ class ABMMobilityParameters { public: - using MobilityRulesVec = - std::vector; + 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) + ABMMobilityParameters(const std::vector& commuting_persons, + const std::vector& mobility_rules) : m_commuting_persons(commuting_persons) + , m_mobility_rules(mobility_rules) { } @@ -130,28 +134,29 @@ class ABMMobilityParameters } /** - * Get/Set the mobility rules. + * Get/ the mobility rules. * The rules are applied to the persons in m_commuting_persons every time exchange betwen two nodes is triggered. */ - const MobilityRulesVec& get_mobility_rules() const + const std::vector& get_mobility_rules() const { return m_mobility_rules; } - MobilityRulesVec& get_mobility_rules() + std::vector& get_mobility_rules() { return m_mobility_rules; } /** - * @param[in] mobility_rules Vector with rules for mobility between nodes. + * @brief Add mobility rule to member vector. + * @param[in] mobility_rule Rule to be added for mobility between nodes. */ - void set_mobility_rules(const MobilityRulesVec& mobility_rules) + void add_mobility_rule(const MobilityRuleType& mobility_rule) { - m_mobility_rules = mobility_rules; + m_mobility_rules.push_back(mobility_rule); } private: std::vector m_commuting_persons; ///< Person ids that are commuting via an edge - MobilityRulesVec m_mobility_rules; ///< Rules for moving persons from one node to another + std::vector m_mobility_rules; ///< Rules for moving persons from one node to another }; /** @@ -160,6 +165,7 @@ class ABMMobilityParameters template class ABMMobilityEdge { + using MobilityRuleType = abm::LocationType (*)(const abm::Person&, abm::TimePoint, const abm::Parameters&); public: /** @@ -171,6 +177,20 @@ class ABMMobilityEdge { } + 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. diff --git a/cpp/models/graph_abm/mobility_rules.cpp b/cpp/models/graph_abm/mobility_rules.cpp index 31c7ac4a98..797e6e08cd 100644 --- a/cpp/models/graph_abm/mobility_rules.cpp +++ b/cpp/models/graph_abm/mobility_rules.cpp @@ -28,8 +28,9 @@ abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, c { abm::LocationType current_loc = person.get_location().get_type(); - if (current_loc == abm::LocationType::Home && t < params.get() && t.day_of_week() < 5 && - person.goes_to_work(t, params) && !person.is_in_quarantine(t, params)) { + 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::Home; } diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 4e6c389fe8..8d48f72c48 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -72,22 +72,23 @@ 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}) diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp new file mode 100644 index 0000000000..3c41c73c2d --- /dev/null +++ b/cpp/tests/test_graph_abm.cpp @@ -0,0 +1,115 @@ +/* +* 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 "memilio/io/history.h" +#include +#include +#include +#include + +TEST(TestGraphAbm, test_activessness) +{ + auto world = mio::abm::World(size_t(1)); + world.parameters.get().set_multiple({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& p1 = world.add_person(home_id, mio::AgeGroup(0)); + auto& p2 = world.add_person(home_id, mio::AgeGroup(0)); + p1.set_assigned_location(work_id); + p2.set_assigned_location(work_id); + p1.set_assigned_location(home_id); + p2.set_assigned_location(home_id); + + auto& home = world.get_individualized_location(home_id); + auto& work = world.get_individualized_location(work_id); + + EXPECT_EQ(p1.get_location(), home); + EXPECT_EQ(p2.get_location(), home); + EXPECT_EQ(world.get_activeness_statuses().size(), 2); + + world.change_activeness(p1.get_person_id()); + EXPECT_EQ(world.get_activeness_statuses()[p1.get_person_id()], false); + EXPECT_EQ(world.get_activeness_statuses()[p2.get_person_id()], 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); + EXPECT_EQ(p2.get_location(), work); +} + +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 work_id_1 = world_1.add_location(mio::abm::LocationType::Work); + world_1.get_individualized_location(work_id_1).set_world_id(world_1.get_id()); + auto work_id_2 = world_1.add_location(mio::abm::LocationType::Work); + world_1.get_individualized_location(work_id_2).set_world_id(2); + auto home_id = world_1.add_location(mio::abm::LocationType::Home); + world_1.get_individualized_location(home_id).set_world_id(world_1.get_id()); + auto& p1 = world_1.add_person(home_id, mio::AgeGroup(0)); + auto& p2 = world_1.add_person(home_id, mio::AgeGroup(0)); + p1.set_assigned_location(work_id_1); + p2.set_assigned_location(work_id_2); + p1.set_assigned_location(home_id); + p2.set_assigned_location(home_id); + auto world_2 = mio::abm::World(world_1, 2); + // Deactivate persons in world 2 + world_2.change_activeness(p1.get_person_id()); + world_2.change_activeness(p2.get_person_id()); + + auto t0 = mio::abm::TimePoint(0) + mio::abm::hours(6); + // mio::ABMSimulationNode node1(MockHistory{}, t0, std::move(world_1)); + // mio::ABMSimulationNode node2(MockHistory{}, t0, std::move(world_2)); + //const auto&& a = mio::apply_commuting; + auto a = mio::apply_commuting(p1, t0, world_1.parameters); + mio::unused(a); + //auto b = decltype(a); + + //mio::ABMMobilityEdge edge({p2.get_person_id()}, {*mio::apply_commuting}); +} \ No newline at end of file From 8f1d7cb425f6eb1abd6b5aa3f8e5b9d57613e8d5 Mon Sep 17 00:00:00 2001 From: jubicker Date: Mon, 15 Jul 2024 15:32:41 +0200 Subject: [PATCH 09/23] test apply_mobility --- cpp/models/abm/person.cpp | 2 +- cpp/models/abm/person.h | 2 -- cpp/models/graph_abm/CMakeLists.txt | 2 +- cpp/models/graph_abm/graph_abm_mobility.h | 3 +-- cpp/models/graph_abm/mobility_rules.cpp | 3 +-- cpp/tests/CMakeLists.txt | 2 +- cpp/tests/test_graph_abm.cpp | 27 ++++++++++++----------- 7 files changed, 19 insertions(+), 22 deletions(-) diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index 32a1509935..e2dcd5f848 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -149,7 +149,7 @@ uint32_t Person::get_assigned_location_index(LocationType type) const bool Person::goes_to_work(TimePoint t, const Parameters& params) const { - return (m_random_workgroup < params.get().get_matrix_at(t.days())[0] && !m_is_commuter); + return m_random_workgroup < params.get().get_matrix_at(t.days())[0]; } TimeSpan Person::get_go_to_work_time(const Parameters& params) const diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 4076701d9b..c65664dc73 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -526,8 +526,6 @@ 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 - bool m_is_commuter = - false; ///< Whether the Person commutes i.e. has work in another graph node. Only used for ABM graph model or hybrid graph model. }; } // namespace abm diff --git a/cpp/models/graph_abm/CMakeLists.txt b/cpp/models/graph_abm/CMakeLists.txt index 780cdac99f..866bc85b29 100644 --- a/cpp/models/graph_abm/CMakeLists.txt +++ b/cpp/models/graph_abm/CMakeLists.txt @@ -1,8 +1,8 @@ add_library(graph_abm graph_abm_mobility.cpp graph_abm_mobility.h - mobility_rules.h mobility_rules.cpp + mobility_rules.h ) target_link_libraries(graph_abm PUBLIC memilio) diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 61a14d2ce8..2593b5b56d 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -45,8 +45,7 @@ class ABMSimulationNode template ::value, void>> ABMSimulationNode(std::tuple history, Args&&... args) - : m_history(history) - , m_simulation(std::forward(args)...) + : m_simulation(std::forward(args)...), m_history(history) { } diff --git a/cpp/models/graph_abm/mobility_rules.cpp b/cpp/models/graph_abm/mobility_rules.cpp index 797e6e08cd..0d7c7df3da 100644 --- a/cpp/models/graph_abm/mobility_rules.cpp +++ b/cpp/models/graph_abm/mobility_rules.cpp @@ -31,11 +31,10 @@ abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, c 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::Home; + return abm::LocationType::Work; } // agents are sent home or to work 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/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 8d48f72c48..4620468e6d 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -93,7 +93,7 @@ endif() add_executable(memilio-test ${TESTSOURCES}) target_include_directories(memilio-test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(memilio-test PRIVATE memilio ode_secir ode_seir ode_secirvvs ode_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/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp index 3c41c73c2d..aa8e45ac97 100644 --- a/cpp/tests/test_graph_abm.cpp +++ b/cpp/tests/test_graph_abm.cpp @@ -24,11 +24,7 @@ #include "graph_abm/graph_abm_mobility.h" #include "graph_abm/mobility_rules.h" #include "memilio/epidemiology/age_group.h" -#include "memilio/io/history.h" -#include -#include #include -#include TEST(TestGraphAbm, test_activessness) { @@ -86,10 +82,12 @@ TEST(TestGraphAbm, test_evolve_node) TEST(TestGraphAbm, test_apply_mobility) { auto world_1 = mio::abm::World(size_t(1), 1); + world_1.parameters.get().set_multiple({mio::AgeGroup(0)}, true); auto work_id_1 = world_1.add_location(mio::abm::LocationType::Work); world_1.get_individualized_location(work_id_1).set_world_id(world_1.get_id()); auto work_id_2 = world_1.add_location(mio::abm::LocationType::Work); - world_1.get_individualized_location(work_id_2).set_world_id(2); + auto& work_2 = world_1.get_individualized_location(work_id_2); + work_2.set_world_id(2); auto home_id = world_1.add_location(mio::abm::LocationType::Home); world_1.get_individualized_location(home_id).set_world_id(world_1.get_id()); auto& p1 = world_1.add_person(home_id, mio::AgeGroup(0)); @@ -104,12 +102,15 @@ TEST(TestGraphAbm, test_apply_mobility) world_2.change_activeness(p2.get_person_id()); auto t0 = mio::abm::TimePoint(0) + mio::abm::hours(6); - // mio::ABMSimulationNode node1(MockHistory{}, t0, std::move(world_1)); - // mio::ABMSimulationNode node2(MockHistory{}, t0, std::move(world_2)); - //const auto&& a = mio::apply_commuting; - auto a = mio::apply_commuting(p1, t0, world_1.parameters); - mio::unused(a); - //auto b = decltype(a); - - //mio::ABMMobilityEdge edge({p2.get_person_id()}, {*mio::apply_commuting}); + mio::ABMSimulationNode node1(MockHistory{}, t0, std::move(world_1)); + mio::ABMSimulationNode node2(MockHistory{}, t0, std::move(world_2)); + + mio::ABMMobilityEdge edge({p2.get_person_id()}, {&mio::apply_commuting}); + edge.apply_mobility(node1, node2, t0+mio::abm::hours(2)); + + EXPECT_EQ(work_2.get_number_persons(), 1); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], true); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], false); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], false); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], true); } \ No newline at end of file From 7400802d6515edb7c5f80511e7cabbdb9e31a271 Mon Sep 17 00:00:00 2001 From: jubicker Date: Tue, 16 Jul 2024 08:23:48 +0200 Subject: [PATCH 10/23] extend apply_mobility test --- cpp/models/graph_abm/graph_abm_mobility.h | 3 ++- cpp/tests/test_graph_abm.cpp | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 2593b5b56d..4191a6aee5 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -45,7 +45,8 @@ class ABMSimulationNode template ::value, void>> ABMSimulationNode(std::tuple history, Args&&... args) - : m_simulation(std::forward(args)...), m_history(history) + : m_simulation(std::forward(args)...) + , m_history(history) { } diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp index aa8e45ac97..0a9a254a6f 100644 --- a/cpp/tests/test_graph_abm.cpp +++ b/cpp/tests/test_graph_abm.cpp @@ -81,12 +81,12 @@ TEST(TestGraphAbm, test_evolve_node) TEST(TestGraphAbm, test_apply_mobility) { - auto world_1 = mio::abm::World(size_t(1), 1); + auto world_1 = mio::abm::World(size_t(1), 1); world_1.parameters.get().set_multiple({mio::AgeGroup(0)}, true); auto work_id_1 = world_1.add_location(mio::abm::LocationType::Work); world_1.get_individualized_location(work_id_1).set_world_id(world_1.get_id()); auto work_id_2 = world_1.add_location(mio::abm::LocationType::Work); - auto& work_2 = world_1.get_individualized_location(work_id_2); + auto& work_2 = world_1.get_individualized_location(work_id_2); work_2.set_world_id(2); auto home_id = world_1.add_location(mio::abm::LocationType::Home); world_1.get_individualized_location(home_id).set_world_id(world_1.get_id()); @@ -101,16 +101,25 @@ TEST(TestGraphAbm, test_apply_mobility) world_2.change_activeness(p1.get_person_id()); world_2.change_activeness(p2.get_person_id()); - auto t0 = mio::abm::TimePoint(0) + mio::abm::hours(6); - mio::ABMSimulationNode node1(MockHistory{}, t0, std::move(world_1)); - mio::ABMSimulationNode node2(MockHistory{}, t0, std::move(world_2)); + 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_person_id()}, {&mio::apply_commuting}); - edge.apply_mobility(node1, node2, t0+mio::abm::hours(2)); + edge.apply_mobility(node1, node2, t); EXPECT_EQ(work_2.get_number_persons(), 1); EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], true); EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], false); EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], false); EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], true); + + //return home + t += mio::abm::hours(12); + edge.apply_mobility(node1, node2, t); + EXPECT_EQ(work_2.get_number_persons(), 0); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], true); + EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], true); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], false); + EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], false); } \ No newline at end of file From b31471551b7c5698c04929f0819b95f277cb219f Mon Sep 17 00:00:00 2001 From: jubicker Date: Tue, 16 Jul 2024 16:01:16 +0200 Subject: [PATCH 11/23] error in abm_minimal example --- cpp/examples/abm_minimal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 81be666e75..0c063cf567 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -54,7 +54,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); From dbbd06864c718984847031e09c110069f26b5dab Mon Sep 17 00:00:00 2001 From: jubicker Date: Wed, 17 Jul 2024 15:13:39 +0200 Subject: [PATCH 12/23] test for apply mobility --- cpp/models/abm/location_type.h | 5 ++-- cpp/models/abm/person.cpp | 12 ++++++-- cpp/models/abm/person.h | 20 +++++++++++++ cpp/models/abm/world.cpp | 15 +++++++++- cpp/models/abm/world.h | 34 +++++++++++++++++++++++ cpp/models/graph_abm/graph_abm_mobility.h | 7 +++-- cpp/tests/test_graph_abm.cpp | 29 ++++++++++--------- 7 files changed, 102 insertions(+), 20 deletions(-) diff --git a/cpp/models/abm/location_type.h b/cpp/models/abm/location_type.h index 9b685a9a7a..80a2979e0e 100644 --- a/cpp/models/abm/location_type.h +++ b/cpp/models/abm/location_type.h @@ -58,15 +58,16 @@ static constexpr uint32_t INVALID_LOCATION_INDEX = std::numeric_limits struct LocationId { uint32_t index; LocationType type; + int world_id = 0; bool operator==(const LocationId& rhs) const { - return (index == rhs.index && type == rhs.type); + return (index == rhs.index && type == rhs.type && world_id == rhs.world_id); } bool operator!=(const LocationId& rhs) const { - return !(index == rhs.index && type == rhs.type); + return !(index == rhs.index && type == rhs.type && world_id == rhs.world_id); } bool operator<(const LocationId& rhs) const diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index e2dcd5f848..e202826576 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -45,6 +45,7 @@ Person::Person(mio::RandomNumberGenerator& rng, Location& location, AgeGroup age , 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); @@ -134,12 +135,14 @@ void Person::set_assigned_location(Location& location) * For now only use it like this: auto home_id = world.add_location(mio::abm::LocationType::Home); * person.set_assigned_location(home); */ - m_assigned_locations[(uint32_t)location.get_type()] = location.get_index(); + m_assigned_locations[(uint32_t)location.get_type()] = location.get_index(); + m_assigned_location_world_ids[(uint32_t)location.get_type()] = location.get_world_id(); } void Person::set_assigned_location(LocationId id) { - m_assigned_locations[(uint32_t)id.type] = id.index; + m_assigned_locations[(uint32_t)id.type] = id.index; + m_assigned_location_world_ids[(uint32_t)id.type] = id.world_id; } uint32_t Person::get_assigned_location_index(LocationType type) const @@ -147,6 +150,11 @@ uint32_t Person::get_assigned_location_index(LocationType type) const return m_assigned_locations[(uint32_t)type]; } +int Person::get_assigned_location_world_id(LocationType type) const +{ + return m_assigned_location_world_ids[(uint32_t)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 c65664dc73..06f29d59e9 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -32,6 +32,7 @@ #include "memilio/utils/memory.h" #include "abm/movement_data.h" #include +#include namespace mio { @@ -281,6 +282,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 @@ -526,6 +544,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 82a060ded0..0b3509fecd 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -37,8 +37,9 @@ namespace abm LocationId World::add_location(LocationType type, uint32_t num_cells) { - LocationId id = {static_cast(m_locations.size()), type}; + LocationId id = {static_cast(m_locations.size()), type, m_id}; m_locations.emplace_back(std::make_unique(id, parameters.get_num_groups(), num_cells)); + m_locations[id.index]->set_world_id(m_id); m_has_locations[size_t(type)] = true; return id; } @@ -55,6 +56,18 @@ Person& World::add_person(const LocationId id, AgeGroup age) return person; } +Person& World::add_external_person(Location& loc, AgeGroup age) +{ + assert(age.get() < parameters.get_num_groups()); + uint32_t person_id = static_cast(m_persons.size()); + m_persons.push_back(std::make_unique(m_rng, loc, age, person_id)); + m_activeness_statuses.push_back(false); + auto& person = *m_persons.back(); + person.set_assigned_location(m_cemetery_id); + loc.add_person(person); + return person; +} + void World::evolve(TimePoint t, TimeSpan dt) { begin_step(t, dt); diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 2a97a0a724..4b47686599 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -33,6 +33,7 @@ #include "memilio/utils/stl_util.h" #include +#include #include #include #include @@ -183,6 +184,15 @@ class World */ Person& add_person(const LocationId id, AgeGroup age); + /** + * @brief Add an external Person i.e. a Person whoseHome location is in another World to the World. + * Only used for abm graph model. + * @param[in] loc Initial Location of the Person + * @param[in] age AgeGroup of the Person + * @return Reference to the newly created Person + */ + Person& add_external_person(Location& loc, AgeGroup age); + /** * @brief Get a range of all Location%s in the World. * @return A range of all Location%s. @@ -335,6 +345,30 @@ class World m_activeness_statuses[person_id] = !m_activeness_statuses[person_id]; } + /** + * @brief Copy the persons from another World to this World. + * @param[in] other The World the Person%s are copied from. + */ + void copy_persons_from_other_world(const World& other) + { + for (auto& person : other.get_persons()) { + auto new_person = Person(person.copy_person(person.get_location())); + //copy assigned locations + for (auto type = 0; type < person.get_assigned_locations().size(); ++type) { + auto index = person.get_assigned_location_index(LocationType(type)); + auto world_id = person.get_assigned_location_world_id(LocationType(type)); + new_person.set_assigned_location(LocationId{index, LocationType(type), world_id}); + } + m_persons.push_back(std::make_unique(new_person)); + if (person.get_location().get_world_id() == m_id) { + m_activeness_statuses.push_back(true); + } + else { + m_activeness_statuses.push_back(false); + } + } + } + private: /** * @brief Person%s interact at their Location and may become infected. diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 4191a6aee5..64dac753af 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -208,9 +208,12 @@ class ABMMobilityEdge auto& params = node_from.get_simulation().get_world().parameters; auto& current_location = person.get_location(); for (auto& rule : m_parameters.get_mobility_rules()) { - auto target_type = rule(person, t, params); + auto target_type = rule(person, t, params); + auto target_world_id = person.get_assigned_location_world_id(target_type); abm::Location& target_location = - node_from.get_simulation().get_world().find_location(target_type, person); + (target_world_id == node_from.get_simulation().get_world().get_id()) + ? node_from.get_simulation().get_world().find_location(target_type, person) + : node_to.get_simulation().get_world().find_location(target_type, person); 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."); diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp index 0a9a254a6f..70cbe350ab 100644 --- a/cpp/tests/test_graph_abm.cpp +++ b/cpp/tests/test_graph_abm.cpp @@ -24,6 +24,7 @@ #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) @@ -81,27 +82,29 @@ TEST(TestGraphAbm, test_evolve_node) TEST(TestGraphAbm, test_apply_mobility) { - auto world_1 = mio::abm::World(size_t(1), 1); - world_1.parameters.get().set_multiple({mio::AgeGroup(0)}, true); + 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); - world_1.get_individualized_location(work_id_1).set_world_id(world_1.get_id()); - auto work_id_2 = world_1.add_location(mio::abm::LocationType::Work); - auto& work_2 = world_1.get_individualized_location(work_id_2); - work_2.set_world_id(2); - auto home_id = world_1.add_location(mio::abm::LocationType::Home); - world_1.get_individualized_location(home_id).set_world_id(world_1.get_id()); + auto home_id = world_1.add_location(mio::abm::LocationType::Home); + auto work_id_2 = world_2.add_location(mio::abm::LocationType::Work); + + EXPECT_EQ(work_id_1.world_id, 1); + EXPECT_EQ(work_id_2.world_id, 2); + auto& p1 = world_1.add_person(home_id, mio::AgeGroup(0)); auto& p2 = world_1.add_person(home_id, mio::AgeGroup(0)); p1.set_assigned_location(work_id_1); p2.set_assigned_location(work_id_2); p1.set_assigned_location(home_id); p2.set_assigned_location(home_id); - auto world_2 = mio::abm::World(world_1, 2); - // Deactivate persons in world 2 - world_2.change_activeness(p1.get_person_id()); - world_2.change_activeness(p2.get_person_id()); - auto t = mio::abm::TimePoint(0) + mio::abm::hours(6); + //copy persons to world 2 + world_2.copy_persons_from_other_world(world_1); + + auto& work_2 = world_2.get_individualized_location(work_id_2); + 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)); From 196275fb95fdd790aa46412a570e7aaa0316708f Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 18 Jul 2024 09:23:06 +0200 Subject: [PATCH 13/23] finx clang bug --- cpp/models/abm/world.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 4b47686599..cab8262d0d 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -354,7 +354,7 @@ class World for (auto& person : other.get_persons()) { auto new_person = Person(person.copy_person(person.get_location())); //copy assigned locations - for (auto type = 0; type < person.get_assigned_locations().size(); ++type) { + for (size_t type = size_t(0); type < person.get_assigned_locations().size(); ++type) { auto index = person.get_assigned_location_index(LocationType(type)); auto world_id = person.get_assigned_location_world_id(LocationType(type)); new_person.set_assigned_location(LocationId{index, LocationType(type), world_id}); From a5a4e88f411b8583fb40705689d5057e4838eefb Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 18 Jul 2024 10:13:52 +0200 Subject: [PATCH 14/23] abm minimal bug fix --- cpp/examples/abm_minimal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 0c063cf567..17fd3767bd 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -135,7 +135,7 @@ int main() person.set_assigned_location(hospital); person.set_assigned_location(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) { person.set_assigned_location(school); } if (person.get_age() == age_group_15_to_34 || person.get_age() == age_group_35_to_59) { From 8cce3908fec118d570153ef1675d1c730268a603 Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 18 Jul 2024 14:21:58 +0200 Subject: [PATCH 15/23] graph_abm example --- cpp/examples/CMakeLists.txt | 10 +- cpp/examples/graph_abm.cpp | 214 ++++++++++++++++++++++++++++++++++++ cpp/models/abm/person.cpp | 5 + cpp/models/abm/person.h | 6 + cpp/models/abm/world.h | 36 ++++-- 5 files changed, 258 insertions(+), 13 deletions(-) create mode 100644 cpp/examples/graph_abm.cpp 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/graph_abm.cpp b/cpp/examples/graph_abm.cpp new file mode 100644 index 0000000000..5ded098d16 --- /dev/null +++ b/cpp/examples/graph_abm.cpp @@ -0,0 +1,214 @@ +/* +* 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/abm.h" +#include +#include +#include + +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()) { + persons.push_back(person); + } + + 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()) { + persons.push_back(person); + } + + //Create locations for both worlds + //world 0 + auto event_w1 = world1.add_location(mio::abm::LocationType::SocialEvent); + world1.get_individualized_location(event_w1).get_infection_parameters().set(10); + auto hospital_w1 = world1.add_location(mio::abm::LocationType::Hospital); + world1.get_individualized_location(hospital_w1).get_infection_parameters().set(10); + auto icu_w1 = world1.add_location(mio::abm::LocationType::ICU); + world1.get_individualized_location(icu_w1).get_infection_parameters().set(5); + auto shop_w1 = world1.add_location(mio::abm::LocationType::BasicsShop); + world1.get_individualized_location(shop_w1).get_infection_parameters().set(20); + auto school_w1 = world1.add_location(mio::abm::LocationType::School); + world1.get_individualized_location(school_w1).get_infection_parameters().set(20); + auto work_w1 = world1.add_location(mio::abm::LocationType::Work); + world1.get_individualized_location(work_w1).get_infection_parameters().set(10); + //World 1 + auto event_w2 = world2.add_location(mio::abm::LocationType::SocialEvent); + world2.get_individualized_location(event_w2).get_infection_parameters().set(10); + auto hospital_w2 = world2.add_location(mio::abm::LocationType::Hospital); + world2.get_individualized_location(hospital_w2).get_infection_parameters().set(10); + auto icu_w2 = world2.add_location(mio::abm::LocationType::ICU); + world2.get_individualized_location(icu_w2).get_infection_parameters().set(5); + auto shop_w2 = world2.add_location(mio::abm::LocationType::BasicsShop); + world2.get_individualized_location(shop_w2).get_infection_parameters().set(20); + auto school_w2 = world2.add_location(mio::abm::LocationType::School); + world2.get_individualized_location(school_w2).get_infection_parameters().set(20); + auto work_w2 = world2.add_location(mio::abm::LocationType::Work); + world2.get_individualized_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::Person::RandomNumberGenerator(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)); + } + if (person.get_assigned_location_world_id(mio::abm::LocationType::Home) == world1.get_id()) { + person.set_assigned_location(event_w1); + person.set_assigned_location(shop_w1); + person.set_assigned_location(hospital_w1); + person.set_assigned_location(icu_w1); + if (person.get_age() == age_group_children) { + person.set_assigned_location(school_w1); + } + 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{90, 10}); + if (work_world == 1) { //person works in other world + person.set_assigned_location(work_w2); + //add person to edge parameters + params_e1.push_back(person.get_person_id()); + } + else { //person works in same world + person.set_assigned_location(work_w1); + } + } + } + else { + person.set_assigned_location(event_w2); + person.set_assigned_location(shop_w2); + person.set_assigned_location(hospital_w2); + person.set_assigned_location(icu_w2); + if (person.get_age() == age_group_children) { + person.set_assigned_location(school_w2); + } + 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{20, 80}); + if (work_world == 0) { //person works in other world + person.set_assigned_location(work_w1); + //add person to edge parameters + params_e2.push_back(person.get_person_id()); + } + else { //person works in same world + person.set_assigned_location(work_w2); + } + } + } + } + + //copy persons to both worlds + world1.set_persons(persons); + world2.set_persons(persons); + + return 0; +} \ No newline at end of file diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index e202826576..d558aabf47 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -222,6 +222,11 @@ uint32_t Person::get_person_id() return m_person_id; } +void Person::set_person_id(uint32_t id) +{ + m_person_id = id; +} + std::vector& Person::get_cells() { return m_cells; diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 06f29d59e9..5e63c6ba37 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -371,6 +371,12 @@ class Person */ uint32_t get_person_id(); + /** + * @brief Set the PersonID of the Person. + * The PersonID should correspond to the index in m_persons in world. + */ + void set_person_id(uint32_t id); + /** * @brief Get index of Cell%s of the Person. * @return A vector of all Cell indices the Person visits at the current Location. diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index cab8262d0d..a140c10849 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -346,21 +346,37 @@ class World } /** - * @brief Copy the persons from another World to this World. + * @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. * @param[in] other The World the Person%s are copied from. */ void copy_persons_from_other_world(const World& other) { - for (auto& person : other.get_persons()) { - auto new_person = Person(person.copy_person(person.get_location())); - //copy assigned locations - for (size_t type = size_t(0); type < person.get_assigned_locations().size(); ++type) { - auto index = person.get_assigned_location_index(LocationType(type)); - auto world_id = person.get_assigned_location_world_id(LocationType(type)); - new_person.set_assigned_location(LocationId{index, LocationType(type), world_id}); + for (auto& p : other.get_persons()) { + p.set_person_id(static_cast(m_persons.size())); + m_persons.push_back(std::make_unique(p.copy_person(p.get_location()))); + if (p.get_location().get_world_id() == m_id) { + m_activeness_statuses.push_back(true); } - m_persons.push_back(std::make_unique(new_person)); - if (person.get_location().get_world_id() == m_id) { + 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) + { + //first clear old person vector and corresponding activeness vector + m_persons.clear(); + m_activeness_statuses.clear(); + for (auto& p : persons) { + p.set_person_id(static_cast(m_persons.size())); + m_persons.emplace_back(std::make_unique(p.copy_person(p.get_location()))); + if (p.get_location().get_world_id() == m_id) { m_activeness_statuses.push_back(true); } else { From 2fc9026d95b3ee2f76a15ea49dd506ada6e61688 Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 19 Jul 2024 15:22:46 +0200 Subject: [PATCH 16/23] graph abm example --- cpp/examples/graph_abm.cpp | 53 ++++++++++++++++++- cpp/memilio/mobility/graph_simulation.h | 12 ++--- .../metapopulation_mobility_instant.h | 8 ++- cpp/models/abm/world.cpp | 3 +- cpp/models/abm/world.h | 6 +++ cpp/models/graph_abm/graph_abm_mobility.h | 7 ++- 6 files changed, 76 insertions(+), 13 deletions(-) diff --git a/cpp/examples/graph_abm.cpp b/cpp/examples/graph_abm.cpp index 5ded098d16..f26d2fd23d 100644 --- a/cpp/examples/graph_abm.cpp +++ b/cpp/examples/graph_abm.cpp @@ -19,10 +19,47 @@ */ #include "abm/abm.h" +#include "abm/infection_state.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/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{}; + std::map persons_per_infection_state; + auto t = sim.get_time(); + for (auto&& loc : sim.get_world().get_locations()) { + 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, loc.get_subpopulation(t, inf_state)}); + } + location_information.push_back( + std::make_tuple(mio::abm::LocationId{loc.get_index(), loc.get_type(), loc.get_world_id()}, + loc.get_number_persons(), persons_per_infection_state)); + } + return location_information; + } +}; + int main() { // This is an example with three age groups representing children, adults and seniors. @@ -171,7 +208,7 @@ int main() 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{90, 10}); + std::vector{0.9, 0.1}); if (work_world == 1) { //person works in other world person.set_assigned_location(work_w2); //add person to edge parameters @@ -193,7 +230,7 @@ int main() 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{20, 80}); + std::vector{0.2, 0.8}); if (work_world == 0) { //person works in other world person.set_assigned_location(work_w1); //add person to edge parameters @@ -210,5 +247,17 @@ int main() 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); + 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 4931662282..557dc1ffac 100644 --- a/cpp/memilio/mobility/graph_simulation.h +++ b/cpp/memilio/mobility/graph_simulation.h @@ -85,13 +85,13 @@ class GraphSimulationBase }; template , - class node_f = std::function> + class edge_f = void (*)(Timepoint, Timespan, typename Graph::EdgeProperty&, typename Graph::NodeProperty&, + typename Graph::NodeProperty&), + class node_f = void (*)(Timepoint, Timespan, typename Graph::NodeProperty&)> class GraphSimulation : public GraphSimulationBase { + using GraphSimulationBase::GraphSimulationBase; using Base = GraphSimulationBase; - using Base::GraphSimulationBase; public: void advance(Timepoint t_max = 1.0) @@ -255,8 +255,8 @@ class GraphSimulationStochastic 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/world.cpp b/cpp/models/abm/world.cpp index 0b3509fecd..7a4446eab0 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -102,7 +102,8 @@ void World::migration(TimePoint t, TimeSpan dt) auto target_type = rule(personal_rng, *person, t, dt, parameters); auto& target_location = find_location(target_type, *person); auto& current_location = person->get_location(); - if (m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { + if (person->get_assigned_location_world_id(target_type) == m_id && + m_testing_strategy.run_strategy(personal_rng, *person, target_location, t)) { if (target_location != current_location && target_location.get_number_persons() < target_location.get_capacity().persons) { bool wears_mask = person->apply_mask_intervention(personal_rng, target_location); diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index a140c10849..5ce8638cf5 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -370,6 +370,12 @@ class World */ void set_persons(std::vector& persons) { + //first remove all old persons from the locations + for (auto&& person : m_persons) { + if (person->get_location().get_world_id() == m_id) { + person->get_location().remove_person(*person); + } + } //first clear old person vector and corresponding activeness vector m_persons.clear(); m_activeness_statuses.clear(); diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 64dac753af..cf51617d64 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -165,9 +165,9 @@ class ABMMobilityParameters template class ABMMobilityEdge { - using MobilityRuleType = abm::LocationType (*)(const abm::Person&, abm::TimePoint, const abm::Parameters&); 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 @@ -271,7 +271,10 @@ void evolve_model(abm::TimePoint t, abm::TimeSpan dt, ABMSimulationNode -GraphSimulation, ABMMobilityEdge>, abm::TimePoint, abm::TimeSpan> +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) { From 0ece6beda507f2ddd7b8a3d3ce2e16d22a372f0b Mon Sep 17 00:00:00 2001 From: jubicker Date: Tue, 23 Jul 2024 08:32:45 +0200 Subject: [PATCH 17/23] fix merge conflicts --- cpp/examples/CMakeLists.txt | 7 +- cpp/models/abm/model_functions.cpp | 2 +- cpp/models/abm/person.cpp | 25 ++- cpp/models/abm/person.h | 23 ++- cpp/models/abm/world.cpp | 155 +++++++++--------- cpp/models/abm/world.h | 56 ++++--- cpp/models/graph_abm/mobility_rules.cpp | 2 +- cpp/tests/CMakeLists.txt | 3 +- cpp/tests/abm_helpers.cpp | 2 +- cpp/tests/test_abm_infection.cpp | 3 +- cpp/tests/test_abm_location.cpp | 8 +- cpp/tests/test_abm_lockdown_rules.cpp | 48 +++--- cpp/tests/test_abm_masks.cpp | 12 +- cpp/tests/test_abm_migration_rules.cpp | 47 +++--- cpp/tests/test_abm_person.cpp | 25 +-- cpp/tests/test_abm_world.cpp | 72 ++++---- .../memilio/simulation/abm.cpp | 4 +- 17 files changed, 263 insertions(+), 231 deletions(-) diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 897625f3f3..e321c7d966 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -112,10 +112,9 @@ 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}) - +# 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) 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 e4fe5de865..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) @@ -93,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 @@ -110,10 +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_location_world_ids[static_cast(type)] = location.get_world_id(); + m_assigned_location_world_ids[static_cast(type)] = world_id; } LocationId Person::get_assigned_location(LocationType type) const @@ -123,7 +125,7 @@ LocationId Person::get_assigned_location(LocationType type) const int Person::get_assigned_location_world_id(LocationType type) const { - return m_assigned_locations[static_cast(type)]; + return m_assigned_location_world_ids[static_cast(type)]; } bool Person::goes_to_work(TimePoint t, const Parameters& params) const @@ -193,11 +195,6 @@ PersonId Person::get_id() const return m_person_id; } -void Person::set_person_id(uint32_t id) -{ - m_person_id = id; -} - std::vector& Person::get_cells() { return m_cells; diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index 86904a6e23..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. @@ -282,10 +290,10 @@ class Person PersonId get_id() const; /** - * @brief Set the PersonID of the Person. + * @brief Set the PersonId of the Person. * The PersonID should correspond to the index in m_persons in world. */ - void set_person_id(uint32_t id); + void set_id(PersonId id); /** * @brief Get index of Cell%s of the Person. @@ -451,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. diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index a2876896ce..e00776788f 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -39,7 +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.index]->set_world_id(m_id); + m_locations[id.get()].set_world_id(m_id); // mark caches for rebuild m_is_local_population_cache_valid = false; @@ -51,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) @@ -65,7 +65,7 @@ PersonId World::add_person(Person&& person) 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()]; @@ -73,17 +73,17 @@ PersonId World::add_person(Person&& person) return new_id; } -Person& World::add_external_person(Location& loc, AgeGroup age) -{ - assert(age.get() < parameters.get_num_groups()); - uint32_t person_id = static_cast(m_persons.size()); - m_persons.push_back(std::make_unique(m_rng, loc, age, person_id)); - m_activeness_statuses.push_back(false); - auto& person = *m_persons.back(); - person.set_assigned_location(m_cemetery_id); - loc.add_person(person); - return person; -} +// Person& World::add_external_person(Location& loc, AgeGroup age) +// { +// assert(age.get() < parameters.get_num_groups()); +// uint32_t person_id = static_cast(m_persons.size()); +// m_persons.push_back(std::make_unique(m_rng, loc, age, person_id)); +// m_activeness_statuses.push_back(false); +// auto& person = *m_persons.back(); +// person.set_assigned_location(m_cemetery_id); +// loc.add_person(person); +// return person; +// } void World::evolve(TimePoint t, TimeSpan dt) { @@ -99,7 +99,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); + } } } @@ -108,74 +110,79 @@ 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)); } } -} -// check if a person makes a trip -bool weekend = t.is_weekend(); -size_t num_trips = m_trip_list.num_trips(weekend); - -if (num_trips != 0) { - while (m_trip_list.get_current_index() < num_trips && - m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds()) { - auto& trip = m_trip_list.get_next_trip(weekend); - auto& person = get_person(trip.person_id); - auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); - if (!person.is_in_quarantine(t, parameters) && person.get_infection_state(t) != InfectionState::Dead) { - auto& target_location = get_location(trip.migration_destination); - if (m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { - person.apply_mask_intervention(personal_rng, target_location); - migrate(person.get_id(), target_location.get_id(), trip.trip_mode); + // check if a person makes a trip + bool weekend = t.is_weekend(); + size_t num_trips = m_trip_list.num_trips(weekend); + + if (num_trips != 0) { + while (m_trip_list.get_current_index() < num_trips && + m_trip_list.get_next_trip_time(weekend).seconds() < (t + dt).time_since_midnight().seconds()) { + auto& trip = m_trip_list.get_next_trip(weekend); + auto& person = get_person(trip.person_id); + auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); + if (!person.is_in_quarantine(t, parameters) && person.get_infection_state(t) != InfectionState::Dead) { + auto& target_location = get_location(trip.migration_destination); + if (m_testing_strategy.run_strategy(personal_rng, person, target_location, t)) { + person.apply_mask_intervention(personal_rng, target_location); + migrate(person.get_id(), target_location.get_id(), trip.trip_mode); + } } + m_trip_list.increase_index(); } - m_trip_list.increase_index(); } -} -if (((t).days() < std::floor((t + dt).days()))) { - m_trip_list.reset_index(); -} + if (((t).days() < std::floor((t + dt).days()))) { + m_trip_list.reset_index(); + } } void World::build_compute_local_population_cache() const diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 3aff91ef0b..f8b749bc45 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -73,16 +73,17 @@ 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_trip_list() , m_use_migration_rules(true) , m_cemetery_id(add_location(LocationType::Cemetery)) + , m_id(world_id) { 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() @@ -92,6 +93,7 @@ class World , m_exposure_caches_need_rebuild(true) , 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) @@ -99,6 +101,7 @@ class World , m_migration_rules(other.m_migration_rules) , m_cemetery_id(other.m_cemetery_id) , m_rng(other.m_rng) + , m_id(world_id) { } World& operator=(const World&) = default; @@ -189,14 +192,14 @@ class World */ PersonId add_person(Person&& person); - /** - * @brief Add an external Person i.e. a Person whoseHome location is in another World to the World. - * Only used for abm graph model. - * @param[in] loc Initial Location of the Person - * @param[in] age AgeGroup of the Person - * @return Reference to the newly created Person - */ - Person& add_external_person(Location& loc, AgeGroup age); + // /** + // * @brief Add an external Person i.e. a Person whoseHome location is in another World to the World. + // * Only used for abm graph model. + // * @param[in] loc Initial Location of the Person + // * @param[in] age AgeGroup of the Person + // * @return Reference to the newly created Person + // */ + // Person& add_external_person(Location& loc, AgeGroup age); /** * @brief Get a range of all Location%s in the World. @@ -233,7 +236,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); } /** @@ -486,15 +489,20 @@ class World /** * @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 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()) { - p.set_person_id(static_cast(m_persons.size())); - m_persons.push_back(std::make_unique(p.copy_person(p.get_location()))); - if (p.get_location().get_world_id() == m_id) { + 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 { @@ -509,19 +517,19 @@ class World */ void set_persons(std::vector& persons) { - //first remove all old persons from the locations - for (auto&& person : m_persons) { - if (person->get_location().get_world_id() == m_id) { - person->get_location().remove_person(*person); - } - } + 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) { - p.set_person_id(static_cast(m_persons.size())); - m_persons.emplace_back(std::make_unique(p.copy_person(p.get_location()))); - if (p.get_location().get_world_id() == m_id) { + 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 { diff --git a/cpp/models/graph_abm/mobility_rules.cpp b/cpp/models/graph_abm/mobility_rules.cpp index 0d7c7df3da..16b536fd93 100644 --- a/cpp/models/graph_abm/mobility_rules.cpp +++ b/cpp/models/graph_abm/mobility_rules.cpp @@ -26,7 +26,7 @@ namespace mio abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, const abm::Parameters& params) { - abm::LocationType current_loc = person.get_location().get_type(); + 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) && diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 4620468e6d..63994d2f3e 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -72,7 +72,8 @@ set(TESTSOURCES test_lct_secir.cpp test_lct_initializer_flows.cpp test_ad.cpp - test_graph_abm.cpp + + # test_graph_abm.cpp ) if(MEMILIO_HAS_JSONCPP) 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/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); From b773193555162f8e3582e877197b40fcdbf9ede7 Mon Sep 17 00:00:00 2001 From: jubicker Date: Tue, 23 Jul 2024 13:16:17 +0200 Subject: [PATCH 18/23] fix test --- cpp/models/abm/world.cpp | 4 +- cpp/models/abm/world.h | 15 +++- cpp/models/graph_abm/graph_abm_mobility.h | 40 +++++++--- cpp/tests/CMakeLists.txt | 3 +- cpp/tests/test_graph_abm.cpp | 91 ++++++++++++----------- 5 files changed, 93 insertions(+), 60 deletions(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index e00776788f..f41a10a489 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -198,7 +198,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 } diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index f8b749bc45..d0c6e183aa 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -480,11 +480,11 @@ class World /** * @brief Flip activeness status of a person in the world. - * @param[in] person_id Person whose activeness status is fipped. + * @param[in] person_id PersonId of Person whose activeness status is fipped. */ - void change_activeness(uint32_t person_id) + void change_activeness(PersonId person_id) { - m_activeness_statuses[person_id] = !m_activeness_statuses[person_id]; + m_activeness_statuses[person_id.get()] = !m_activeness_statuses[person_id.get()]; } /** @@ -538,6 +538,15 @@ class World } } + /** + * @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. diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index cf51617d64..6e2229c1f0 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -26,6 +26,8 @@ #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 @@ -203,23 +205,37 @@ class ABMMobilityEdge { // 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& person = node_from.get_simulation().get_world().get_persons()[p]; - auto& params = node_from.get_simulation().get_world().parameters; - auto& current_location = person.get_location(); + 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, t, params); - auto target_world_id = person.get_assigned_location_world_id(target_type); - abm::Location& target_location = - (target_world_id == node_from.get_simulation().get_world().get_id()) - ? node_from.get_simulation().get_world().find_location(target_type, person) - : node_to.get_simulation().get_world().find_location(target_type, person); + 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_location != current_location && - target_location.get_number_persons() < target_location.get_capacity().persons) { - person.migrate_to(target_location); + 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(); // change activeness status for commuted person node_to.get_simulation().get_world().change_activeness(p); node_from.get_simulation().get_world().change_activeness(p); diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 63994d2f3e..4620468e6d 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -72,8 +72,7 @@ set(TESTSOURCES test_lct_secir.cpp test_lct_initializer_flows.cpp test_ad.cpp - - # test_graph_abm.cpp + test_graph_abm.cpp ) if(MEMILIO_HAS_JSONCPP) diff --git a/cpp/tests/test_graph_abm.cpp b/cpp/tests/test_graph_abm.cpp index 70cbe350ab..8d26e21648 100644 --- a/cpp/tests/test_graph_abm.cpp +++ b/cpp/tests/test_graph_abm.cpp @@ -29,27 +29,30 @@ TEST(TestGraphAbm, test_activessness) { - auto world = mio::abm::World(size_t(1)); - world.parameters.get().set_multiple({mio::AgeGroup(0)}, true); + 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& p1 = world.add_person(home_id, mio::AgeGroup(0)); - auto& p2 = world.add_person(home_id, mio::AgeGroup(0)); - p1.set_assigned_location(work_id); - p2.set_assigned_location(work_id); - p1.set_assigned_location(home_id); - p2.set_assigned_location(home_id); - - auto& home = world.get_individualized_location(home_id); - auto& work = world.get_individualized_location(work_id); - - EXPECT_EQ(p1.get_location(), home); - EXPECT_EQ(p2.get_location(), 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_person_id()); - EXPECT_EQ(world.get_activeness_statuses()[p1.get_person_id()], false); - EXPECT_EQ(world.get_activeness_statuses()[p2.get_person_id()], true); + 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); @@ -57,8 +60,8 @@ TEST(TestGraphAbm, test_activessness) world.evolve(t, dt); //inactive persons do not move - EXPECT_EQ(p1.get_location(), home); - EXPECT_EQ(p2.get_location(), work); + EXPECT_EQ(p1.get_location(), home_id); + EXPECT_EQ(p2.get_location(), work_id); } struct MockHistory { @@ -89,40 +92,44 @@ TEST(TestGraphAbm, test_apply_mobility) 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); - - EXPECT_EQ(work_id_1.world_id, 1); - EXPECT_EQ(work_id_2.world_id, 2); - - auto& p1 = world_1.add_person(home_id, mio::AgeGroup(0)); - auto& p2 = world_1.add_person(home_id, mio::AgeGroup(0)); - p1.set_assigned_location(work_id_1); - p2.set_assigned_location(work_id_2); - p1.set_assigned_location(home_id); - p2.set_assigned_location(home_id); + 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& work_2 = world_2.get_individualized_location(work_id_2); - auto t = mio::abm::TimePoint(0) + mio::abm::hours(6); + 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_person_id()}, {&mio::apply_commuting}); + mio::ABMMobilityEdge edge({p2.get_id().get()}, {&mio::apply_commuting}); edge.apply_mobility(node1, node2, t); - EXPECT_EQ(work_2.get_number_persons(), 1); - EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], true); - EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], false); - EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], false); - EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], true); + 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(work_2.get_number_persons(), 0); - EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], true); - EXPECT_EQ(node1.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], true); - EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p1.get_person_id()], false); - EXPECT_EQ(node2.get_simulation().get_world().get_activeness_statuses()[p2.get_person_id()], false); + 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 From b4d44d56c9e8dc969a230f3c3e2d6238d4319905 Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 25 Jul 2024 10:22:55 +0200 Subject: [PATCH 19/23] graph abm example --- cpp/examples/CMakeLists.txt | 7 +- cpp/examples/graph_abm.cpp | 87 +++++++++++++---------- cpp/models/abm/world.cpp | 26 +++---- cpp/models/abm/world.h | 12 ++-- cpp/models/graph_abm/graph_abm_mobility.h | 23 ++++-- cpp/models/graph_abm/mobility_rules.cpp | 25 ++++++- 6 files changed, 115 insertions(+), 65 deletions(-) diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index e321c7d966..897625f3f3 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -112,9 +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}) +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) diff --git a/cpp/examples/graph_abm.cpp b/cpp/examples/graph_abm.cpp index f26d2fd23d..a107be850d 100644 --- a/cpp/examples/graph_abm.cpp +++ b/cpp/examples/graph_abm.cpp @@ -18,10 +18,12 @@ * limitations under the License. */ -#include "abm/abm.h" +#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" @@ -41,20 +43,22 @@ struct Logger : mio::LogAlways { * - The total number of Persons at the location * - A map containing the number of Persons per InfectionState at the location */ - using Type = std::vector>>; + using Type = std::vector>>; static Type log(const mio::abm::Simulation& sim) { Type location_information{}; - std::map persons_per_infection_state; 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, loc.get_subpopulation(t, inf_state)}); + 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(mio::abm::LocationId{loc.get_index(), loc.get_type(), loc.get_world_id()}, - loc.get_number_persons(), persons_per_infection_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; } @@ -131,10 +135,12 @@ int main() //add persons from world 0 to vector for (auto& person : world1.get_persons()) { - persons.push_back(person); + 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); @@ -151,36 +157,37 @@ int main() //add persons from world 1 to vector for (auto& person : world2.get_persons()) { - persons.push_back(person); + 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_individualized_location(event_w1).get_infection_parameters().set(10); + world1.get_location(event_w1).get_infection_parameters().set(10); auto hospital_w1 = world1.add_location(mio::abm::LocationType::Hospital); - world1.get_individualized_location(hospital_w1).get_infection_parameters().set(10); + world1.get_location(hospital_w1).get_infection_parameters().set(10); auto icu_w1 = world1.add_location(mio::abm::LocationType::ICU); - world1.get_individualized_location(icu_w1).get_infection_parameters().set(5); + world1.get_location(icu_w1).get_infection_parameters().set(5); auto shop_w1 = world1.add_location(mio::abm::LocationType::BasicsShop); - world1.get_individualized_location(shop_w1).get_infection_parameters().set(20); + world1.get_location(shop_w1).get_infection_parameters().set(20); auto school_w1 = world1.add_location(mio::abm::LocationType::School); - world1.get_individualized_location(school_w1).get_infection_parameters().set(20); + world1.get_location(school_w1).get_infection_parameters().set(20); auto work_w1 = world1.add_location(mio::abm::LocationType::Work); - world1.get_individualized_location(work_w1).get_infection_parameters().set(10); + world1.get_location(work_w1).get_infection_parameters().set(10); //World 1 auto event_w2 = world2.add_location(mio::abm::LocationType::SocialEvent); - world2.get_individualized_location(event_w2).get_infection_parameters().set(10); + world2.get_location(event_w2).get_infection_parameters().set(10); auto hospital_w2 = world2.add_location(mio::abm::LocationType::Hospital); - world2.get_individualized_location(hospital_w2).get_infection_parameters().set(10); + world2.get_location(hospital_w2).get_infection_parameters().set(10); auto icu_w2 = world2.add_location(mio::abm::LocationType::ICU); - world2.get_individualized_location(icu_w2).get_infection_parameters().set(5); + world2.get_location(icu_w2).get_infection_parameters().set(5); auto shop_w2 = world2.add_location(mio::abm::LocationType::BasicsShop); - world2.get_individualized_location(shop_w2).get_infection_parameters().set(20); + world2.get_location(shop_w2).get_infection_parameters().set(20); auto school_w2 = world2.add_location(mio::abm::LocationType::School); - world2.get_individualized_location(school_w2).get_infection_parameters().set(20); + world2.get_location(school_w2).get_infection_parameters().set(20); auto work_w2 = world2.add_location(mio::abm::LocationType::Work); - world2.get_individualized_location(work_w2).get_infection_parameters().set(10); + 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); @@ -192,52 +199,53 @@ int main() 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::Person::RandomNumberGenerator(mio::thread_local_rng(), person); + 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(event_w1); - person.set_assigned_location(shop_w1); - person.set_assigned_location(hospital_w1); - person.set_assigned_location(icu_w1); + 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(school_w1); + 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(work_w2); + person.set_assigned_location(mio::abm::LocationType::Work, work_w2, world2.get_id()); //add person to edge parameters - params_e1.push_back(person.get_person_id()); + params_e1.push_back(person.get_id().get()); } else { //person works in same world - person.set_assigned_location(work_w1); + person.set_assigned_location(mio::abm::LocationType::Work, work_w1, world1.get_id()); } } } else { - person.set_assigned_location(event_w2); - person.set_assigned_location(shop_w2); - person.set_assigned_location(hospital_w2); - person.set_assigned_location(icu_w2); + 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(school_w2); + 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(work_w1); + person.set_assigned_location(mio::abm::LocationType::Work, work_w1, world1.get_id()); //add person to edge parameters - params_e2.push_back(person.get_person_id()); + params_e2.push_back(person.get_id().get()); } else { //person works in same world - person.set_assigned_location(work_w2); + person.set_assigned_location(mio::abm::LocationType::Work, work_w2, world2.get_id()); } } } @@ -259,5 +267,8 @@ int main() 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/models/abm/world.cpp b/cpp/models/abm/world.cpp index f41a10a489..d7a663dbd6 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -73,18 +73,6 @@ PersonId World::add_person(Person&& person) return new_id; } -// Person& World::add_external_person(Location& loc, AgeGroup age) -// { -// assert(age.get() < parameters.get_num_groups()); -// uint32_t person_id = static_cast(m_persons.size()); -// m_persons.push_back(std::make_unique(m_rng, loc, age, person_id)); -// m_activeness_statuses.push_back(false); -// auto& person = *m_persons.back(); -// person.set_assigned_location(m_cemetery_id); -// loc.add_person(person); -// return person; -// } - void World::evolve(TimePoint t, TimeSpan dt) { begin_step(t, dt); @@ -96,10 +84,16 @@ void World::evolve(TimePoint t, TimeSpan dt) void World::interaction(TimePoint t, TimeSpan dt) { + if (t.days() == 14) { + std::cout << "stop\n"; + } 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) { if (m_activeness_statuses[person_id]) { + if (person_id == 41 && t.days() == 14) { + std::cout << "person= " << person_id << std::endl; + } interact(person_id, t, dt); } } @@ -256,9 +250,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 d0c6e183aa..3097e33ce9 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -75,10 +75,10 @@ class World */ 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)) - , m_id(world_id) { parameters = params; } @@ -91,6 +91,7 @@ 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) @@ -101,7 +102,6 @@ class World , m_migration_rules(other.m_migration_rules) , m_cemetery_id(other.m_cemetery_id) , m_rng(other.m_rng) - , m_id(world_id) { } World& operator=(const World&) = default; @@ -380,7 +380,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; }); } @@ -485,6 +486,9 @@ class World void change_activeness(PersonId person_id) { m_activeness_statuses[person_id.get()] = !m_activeness_statuses[person_id.get()]; + if (get_person(person_id).get_location_world_id() != m_id && m_activeness_statuses[person_id.get()]) { + std::cout << "Problem\n"; + } } /** @@ -584,6 +588,7 @@ 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 @@ -598,7 +603,6 @@ class World m_migration_rules; ///< Rules that govern the migration between Location%s. LocationId m_cemetery_id; // Central cemetery for all dead persons. RandomNumberGenerator m_rng; ///< Global random number generator - int m_id; ///< World id. Is only used for abm graph model or hybrid model. }; } // namespace abm diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 6e2229c1f0..7b90d7bf5d 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -53,7 +53,7 @@ class ABMSimulationNode } /** - *@brief get abm simulation in this node. + *@brief Get abm simulation in this node. */ Sim& get_simulation() { @@ -64,6 +64,18 @@ class ABMSimulationNode 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) @@ -236,9 +248,11 @@ class ABMMobilityEdge // invalidate both worlds' cache node_to.get_simulation().get_world().invalidate_cache(); node_from.get_simulation().get_world().invalidate_cache(); - // change activeness status for commuted person - node_to.get_simulation().get_world().change_activeness(p); - node_from.get_simulation().get_world().change_activeness(p); + 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; } @@ -275,6 +289,7 @@ void apply_mobility(abm::TimePoint t, abm::TimeSpan /*dt*/, ABMMobilityEdge void evolve_model(abm::TimePoint t, abm::TimeSpan dt, ABMSimulationNode& node) { + std::cout << "t= " << t.days() << std::endl; node.evolve(t, dt); } diff --git a/cpp/models/graph_abm/mobility_rules.cpp b/cpp/models/graph_abm/mobility_rules.cpp index 16b536fd93..cab23286c8 100644 --- a/cpp/models/graph_abm/mobility_rules.cpp +++ b/cpp/models/graph_abm/mobility_rules.cpp @@ -20,6 +20,7 @@ #include "graph_abm/mobility_rules.h" #include "abm/location.h" +#include "abm/infection_state.h" namespace mio { @@ -34,7 +35,29 @@ abm::LocationType apply_commuting(const abm::Person& person, abm::TimePoint t, c return abm::LocationType::Work; } - // agents are sent home or to work every time this function is called i.e. if it is called too often they will be sent to work multiple times + //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 From ae95301abf156ed782d45afc5f1958879cfa5bf7 Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 25 Jul 2024 10:31:01 +0200 Subject: [PATCH 20/23] remove unnecessary comment --- cpp/models/abm/world.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index d0c6e183aa..1724014f61 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -192,15 +192,6 @@ class World */ PersonId add_person(Person&& person); - // /** - // * @brief Add an external Person i.e. a Person whoseHome location is in another World to the World. - // * Only used for abm graph model. - // * @param[in] loc Initial Location of the Person - // * @param[in] age AgeGroup of the Person - // * @return Reference to the newly created Person - // */ - // Person& add_external_person(Location& loc, AgeGroup age); - /** * @brief Get a range of all Location%s in the World. * @return A range of all Location%s. From e22009b1bbe9c7c5bf4d74ac9f22dbc3c4b8f75d Mon Sep 17 00:00:00 2001 From: jubicker Date: Thu, 25 Jul 2024 10:39:15 +0200 Subject: [PATCH 21/23] fix bug --- cpp/models/abm/world.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 37025127f4..993e2be59a 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -61,10 +61,10 @@ class World */ 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)) - , m_id(id) { assert(num_agegroups < MAX_NUM_AGE_GROUPS && "MAX_NUM_AGE_GROUPS exceeded."); } From 11c3ae861593aee7ce43bd83e7d1a4c6c4af6131 Mon Sep 17 00:00:00 2001 From: jubicker Date: Fri, 26 Jul 2024 09:24:29 +0200 Subject: [PATCH 22/23] delete dbugging comments --- cpp/models/abm/world.cpp | 6 ------ cpp/models/abm/world.h | 3 --- cpp/models/graph_abm/graph_abm_mobility.h | 1 - 3 files changed, 10 deletions(-) diff --git a/cpp/models/abm/world.cpp b/cpp/models/abm/world.cpp index d7a663dbd6..c9ed70e5bd 100755 --- a/cpp/models/abm/world.cpp +++ b/cpp/models/abm/world.cpp @@ -84,16 +84,10 @@ void World::evolve(TimePoint t, TimeSpan dt) void World::interaction(TimePoint t, TimeSpan dt) { - if (t.days() == 14) { - std::cout << "stop\n"; - } 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) { if (m_activeness_statuses[person_id]) { - if (person_id == 41 && t.days() == 14) { - std::cout << "person= " << person_id << std::endl; - } interact(person_id, t, dt); } } diff --git a/cpp/models/abm/world.h b/cpp/models/abm/world.h index 37025127f4..eaa761b3a6 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -477,9 +477,6 @@ class World void change_activeness(PersonId person_id) { m_activeness_statuses[person_id.get()] = !m_activeness_statuses[person_id.get()]; - if (get_person(person_id).get_location_world_id() != m_id && m_activeness_statuses[person_id.get()]) { - std::cout << "Problem\n"; - } } /** diff --git a/cpp/models/graph_abm/graph_abm_mobility.h b/cpp/models/graph_abm/graph_abm_mobility.h index 7b90d7bf5d..deef17d8f8 100644 --- a/cpp/models/graph_abm/graph_abm_mobility.h +++ b/cpp/models/graph_abm/graph_abm_mobility.h @@ -289,7 +289,6 @@ void apply_mobility(abm::TimePoint t, abm::TimeSpan /*dt*/, ABMMobilityEdge void evolve_model(abm::TimePoint t, abm::TimeSpan dt, ABMSimulationNode& node) { - std::cout << "t= " << t.days() << std::endl; node.evolve(t, dt); } From fdad4dae1379ccebcb09988ed68b84728eb06064 Mon Sep 17 00:00:00 2001 From: jubicker Date: Wed, 31 Jul 2024 11:49:45 +0200 Subject: [PATCH 23/23] benchmark inactive persons --- cpp/benchmarks/abm.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++ cpp/models/abm/world.h | 5 ++++ 2 files changed, 68 insertions(+) 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/models/abm/world.h b/cpp/models/abm/world.h index 7705045de2..0af2a7402d 100644 --- a/cpp/models/abm/world.h +++ b/cpp/models/abm/world.h @@ -479,6 +479,11 @@ class World 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.