From ab4b8332948d8fb60589aabb355baf493c154bf5 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:40:04 +0100 Subject: [PATCH 01/10] change personal rng so that it accepts the model's rng. remove superfluous members from person. add a reset_rng function to model. --- cpp/benchmarks/abm.cpp | 2 +- cpp/examples/abm_history_object.cpp | 2 +- cpp/examples/abm_minimal.cpp | 2 +- cpp/examples/abm_parameter_study.cpp | 15 +++--- cpp/examples/graph_abm.cpp | 4 +- cpp/models/abm/model.cpp | 4 +- cpp/models/abm/model.h | 21 +++++++- cpp/models/abm/person.cpp | 3 -- cpp/models/abm/person.h | 24 +-------- cpp/models/abm/personal_rng.cpp | 4 +- cpp/models/abm/personal_rng.h | 3 +- cpp/models/graph_abm/graph_abmodel.h | 4 +- cpp/tests/abm_helpers.cpp | 2 +- cpp/tests/test_abm_location.cpp | 4 +- cpp/tests/test_abm_lockdown_rules.cpp | 16 +++--- cpp/tests/test_abm_masks.cpp | 4 +- cpp/tests/test_abm_mobility_rules.cpp | 68 ++++++++++++++----------- cpp/tests/test_abm_model.cpp | 8 +-- cpp/tests/test_abm_person.cpp | 14 ++--- cpp/tests/test_abm_serialization.cpp | 1 - cpp/tests/test_abm_testing_strategy.cpp | 22 ++++---- 21 files changed, 113 insertions(+), 114 deletions(-) diff --git a/cpp/benchmarks/abm.cpp b/cpp/benchmarks/abm.cpp index ed60379588..54159bd4a9 100644 --- a/cpp/benchmarks/abm.cpp +++ b/cpp/benchmarks/abm.cpp @@ -68,7 +68,7 @@ mio::abm::Simulation<> make_simulation(size_t num_persons, std::initializer_list //infections and masks for (auto& person : model.get_persons()) { - auto prng = mio::abm::PersonalRandomNumberGenerator(person); + auto prng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); //some % of people are infected, large enough to have some infection activity without everyone dying auto pct_infected = 0.05; if (mio::UniformDistribution::get_instance()(prng, 0.0, 1.0) < pct_infected) { diff --git a/cpp/examples/abm_history_object.cpp b/cpp/examples/abm_history_object.cpp index e9b75cb358..939bfa26d0 100644 --- a/cpp/examples/abm_history_object.cpp +++ b/cpp/examples/abm_history_object.cpp @@ -143,7 +143,7 @@ int main() // The infection states are chosen randomly. auto persons = model.get_persons(); for (auto& person : persons) { - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); mio::abm::InfectionState infection_state = (mio::abm::InfectionState)(rand() % ((uint32_t)mio::abm::InfectionState::Count - 1)); if (infection_state != mio::abm::InfectionState::Susceptible) diff --git a/cpp/examples/abm_minimal.cpp b/cpp/examples/abm_minimal.cpp index 643a17b1df..685cfb6e17 100644 --- a/cpp/examples/abm_minimal.cpp +++ b/cpp/examples/abm_minimal.cpp @@ -121,7 +121,7 @@ int main() for (auto& person : model.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), infection_distribution)); - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), model.parameters, start_date, infection_state)); diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index 8446465c5f..57b7f60322 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -38,7 +38,7 @@ constexpr size_t num_age_groups = 4; /// An ABM setup taken from abm_minimal.cpp. -mio::abm::Model make_model(const mio::RandomNumberGenerator& rng) +mio::abm::Model make_model() { const auto age_group_0_to_4 = mio::AgeGroup(0); @@ -46,8 +46,7 @@ mio::abm::Model make_model(const mio::RandomNumberGenerator& rng) const auto age_group_15_to_34 = mio::AgeGroup(2); const auto age_group_35_to_59 = mio::AgeGroup(3); // Create the model with 4 age groups. - auto model = mio::abm::Model(num_age_groups); - model.get_rng() = rng; + auto model = mio::abm::Model(num_age_groups); // Set same infection parameter for all age groups. For example, the incubation period is log normally distributed with parameters 4 and 1. model.parameters.get() = mio::ParameterDistributionLogNormal(4., 1.); @@ -133,7 +132,7 @@ mio::abm::Model make_model(const mio::RandomNumberGenerator& rng) for (auto& person : model.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), infection_distribution)); - auto person_rng = mio::abm::PersonalRandomNumberGenerator(person); + auto person_rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(person_rng, mio::abm::VirusVariant::Wildtype, person.get_age(), model.parameters, start_date, infection_state)); @@ -181,7 +180,7 @@ int main() // Note that the study for the ABM currently does not make use of the arguments "parameters" or "dt", as we create // a new model for each simulation. Hence we set both arguments to 0. // This is mostly due to https://github.com/SciCompMod/memilio/issues/1400 - mio::ParameterStudy study(0, t0, tmax, mio::abm::TimeSpan(0), num_runs); + mio::ParameterStudy study(make_model(), t0, tmax, mio::abm::TimeSpan(0), num_runs); // Optional: set seeds to get reproducable results // study.get_rng().seed({12341234, 53456, 63451, 5232576, 84586, 52345}); @@ -193,8 +192,10 @@ int main() } auto ensemble_results = study.run( - [](auto, auto t0_, auto, size_t) { - return mio::abm::ResultSimulation(make_model(mio::thread_local_rng()), t0_); + [](auto&& model, auto t0_, auto, size_t) { + auto copy = model; + copy.reset_rng(mio::thread_local_rng().get_seeds()); + return mio::abm::ResultSimulation(std::move(copy), t0_); }, [result_dir](auto&& sim, auto&& run_idx) { auto interpolated_result = mio::interpolate_simulation_result(sim.get_result()); diff --git a/cpp/examples/graph_abm.cpp b/cpp/examples/graph_abm.cpp index dedc28b60e..fa19c69153 100644 --- a/cpp/examples/graph_abm.cpp +++ b/cpp/examples/graph_abm.cpp @@ -201,7 +201,7 @@ int main() for (auto& person : model1.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( mio::DiscreteDistribution::get_instance()(model1.get_rng(), infection_distribution_m1)); - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(model1.get_rng(), person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), model1.parameters, start_date, infection_state)); @@ -231,7 +231,7 @@ int main() for (auto& person : model2.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( mio::DiscreteDistribution::get_instance()(model2.get_rng(), infection_distribution_m2)); - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(model2.get_rng(), person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(rng, mio::abm::VirusVariant::Wildtype, person.get_age(), model2.parameters, start_date, infection_state)); diff --git a/cpp/models/abm/model.cpp b/cpp/models/abm/model.cpp index 04aeaa8b4e..4561dfb4fa 100755 --- a/cpp/models/abm/model.cpp +++ b/cpp/models/abm/model.cpp @@ -108,7 +108,7 @@ void Model::perform_mobility(TimePoint t, TimeSpan dt) for (uint32_t person_index = 0; person_index < num_persons; ++person_index) { if (m_activeness_statuses[person_index]) { Person& person = m_persons[person_index]; - auto personal_rng = PersonalRandomNumberGenerator(person); + auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); auto try_mobility_rule = [&](auto rule) -> bool { // run mobility rule and check if change of location can actually happen @@ -186,7 +186,7 @@ void Model::perform_mobility(TimePoint t, TimeSpan dt) m_trip_list.increase_index()) { auto& trip = m_trip_list.get_next_trip(); auto& person = get_person(trip.person_id); - auto personal_rng = PersonalRandomNumberGenerator(person); + auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); // skip the trip if the person is in quarantine or is dead if (person.is_in_quarantine(t, parameters) || person.get_infection_state(t) == InfectionState::Dead) { continue; diff --git a/cpp/models/abm/model.h b/cpp/models/abm/model.h index a8049a158d..7126cd5f30 100644 --- a/cpp/models/abm/model.h +++ b/cpp/models/abm/model.h @@ -339,6 +339,25 @@ class Model return m_rng; } + /** + * @brief Sets the RNG counters of the model and all persons to 0. + * @param[in] seeds Optional parameter that can be used to overwrite the current seed. + * @{ + */ + void reset_rng() + { + m_rng.set_counter(Counter(0)); + for (Person& person : get_persons()) { + person.get_rng_counter() = Counter(0); + } + } + void reset_rng(const std::vector& seeds) + { + m_rng.seed(seeds); + reset_rng(); + } + /** @} */ + /** * Get the model id. Is only relevant for graph abm or hybrid model. * @return The model id @@ -609,7 +628,7 @@ class Model compute_exposure_caches(t, dt); m_are_exposure_caches_valid = true; } - auto personal_rng = PersonalRandomNumberGenerator(person); + auto personal_rng = PersonalRandomNumberGenerator(m_rng, person); auto location = person.get_location(); mio::abm::interact(personal_rng, person, get_location(location), m_local_population_by_age_cache[location.get()], m_air_exposure_rates_cache[location.get()], diff --git a/cpp/models/abm/person.cpp b/cpp/models/abm/person.cpp index faff3aa73c..5087c2f5db 100755 --- a/cpp/models/abm/person.cpp +++ b/cpp/models/abm/person.cpp @@ -49,8 +49,6 @@ Person::Person(mio::RandomNumberGenerator& rng, LocationType location_type, Loca , m_test_results({TestType::Count}, TestResult()) , m_assigned_location_model_ids((int)LocationType::Count) , m_person_id(person_id) - , m_rng_key(rng.get_key()) - , m_rng_index(static_cast(person_id.get())) { m_random_workgroup = UniformDistribution::get_instance()(rng); m_random_schoolgroup = UniformDistribution::get_instance()(rng); @@ -62,7 +60,6 @@ Person::Person(const Person& other, PersonId person_id) : Person(other) { m_person_id = person_id; - m_rng_index = static_cast(person_id.get()); } bool Person::is_infected(TimePoint t) const diff --git a/cpp/models/abm/person.h b/cpp/models/abm/person.h index a4e0c8da81..4d741c99eb 100755 --- a/cpp/models/abm/person.h +++ b/cpp/models/abm/person.h @@ -23,7 +23,6 @@ #include "abm/infection.h" #include "abm/infection_state.h" #include "abm/location_id.h" -#include "abm/location.h" #include "abm/location_type.h" #include "abm/parameters.h" #include "abm/person_id.h" @@ -398,24 +397,6 @@ class Person return m_rng_counter; } - /** - * @brief Get this Person's index that is used for the RandomNumberGenerator. - * @see mio::abm::PersonalRandomNumberGenerator. - */ - uint32_t get_rng_index() - { - return m_rng_index; - } - - /** - * @brief Get this Person's key that is used for the RandomNumberGenerator. - * @see mio::abm::PersonalRandomNumberGenerator. - */ - mio::Key get_rng_key() - { - return m_rng_key; - } - /** * @brief Get the latest #ProtectionType and its initial TimePoint of the Person. */ @@ -443,8 +424,7 @@ class Person .add("last_transport_mode", m_last_transport_mode) .add("rng_counter", m_rng_counter) .add("test_results", m_test_results) - .add("id", m_person_id) - .add("rng_index", m_rng_index); + .add("id", m_person_id); } /** @@ -489,8 +469,6 @@ class Person std::vector m_assigned_location_model_ids; ///< Vector with model ids of the assigned locations. Only used in graph abm. PersonId m_person_id; ///< Unique identifier of a person. - mio::Key m_rng_key; ///< Key for PersonalRandomNumberGenerator - uint32_t m_rng_index; ///< Index for PersonalRandomNumberGenerator. Counter m_rng_counter{0}; ///< counter for RandomNumberGenerator. }; diff --git a/cpp/models/abm/personal_rng.cpp b/cpp/models/abm/personal_rng.cpp index 2a5938fe80..1792847616 100644 --- a/cpp/models/abm/personal_rng.cpp +++ b/cpp/models/abm/personal_rng.cpp @@ -35,8 +35,8 @@ PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(mio::Key { } -PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(Person& person) - : PersonalRandomNumberGenerator(person.get_rng_key(), person.get_rng_index(), person.get_rng_counter()) +PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(const RandomNumberGenerator& model_rng, Person& person) + : PersonalRandomNumberGenerator(model_rng.get_key(), person.get_id().get(), person.get_rng_counter()) { } diff --git a/cpp/models/abm/personal_rng.h b/cpp/models/abm/personal_rng.h index e0baa168b9..48a88e8d73 100644 --- a/cpp/models/abm/personal_rng.h +++ b/cpp/models/abm/personal_rng.h @@ -22,7 +22,6 @@ #define MIO_ABM_PERSONAL_RNG_H #include "memilio/utils/random_number_generator.h" -#include "models/abm/person_id.h" #include namespace mio @@ -61,7 +60,7 @@ class PersonalRandomNumberGenerator : public mio::RandomNumberGeneratorBase bool { //run mobility rule and check if change of location can actually happen @@ -160,7 +160,7 @@ class GraphABModel : public abm::Model auto& trip = Base::m_trip_list.get_next_trip(); auto& person = get_person(trip.person_id); auto person_index = Base::get_person_index(trip.person_id); - auto personal_rng = PersonalRandomNumberGenerator(person); + auto personal_rng = PersonalRandomNumberGenerator(Base::get_rng(), person); // skip the trip if the person is in quarantine or is dead if (person.is_in_quarantine(t, parameters) || person.get_infection_state(t) == InfectionState::Dead) { continue; diff --git a/cpp/tests/abm_helpers.cpp b/cpp/tests/abm_helpers.cpp index 3ee03ab1b5..d29f7e9895 100644 --- a/cpp/tests/abm_helpers.cpp +++ b/cpp/tests/abm_helpers.cpp @@ -29,7 +29,7 @@ mio::abm::Person make_test_person(mio::RandomNumberGenerator& rng, mio::abm::Loc assert(age.get() < params.get_num_groups()); mio::abm::Person p(rng, location.get_type(), location.get_id(), location.get_model_id(), age, id); if (infection_state != mio::abm::InfectionState::Susceptible) { - auto rng_p = mio::abm::PersonalRandomNumberGenerator(p); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(rng, p); p.add_new_infection( mio::abm::Infection(rng_p, static_cast(0), age, params, t, infection_state)); } diff --git a/cpp/tests/test_abm_location.cpp b/cpp/tests/test_abm_location.cpp index 92702b991f..60ae6406c8 100644 --- a/cpp/tests/test_abm_location.cpp +++ b/cpp/tests/test_abm_location.cpp @@ -119,7 +119,7 @@ TEST_F(TestLocation, interact) auto susceptible = make_test_person(this->get_rng(), location, age, mio::abm::InfectionState::Susceptible, t, params); EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(Return(0.5)); // Probability of no infection - auto person_rng = mio::abm::PersonalRandomNumberGenerator(susceptible); + auto person_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susceptible); interact_testing(person_rng, susceptible, location, local_population, t, dt, params); EXPECT_EQ(susceptible.get_infection_state(t + dt), mio::abm::InfectionState::Susceptible); @@ -185,4 +185,4 @@ TEST_F(TestLocation, adjustContactRates) auto adjusted_contacts_rate = loc.get_infection_parameters().get()[{mio::AgeGroup(0), mio::AgeGroup(0)}]; EXPECT_EQ(adjusted_contacts_rate, 2); -} \ No newline at end of file +} diff --git a/cpp/tests/test_abm_lockdown_rules.cpp b/cpp/tests/test_abm_lockdown_rules.cpp index 13d08aca01..1a8a932c0c 100644 --- a/cpp/tests/test_abm_lockdown_rules.cpp +++ b/cpp/tests/test_abm_lockdown_rules.cpp @@ -71,9 +71,9 @@ TEST_F(TestLockdownRules, school_closure) mio::abm::set_school_closure(t, 0.7, params); // Test that p1 stays home and p2 goes to school - auto p1_rng = mio::abm::PersonalRandomNumberGenerator(p1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p1); EXPECT_EQ(mio::abm::go_to_school(p1_rng, p1, t_morning, dt, params), mio::abm::LocationType::Home); - auto p2_rng = mio::abm::PersonalRandomNumberGenerator(p2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p2); EXPECT_EQ(mio::abm::go_to_school(p2_rng, p2, t_morning, dt, params), mio::abm::LocationType::School); } @@ -119,7 +119,7 @@ TEST_F(TestLockdownRules, school_opening) mio::abm::set_school_closure(t_opening, 0., params); // Test that after reopening, the person goes to school - auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); EXPECT_EQ(mio::abm::go_to_school(p_rng, p, t_morning, dt, params), mio::abm::LocationType::School); } @@ -167,9 +167,9 @@ TEST_F(TestLockdownRules, home_office) person2.set_assigned_location(work.get_type(), work.get_id(), work.get_model_id()); // Check that person1 goes to work and person2 stays at home. - auto p1_rng = mio::abm::PersonalRandomNumberGenerator(person1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person1); EXPECT_EQ(mio::abm::go_to_work(p1_rng, person1, t_morning, dt, params), mio::abm::LocationType::Work); - auto p2_rng = mio::abm::PersonalRandomNumberGenerator(person2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person2); EXPECT_EQ(mio::abm::go_to_work(p2_rng, person2, t_morning, dt, params), mio::abm::LocationType::Home); } @@ -212,7 +212,7 @@ TEST_F(TestLockdownRules, no_home_office) mio::abm::set_home_office(t_opening, 0., params); // Test that after removing the home office rules, p goes back to the office. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); EXPECT_EQ(mio::abm::go_to_work(p_rng, p, t_morning, dt, params), mio::abm::LocationType::Work); } @@ -236,7 +236,7 @@ TEST_F(TestLockdownRules, social_event_closure) mio::abm::close_social_events(t, 1, params); // Checks that p stays home instead of attending social events during a closure. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); EXPECT_EQ(mio::abm::go_to_event(p_rng, p, t_evening, dt, params), mio::abm::LocationType::Home); } @@ -266,6 +266,6 @@ TEST_F(TestLockdownRules, social_events_opening) EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).Times(1).WillOnce(testing::Return(0.01)); // Test that after reopening, p attends social events again. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); EXPECT_EQ(mio::abm::go_to_event(p_rng, p, t_evening, dt, params), mio::abm::LocationType::SocialEvent); } diff --git a/cpp/tests/test_abm_masks.cpp b/cpp/tests/test_abm_masks.cpp index 8ce58222c5..bb0006d3d5 100644 --- a/cpp/tests/test_abm_masks.cpp +++ b/cpp/tests/test_abm_masks.cpp @@ -99,11 +99,11 @@ TEST_F(TestMasks, maskProtection) mock_exponential_dist; // Person 1 interaction with full protection EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillOnce(testing::Return(1)); - auto p1_rng = mio::abm::PersonalRandomNumberGenerator(susc_person1); + auto p1_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susc_person1); interact_testing(p1_rng, susc_person1, infection_location, {susc_person1, susc_person2, infected1}, t, dt, params); // Person 2 interaction without protection EXPECT_CALL(mock_exponential_dist.get_mock(), invoke).WillOnce(testing::Return(0.5)); - auto p2_rng = mio::abm::PersonalRandomNumberGenerator(susc_person2); + auto p2_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susc_person2); interact_testing(p2_rng, susc_person2, infection_location, {susc_person1, susc_person2, infected1}, t, dt, params); // susc_person1 (with mask) should remain susceptible diff --git a/cpp/tests/test_abm_mobility_rules.cpp b/cpp/tests/test_abm_mobility_rules.cpp index 1ad41809a0..0847619b5b 100644 --- a/cpp/tests/test_abm_mobility_rules.cpp +++ b/cpp/tests/test_abm_mobility_rules.cpp @@ -33,7 +33,7 @@ TEST_F(TestMobilityRules, random_mobility) int t = 0, dt = 1; auto default_type = mio::abm::LocationType::Cemetery; auto person = mio::abm::Person(this->get_rng(), default_type, 0, 0, age_group_15_to_34); - auto p_rng = mio::abm::PersonalRandomNumberGenerator(person); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); auto params = mio::abm::Parameters(num_age_groups); ScopedMockDistribution>>> mock_exp_dist; @@ -93,8 +93,8 @@ TEST_F(TestMobilityRules, student_goes_to_school) auto t_weekend = mio::abm::TimePoint(0) + mio::abm::days(5) + mio::abm::hours(7); auto dt = mio::abm::hours(1); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); - auto child_rng = mio::abm::PersonalRandomNumberGenerator(p_child); - auto adult_rng = mio::abm::PersonalRandomNumberGenerator(p_adult); + auto child_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child); + auto adult_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); // Set the age group the can go to school is AgeGroup(1) (i.e. 5-14) params.get() = false; params.get()[age_group_5_to_14] = true; @@ -136,10 +136,12 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times) mio::abm::Location home(mio::abm::LocationType::Home, 0, num_age_groups); auto p_child_goes_to_school_at_6 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_6); + auto rng_child_goes_to_school_at_6 = + mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_6); auto p_child_goes_to_school_at_8 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_8 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_8); + auto rng_child_goes_to_school_at_8 = + mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_8); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); auto t_morning_8 = mio::abm::TimePoint(0) + mio::abm::hours(8); @@ -200,12 +202,14 @@ TEST_F(TestMobilityRules, students_go_to_school_in_different_times_with_smaller_ // First student goes to school at 6:00 AM auto p_child_goes_to_school_at_6 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_6 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_6); + auto rng_child_goes_to_school_at_6 = + mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_6); // Second student goes to school at 8:30 AM auto p_child_goes_to_school_at_8_30 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_5_to_14); - auto rng_child_goes_to_school_at_8_30 = mio::abm::PersonalRandomNumberGenerator(p_child_goes_to_school_at_8_30); + auto rng_child_goes_to_school_at_8_30 = + mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child_goes_to_school_at_8_30); // Time points for the morning scenarios auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); @@ -243,7 +247,7 @@ TEST_F(TestMobilityRules, school_return) mio::abm::Location school(mio::abm::LocationType::School, 0, num_age_groups); auto p_child = mio::abm::Person(this->get_rng(), school.get_type(), school.get_id(), school.get_model_id(), age_group_5_to_14); - auto rng_child = mio::abm::PersonalRandomNumberGenerator(p_child); + auto rng_child = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_child); // Simulate a time point after school hours auto t = mio::abm::TimePoint(0) + mio::abm::hours(15); @@ -276,10 +280,10 @@ TEST_F(TestMobilityRules, worker_goes_to_work) auto p_retiree = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); - auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(p_retiree); + auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_retiree); auto p_adult = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(p_adult); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(8); auto t_night = mio::abm::TimePoint(0) + mio::abm::days(1) + mio::abm::hours(4); @@ -326,10 +330,10 @@ TEST_F(TestMobilityRules, worker_goes_to_work_with_non_dividable_timespan) // Set up two people: one retiree and one working adult. auto p_retiree = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); - auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(p_retiree); + auto rng_retiree = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_retiree); auto p_adult = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(p_adult); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); auto t_morning = mio::abm::TimePoint(0) + mio::abm::hours(8); auto t_night = mio::abm::TimePoint(0) + mio::abm::days(1) + mio::abm::hours(4); @@ -381,10 +385,12 @@ TEST_F(TestMobilityRules, workers_go_to_work_in_different_times) // Create two workers: one goes to work at 6 AM and the other at 8 AM. auto p_adult_goes_to_work_at_6 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); - auto rng_adult_goes_to_work_at_6 = mio::abm::PersonalRandomNumberGenerator(p_adult_goes_to_work_at_6); + auto rng_adult_goes_to_work_at_6 = + mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult_goes_to_work_at_6); auto p_adult_goes_to_work_at_8 = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_15_to_34); - auto rng_adult_goes_to_work_at_8 = mio::abm::PersonalRandomNumberGenerator(p_adult_goes_to_work_at_8); + auto rng_adult_goes_to_work_at_8 = + mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult_goes_to_work_at_8); auto t_morning_6 = mio::abm::TimePoint(0) + mio::abm::hours(6); auto t_morning_8 = mio::abm::TimePoint(0) + mio::abm::hours(8); @@ -428,7 +434,7 @@ TEST_F(TestMobilityRules, work_return) // Set up a random number generator and a worker who is currently at work auto p_adult = mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), work.get_model_id(), age_group_35_to_59); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(p_adult); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_adult); // Set the time to 5 PM (17:00) when the worker should return home auto t = mio::abm::TimePoint(0) + mio::abm::hours(17); auto dt = mio::abm::hours(1); @@ -451,7 +457,7 @@ TEST_F(TestMobilityRules, quarantine) auto p_inf1 = make_test_person(this->get_rng(), work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_inf1 = mio::abm::PersonalRandomNumberGenerator(p_inf1); + auto rng_inf1 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf1); p_inf1.get_tested(rng_inf1, t, test_params); // Check detected infected person quarantines at home EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf1, p_inf1, t, dt, mio::abm::Parameters(num_age_groups)), @@ -459,14 +465,14 @@ TEST_F(TestMobilityRules, quarantine) auto p_inf2 = make_test_person(this->get_rng(), work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_inf2 = mio::abm::PersonalRandomNumberGenerator(p_inf2); + auto rng_inf2 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf2); // Check that undetected infected person does not quaratine EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf2, p_inf2, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); auto p_inf3 = make_test_person(this->get_rng(), hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf3 = mio::abm::PersonalRandomNumberGenerator(p_inf3); + auto rng_inf3 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf3); p_inf1.get_tested(rng_inf3, t, test_params); // Check that detected infected person does not leave hospital to quarantine EXPECT_EQ(mio::abm::go_to_quarantine(rng_inf3, p_inf3, t, dt, mio::abm::Parameters(num_age_groups)), @@ -483,7 +489,7 @@ TEST_F(TestMobilityRules, hospital) auto dt = mio::abm::hours(1); auto p_inf = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf = mio::abm::PersonalRandomNumberGenerator(p_inf); + auto rng_inf = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf); // Ensure person goes to the hospital when severely infected EXPECT_EQ(mio::abm::go_to_hospital(rng_inf, p_inf, t, dt, mio::abm::Parameters(num_age_groups)), @@ -491,7 +497,7 @@ TEST_F(TestMobilityRules, hospital) auto p_car = make_test_person(this->get_rng(), home, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - auto rng_car = mio::abm::PersonalRandomNumberGenerator(p_car); + auto rng_car = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_car); // Ensure person has infection symptoms still stay at home EXPECT_EQ(mio::abm::go_to_hospital(rng_car, p_car, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Home); @@ -513,11 +519,11 @@ TEST_F(TestMobilityRules, go_shopping) // Create an infected child in the hospital auto p_hosp = make_test_person(this->get_rng(), hospital, age_group_0_to_4, mio::abm::InfectionState::InfectedSymptoms, t_weekday); - auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(p_hosp); + auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_hosp); // Create a healthy elderly person at home auto p_home = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); - auto rng_home = mio::abm::PersonalRandomNumberGenerator(p_home); + auto rng_home = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_home); // Check that an infected person stays in the hospital and doesn't go shopping EXPECT_EQ(mio::abm::go_to_shop(rng_hosp, p_hosp, t_weekday, dt, mio::abm::Parameters(num_age_groups)), @@ -551,7 +557,7 @@ TEST_F(TestMobilityRules, shop_return) mio::abm::Location shop(mio::abm::LocationType::BasicsShop, 0, num_age_groups); auto p = make_test_person(this->get_rng(), shop, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, t); - auto rng_p = mio::abm::PersonalRandomNumberGenerator(p); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); // Simulate the person spending 1 hour at the shop p.add_time_at_location(dt); @@ -569,11 +575,11 @@ TEST_F(TestMobilityRules, go_event) mio::abm::Location work(mio::abm::LocationType::Work, 0, num_age_groups); auto p_work = mio::abm::Person(this->get_rng(), work.get_type(), work.get_id(), work.get_model_id(), age_group_35_to_59); - auto rng_work = mio::abm::PersonalRandomNumberGenerator(p_work); + auto rng_work = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_work); mio::abm::Location home(mio::abm::LocationType::Home, 1, num_age_groups); auto p_home = mio::abm::Person(this->get_rng(), home.get_type(), home.get_id(), home.get_model_id(), age_group_60_to_79); - auto rng_home = mio::abm::PersonalRandomNumberGenerator(p_home); + auto rng_home = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_home); auto t_weekday = mio::abm::TimePoint(0) + mio::abm::days(4) + mio::abm::hours(20); auto t_saturday = mio::abm::TimePoint(0) + mio::abm::days(5) + mio::abm::hours(10); @@ -614,7 +620,7 @@ TEST_F(TestMobilityRules, event_return) // Initialize the person at the social event location auto p = mio::abm::Person(this->get_rng(), social_event.get_type(), social_event.get_id(), social_event.get_model_id(), age_group_15_to_34); - auto rng_p = mio::abm::PersonalRandomNumberGenerator(p); + auto rng_p = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); // Simulate the person spending 3 hours at the social event p.add_time_at_location(dt); // After spending the time at the social event, the person should return home @@ -631,7 +637,7 @@ TEST_F(TestMobilityRules, icu) auto dt = mio::abm::hours(1); auto p_hosp = make_test_person(this->get_rng(), hospital, age_group_15_to_34, mio::abm::InfectionState::InfectedCritical, t); - auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(p_hosp); + auto rng_hosp = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_hosp); // Ensure critically infected person goes to the ICU EXPECT_EQ(mio::abm::go_to_icu(rng_hosp, p_hosp, t, dt, mio::abm::Parameters(num_age_groups)), @@ -640,7 +646,7 @@ TEST_F(TestMobilityRules, icu) mio::abm::Location work(mio::abm::LocationType::Work, 1, num_age_groups); auto p_work = make_test_person(this->get_rng(), work, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, t); - auto rng_work = mio::abm::PersonalRandomNumberGenerator(p_work); + auto rng_work = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_work); // Ensure infected with symptions person can still go to work EXPECT_EQ(mio::abm::go_to_icu(rng_work, p_work, t, dt, mio::abm::Parameters(num_age_groups)), mio::abm::LocationType::Work); @@ -656,10 +662,10 @@ TEST_F(TestMobilityRules, recover) auto dt = mio::abm::hours(1); auto p_rec = make_test_person(this->get_rng(), hospital, age_group_60_to_79, mio::abm::InfectionState::Recovered, t); - auto rng_rec = mio::abm::PersonalRandomNumberGenerator(p_rec); + auto rng_rec = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_rec); auto p_inf = make_test_person(this->get_rng(), hospital, age_group_60_to_79, mio::abm::InfectionState::InfectedSevere, t); - auto rng_inf = mio::abm::PersonalRandomNumberGenerator(p_inf); + auto rng_inf = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_inf); // Ensure recovered person returns home and infected severe person stay in hospital EXPECT_EQ(mio::abm::return_home_when_recovered(rng_rec, p_rec, t, dt, {num_age_groups}), mio::abm::LocationType::Home); @@ -676,7 +682,7 @@ TEST_F(TestMobilityRules, dead) auto t = mio::abm::TimePoint(12346); auto dt = mio::abm::hours(1); auto p_dead = make_test_person(this->get_rng(), icu, age_group_60_to_79, mio::abm::InfectionState::Dead, t); - auto p_rng = mio::abm::PersonalRandomNumberGenerator(p_dead); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p_dead); EXPECT_EQ(mio::abm::get_buried(p_rng, p_dead, t, dt, {num_age_groups}), mio::abm::LocationType::Cemetery); } diff --git a/cpp/tests/test_abm_model.cpp b/cpp/tests/test_abm_model.cpp index b0112fe3b6..c772875d90 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -431,13 +431,13 @@ TEST_F(TestModel, evolveMobilityTrips) .WillOnce(testing::Return(0.8)) // draw random virus shed p4 .RetiresOnSaturation(); - auto rng_p1 = mio::abm::PersonalRandomNumberGenerator(p1); + auto rng_p1 = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), p1); p1.add_new_infection(mio::abm::Infection(rng_p1, static_cast(0), p1.get_age(), model.parameters, t, mio::abm::InfectionState::InfectedNoSymptoms)); - auto rng_p3 = mio::abm::PersonalRandomNumberGenerator(p1); + auto rng_p3 = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), p3); p3.add_new_infection(mio::abm::Infection(rng_p3, static_cast(0), p3.get_age(), model.parameters, t, mio::abm::InfectionState::InfectedSevere)); - auto rng_p4 = mio::abm::PersonalRandomNumberGenerator(p1); + auto rng_p4 = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), p4); p4.add_new_infection(mio::abm::Infection(rng_p4, static_cast(0), p4.get_age(), model.parameters, t, mio::abm::InfectionState::Recovered)); @@ -610,7 +610,7 @@ TEST_F(TestModelTestingCriteria, testAddingAndUpdatingAndRunningTestingSchemes) auto pid = add_test_person(model, home_id, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms, current_time - test_time); auto& person = model.get_person(pid); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); person.set_assigned_location(mio::abm::LocationType::Home, home_id, model.get_id()); person.set_assigned_location(mio::abm::LocationType::Work, work_id, model.get_id()); diff --git a/cpp/tests/test_abm_person.cpp b/cpp/tests/test_abm_person.cpp index a605486fed..a9bc77b031 100644 --- a/cpp/tests/test_abm_person.cpp +++ b/cpp/tests/test_abm_person.cpp @@ -162,7 +162,7 @@ TEST_F(TestPerson, quarantine) auto person = make_test_person(this->get_rng(), home, age_group_35_to_59, mio::abm::InfectionState::InfectedSymptoms, t_morning, infection_parameters); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); // Test quarantine when a person is tested and positive. person.get_tested(rng_person, t_morning, test_params); @@ -189,10 +189,10 @@ TEST_F(TestPerson, get_tested) mio::abm::Location loc(mio::abm::LocationType::Home, 0, num_age_groups); auto infected = make_test_person(this->get_rng(), loc, age_group_15_to_34, mio::abm::InfectionState::InfectedSymptoms); - auto rng_infected = mio::abm::PersonalRandomNumberGenerator(infected); + auto rng_infected = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), infected); auto susceptible = mio::abm::Person(this->get_rng(), loc.get_type(), loc.get_id(), loc.get_model_id(), age_group_15_to_34); - auto rng_suscetible = mio::abm::PersonalRandomNumberGenerator(susceptible); + auto rng_suscetible = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), susceptible); auto pcr_parameters = params.get()[mio::abm::TestType::PCR]; auto antigen_parameters = params.get()[mio::abm::TestType::Antigen]; @@ -278,7 +278,7 @@ TEST_F(TestPerson, interact) // Create a person and set up a random number generator specific to that person. auto person = mio::abm::Person(this->get_rng(), loc.get_type(), loc.get_id(), loc.get_model_id(), age_group_15_to_34); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); auto dt = mio::abm::seconds(8640); //0.1 days // Simulate interaction and check that the person accumulates time at the location. interact_testing(rng_person, person, loc, {person}, t, dt, infection_parameters); @@ -338,7 +338,7 @@ TEST_F(TestPerson, getLatestProtection) auto location = mio::abm::Location(mio::abm::LocationType::School, 0, num_age_groups); auto person = mio::abm::Person(this->get_rng(), location.get_type(), location.get_id(), location.get_model_id(), age_group_15_to_34); - auto prng = mio::abm::PersonalRandomNumberGenerator(person); + auto prng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); mio::abm::Parameters params = mio::abm::Parameters(num_age_groups); auto t = mio::abm::TimePoint(0); @@ -367,7 +367,7 @@ TEST_F(TestPerson, rng) EXPECT_EQ(p.get_rng_counter(), mio::Counter(0)); // Verify RNG counter increments. - auto p_rng = mio::abm::PersonalRandomNumberGenerator(p); + auto p_rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), p); EXPECT_EQ(p_rng.get_counter(), mio::rng_totalsequence_counter(13, mio::Counter{0})); p_rng(); @@ -410,7 +410,7 @@ TEST_F(TestPerson, isCompliant) // Create test person and associated random number generator auto person = make_test_person(this->get_rng(), home); - auto rng_person = mio::abm::PersonalRandomNumberGenerator(person); + auto rng_person = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); // Test cases with a complete truth table for compliance levels struct TestCase { diff --git a/cpp/tests/test_abm_serialization.cpp b/cpp/tests/test_abm_serialization.cpp index 173e563261..2830fa1dc0 100644 --- a/cpp/tests/test_abm_serialization.cpp +++ b/cpp/tests/test_abm_serialization.cpp @@ -214,7 +214,6 @@ TEST(TestAbmSerialization, Person) reference_json["time_at_location"]["seconds"] = Json::Int(i++); reference_json["vaccinations"] = Json::Value(Json::arrayValue); reference_json["id"] = Json::UInt(i++); - reference_json["rng_index"] = Json::UInt(i++); test_json_serialization(reference_json); } diff --git a/cpp/tests/test_abm_testing_strategy.cpp b/cpp/tests/test_abm_testing_strategy.cpp index 9d29dd5039..c37b696fcf 100644 --- a/cpp/tests/test_abm_testing_strategy.cpp +++ b/cpp/tests/test_abm_testing_strategy.cpp @@ -104,10 +104,10 @@ TEST_F(TestTestingScheme, runScheme) auto person1 = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, start_date - test_params_pcr.required_time); - auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(person1); + auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person1); auto person2 = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::Recovered, start_date - test_params_pcr.required_time); - auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(person2); + auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person2); // Mock uniform distribution to control random behavior in testing. ScopedMockDistribution>>> mock_uniform_dist; @@ -153,10 +153,10 @@ TEST_F(TestTestingScheme, initAndRunTestingStrategy) auto person1 = make_test_person(this->get_rng(), loc_work, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, start_date - test_params_pcr.required_time); - auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(person1); + auto rng_person1 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person1); auto person2 = make_test_person(this->get_rng(), loc_work, age_group_15_to_34, mio::abm::InfectionState::Recovered, start_date - test_params_pcr.required_time); - auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(person2); + auto rng_person2 = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person2); // Mock uniform distribution to control random behavior in testing. ScopedMockDistribution>>> mock_uniform_dist; @@ -291,7 +291,7 @@ TEST_F(TestTestingScheme, testingSchemeResultCaching) mio::abm::Location loc_home(mio::abm::LocationType::Home, 0, num_age_groups); auto person = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, start_date); - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); // Mock uniform distribution to control test results ScopedMockDistribution>>> mock_uniform_dist; @@ -344,12 +344,12 @@ TEST_F(TestTestingScheme, differentTestTypes) auto person_infected = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::InfectedNoSymptoms, start_date - test_params_pcr.required_time); - auto rng_infected = mio::abm::PersonalRandomNumberGenerator(person_infected); + auto rng_infected = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person_infected); auto person_healthy = make_test_person(this->get_rng(), loc_home, age_group_15_to_34, mio::abm::InfectionState::Susceptible, start_date - test_params_pcr.required_time); - auto rng_healthy = mio::abm::PersonalRandomNumberGenerator(person_healthy); + auto rng_healthy = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person_healthy); // Mock uniform distribution to control test results // Mock uniform distribution to control test results for PCR test with infected person @@ -439,8 +439,8 @@ TEST_F(TestTestingScheme, multipleSchemesCombination) auto adult = make_test_person(this->get_rng(), loc_home, age_group_35_to_59, mio::abm::InfectionState::Susceptible, start_date); - auto rng_child = mio::abm::PersonalRandomNumberGenerator(child); - auto rng_adult = mio::abm::PersonalRandomNumberGenerator(adult); + auto rng_child = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), child); + auto rng_adult = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), adult); // Mock uniform distribution to control test results ScopedMockDistribution>>> mock_uniform_dist; @@ -495,7 +495,7 @@ TEST_F(TestTestingScheme, locationSpecificSchemes) // Create a test person auto person = make_test_person(this->get_rng(), shop1, age_group_15_to_34, mio::abm::InfectionState::Susceptible, start_date); - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); // Mock uniform distribution to control test results ScopedMockDistribution>>> mock_uniform_dist; @@ -541,7 +541,7 @@ TEST_F(TestTestingScheme, testCompliance) // Create a test person auto person = make_test_person(this->get_rng(), shop1, age_group_15_to_34, mio::abm::InfectionState::Susceptible, start_date); - auto rng = mio::abm::PersonalRandomNumberGenerator(person); + auto rng = mio::abm::PersonalRandomNumberGenerator(this->get_rng(), person); person.set_compliance(mio::abm::InterventionType::Testing, 0.1); // Set compliance for testing // Mock uniform distribution to control test results From 94e78b83123966617a60a4704db992099e113aad Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:16:27 +0200 Subject: [PATCH 02/10] move casts around to avoid -0.0 in output --- cpp/memilio/data/analyze_result.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpp/memilio/data/analyze_result.h b/cpp/memilio/data/analyze_result.h index c07d1962e3..533c5e4fe7 100644 --- a/cpp/memilio/data/analyze_result.h +++ b/cpp/memilio/data/analyze_result.h @@ -192,13 +192,13 @@ TimeSeries interpolate_simulation_result(const TimeSeries& simulation_re const auto t0 = simulation_result.get_time(0); const auto t_max = simulation_result.get_last_time(); // add an additional day, if the first time point is within tolerance of floor(t0) - const auto day0 = ceil(t0 - abs_tol); + const auto day0 = static_cast(ceil(t0 - abs_tol)); // add an additional day, if the last time point is within tolerance of ceil(tmax) - const auto day_max = floor(t_max + abs_tol); + const auto day_max = static_cast(floor(t_max + abs_tol)); // create interpolation_times vector with all days between day0 and day_max - std::vector tps(static_cast(day_max) - static_cast(day0) + 1); - std::iota(tps.begin(), tps.end(), day0); + std::vector tps(day_max - day0 + 1); + std::iota(tps.begin(), tps.end(), FP{day0}); return interpolate_simulation_result(simulation_result, tps); } From ea64569f77b55adfbf9c4222f5c38a74828c6ab9 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:08:18 +0200 Subject: [PATCH 03/10] Ignore example_results directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 34e8473d18..50b963e817 100644 --- a/.gitignore +++ b/.gitignore @@ -288,3 +288,6 @@ docs/source/cppapi .vscode/ # End of https://www.gitignore.io/api/c++,node,python + +# Output directory from memilio examples +example_results/ From 7a67e75b3bcfe2a34744aadf2379e58b03bf52f0 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:21:37 +0200 Subject: [PATCH 04/10] add operator for printing std::vector --- cpp/memilio/utils/stl_util.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cpp/memilio/utils/stl_util.h b/cpp/memilio/utils/stl_util.h index d5e66bfe99..4cf3a8c7ef 100644 --- a/cpp/memilio/utils/stl_util.h +++ b/cpp/memilio/utils/stl_util.h @@ -34,6 +34,22 @@ #include #include +/// @brief Stream operator for writing std::vector to an std::ostream. Requires that T has a viable overload itself. +template +std::ostream& operator<<(std::ostream& out, const std::vector& vec) +{ + out << "{"; + // use size - 1 and handle the last entry separately, for nicer separator usage + for (size_t i = 0; i < vec.size() - 1; i++) { + out << vec[i] << ", "; + } + if (!vec.empty()) { + out << vec.back(); + } + out << "}"; + return out; +} + namespace mio { From ff1f01b3aed4fb1281fffe7e2e6e344779016e2d Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:23:32 +0200 Subject: [PATCH 05/10] Add a const overload to get_rng, allow custom counter value in reset_rng --- cpp/models/abm/model.h | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/cpp/models/abm/model.h b/cpp/models/abm/model.h index d711d2ced9..90fff62025 100644 --- a/cpp/models/abm/model.h +++ b/cpp/models/abm/model.h @@ -60,7 +60,7 @@ class Model using ActivenessIterator = std::vector::iterator; using ConstActivenessIterator = std::vector::const_iterator; using MobilityRuleType = LocationType (*)(PersonalRandomNumberGenerator&, const Person&, TimePoint, TimeSpan, - const Parameters&); + const Parameters&); using Compartments = mio::abm::InfectionState; /** @@ -333,28 +333,37 @@ class Model * Get the RandomNumberGenerator used by this Model for random events. * Persons use their own generators with the same key as the global one. * @return The random number generator. + * @{ */ RandomNumberGenerator& get_rng() { return m_rng; } + const RandomNumberGenerator& get_rng() const + { + return m_rng; + } + /** @} */ /** - * @brief Sets the RNG counters of the model and all persons to 0. - * @param[in] seeds Optional parameter that can be used to overwrite the current seed. + * @brief Sets the RNG counters of the model and all persons to 0 (or to the optional counter argument). + * @param[in] seeds Optional argument used to overwrite the current seed. + * @param[in] counter Optional argument used as base value for each RNG counter. + * Note: Both the model's and each person's RNG uses a 64 bit counter. Since the persons reserve half of the + * counter for their ID, we only allow specifying the first 32 bits here, even for the model. * @{ */ - void reset_rng() + void reset_rng(Counter counter = {}) { - m_rng.set_counter(Counter(0)); + m_rng.set_counter(Counter(static_cast(counter.get()))); for (Person& person : get_persons()) { - person.get_rng_counter() = Counter(0); + person.get_rng_counter() = counter; } } - void reset_rng(const std::vector& seeds) + void reset_rng(const std::vector& seeds, Counter counter = {}) { m_rng.seed(seeds); - reset_rng(); + reset_rng(counter); } /** @} */ From 798ce2cf732f8491d9f9091ccc7addb3307328e8 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:30:04 +0200 Subject: [PATCH 06/10] Fix ABM study setup to be reproducible --- cpp/examples/abm_parameter_study.cpp | 69 ++++++++++++++++------------ cpp/memilio/data/analyze_result.h | 2 +- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index 2ba3e8b27a..00fbae7c6d 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -38,7 +38,7 @@ constexpr size_t num_age_groups = 4; /// An ABM setup taken from abm_minimal.cpp. -mio::abm::Model make_model() +mio::abm::Model make_model(const mio::RandomNumberGenerator& rng) { const auto age_group_0_to_4 = mio::AgeGroup(0); @@ -47,6 +47,7 @@ mio::abm::Model make_model() const auto age_group_35_to_59 = mio::AgeGroup(3); // Create the model with 4 age groups. auto model = mio::abm::Model(num_age_groups); + model.reset_rng(rng.get_seeds()); // Set same infection parameter for all age groups. For example, the incubation period is log normally distributed with parameters 4 and 1. model.parameters.get() = mio::ParameterDistributionLogNormal(4., 1.); @@ -130,7 +131,7 @@ mio::abm::Model make_model() std::vector infection_distribution{0.5, 0.3, 0.05, 0.05, 0.05, 0.05, 0.0, 0.0}; for (auto& person : model.get_persons()) { mio::abm::InfectionState infection_state = mio::abm::InfectionState( - mio::DiscreteDistribution::get_instance()(mio::thread_local_rng(), infection_distribution)); + mio::DiscreteDistribution::get_instance()(model.get_rng(), infection_distribution)); auto person_rng = mio::abm::PersonalRandomNumberGenerator(model.get_rng(), person); if (infection_state != mio::abm::InfectionState::Susceptible) { person.add_new_infection(mio::abm::Infection(person_rng, mio::abm::VirusVariant::Wildtype, person.get_age(), @@ -174,15 +175,22 @@ int main() auto tmax = t0 + mio::abm::days(5); // Set the number of simulations to run in the study const size_t num_runs = 3; + // Set up an RNG + mio::RandomNumberGenerator rng; - // Create a parameter study. - // Note that the study for the ABM currently does not make use of the arguments "parameters" or "dt", as we create - // a new model for each simulation. Hence we set both arguments to 0. - // This is mostly due to https://github.com/SciCompMod/memilio/issues/1400 - mio::ParameterStudy study(make_model(), t0, tmax, mio::abm::TimeSpan(0), num_runs); + // Optional: set seeds to get reproducible results + // rng.seed({1234, 2345, 124324, 7567, 34534, 7}); + + // Always synchronise seeds before creating the model, otherwise different MPI ranks will create different models + // If you do want to use a different model each run, move the model setup from the ParameterStudy creation into the + // simulation setup (i.e. into the create_simulation function argument of ParameterStudy::run) + rng.synchronize(); + auto model = make_model(rng); - // Optional: set seeds to get reproducable results - // study.get_rng().seed({12341234, 53456, 63451, 5232576, 84586, 52345}); + // Create a parameter study. + // Note that the ABM's step size is fixed, hence we set the effectively unused "dt" argument to 0 + mio::ParameterStudy study(std::move(model), t0, tmax, mio::abm::TimeSpan(0), num_runs); + study.get_rng() = rng; // use the same RNG as the model const std::string result_dir = mio::path_join(mio::base_dir(), "example_results"); if (!mio::create_directory(result_dir)) { @@ -190,39 +198,40 @@ int main() return 1; } + // Run the study + // The first lambda ("create_simulation" argument) sets up the simulation, the second ("process_simulation_result") + // allows us to process each simulations result. Be mindful of the memory used for storing these results! auto ensemble_results = study.run( - [](auto&& model, auto t0_, auto, size_t) { + [](auto&& model, auto t0_, auto, size_t run_idx) { auto copy = model; - copy.reset_rng(mio::thread_local_rng().get_seeds()); + // using half of the RNG counter for the run index (soft-) limits both the number of runs and RNG draws + // per person to 2^16 = 65536 + copy.reset_rng(mio::Counter(run_idx << 16)); return mio::abm::ResultSimulation(std::move(copy), t0_); }, - [result_dir](auto&& sim, auto&& run_idx) { + [&result_dir](auto&& sim, auto&& run_idx) { auto interpolated_result = mio::interpolate_simulation_result(sim.get_result()); std::string outpath = mio::path_join(result_dir, "abm_minimal_run_" + std::to_string(run_idx) + ".txt"); std::ofstream outfile_run(outpath); sim.get_result().print_table(outfile_run, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, 4); std::cout << "Results written to " << outpath << std::endl; - auto params = std::vector{}; return std::vector{interpolated_result}; }); - if (ensemble_results.size() > 0) { - auto ensemble_results_p05 = ensemble_percentile(ensemble_results, 0.05); - auto ensemble_results_p25 = ensemble_percentile(ensemble_results, 0.25); - auto ensemble_results_p50 = ensemble_percentile(ensemble_results, 0.50); - auto ensemble_results_p75 = ensemble_percentile(ensemble_results, 0.75); - auto ensemble_results_p95 = ensemble_percentile(ensemble_results, 0.95); - - mio::unused(save_result(ensemble_results_p05, {0}, num_age_groups, - mio::path_join(result_dir, "Results_" + std::string("p05") + ".h5"))); - mio::unused(save_result(ensemble_results_p25, {0}, num_age_groups, - mio::path_join(result_dir, "Results_" + std::string("p25") + ".h5"))); - mio::unused(save_result(ensemble_results_p50, {0}, num_age_groups, - mio::path_join(result_dir, "Results_" + std::string("p50") + ".h5"))); - mio::unused(save_result(ensemble_results_p75, {0}, num_age_groups, - mio::path_join(result_dir, "Results_" + std::string("p75") + ".h5"))); - mio::unused(save_result(ensemble_results_p95, {0}, num_age_groups, - mio::path_join(result_dir, "Results_" + std::string("p95") + ".h5"))); + // The study collects all results on the root rank, so we only process the results there + if (mio::mpi::is_root()) { + const auto write_percentile = [&](double p) { + std::ofstream out(mio::path_join(result_dir, fmt::format("Results_p{:0<4.2}.txt", p))); + auto ensemble_percentiles = ensemble_percentile(ensemble_results, p); + ensemble_percentiles.front().print_table(out, {"S", "E", "I_NS", "I_Sy", "I_Sev", "I_Crit", "R", "D"}, 7, + 4); + }; + + write_percentile(0.05); + write_percentile(0.25); + write_percentile(0.50); + write_percentile(0.75); + write_percentile(0.95); } mio::mpi::finalize(); diff --git a/cpp/memilio/data/analyze_result.h b/cpp/memilio/data/analyze_result.h index 533c5e4fe7..08a4716ca7 100644 --- a/cpp/memilio/data/analyze_result.h +++ b/cpp/memilio/data/analyze_result.h @@ -198,7 +198,7 @@ TimeSeries interpolate_simulation_result(const TimeSeries& simulation_re // create interpolation_times vector with all days between day0 and day_max std::vector tps(day_max - day0 + 1); - std::iota(tps.begin(), tps.end(), FP{day0}); + std::iota(tps.begin(), tps.end(), FP(day0)); return interpolate_simulation_result(simulation_result, tps); } From 8d43ac7c160661e927f793f8b948353518cadd9e Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:56:41 +0200 Subject: [PATCH 07/10] add explicit type conversion --- cpp/models/abm/personal_rng.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/models/abm/personal_rng.cpp b/cpp/models/abm/personal_rng.cpp index 0b6748705a..137db0297b 100644 --- a/cpp/models/abm/personal_rng.cpp +++ b/cpp/models/abm/personal_rng.cpp @@ -36,7 +36,8 @@ PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(mio::Key } PersonalRandomNumberGenerator::PersonalRandomNumberGenerator(const RandomNumberGenerator& model_rng, Person& person) - : PersonalRandomNumberGenerator(model_rng.get_key(), person.get_id().get(), person.get_rng_counter()) + : PersonalRandomNumberGenerator(model_rng.get_key(), static_cast(person.get_id().get()), + person.get_rng_counter()) { } From c5a0fca43cfcea7a71a66502d551a71897034059 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:56:52 +0200 Subject: [PATCH 08/10] avoid shadowing --- cpp/examples/abm_parameter_study.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index 00fbae7c6d..795146153d 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -202,8 +202,8 @@ int main() // The first lambda ("create_simulation" argument) sets up the simulation, the second ("process_simulation_result") // allows us to process each simulations result. Be mindful of the memory used for storing these results! auto ensemble_results = study.run( - [](auto&& model, auto t0_, auto, size_t run_idx) { - auto copy = model; + [](auto&& model_, auto t0_, auto, size_t run_idx) { + auto copy = model_; // using half of the RNG counter for the run index (soft-) limits both the number of runs and RNG draws // per person to 2^16 = 65536 copy.reset_rng(mio::Counter(run_idx << 16)); From efd22a2f05d9c6301dd2666cb018293b336bc34d Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:15:10 +0200 Subject: [PATCH 09/10] try handling msvc --- cpp/examples/abm_parameter_study.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/examples/abm_parameter_study.cpp b/cpp/examples/abm_parameter_study.cpp index 795146153d..e3cf55da0a 100644 --- a/cpp/examples/abm_parameter_study.cpp +++ b/cpp/examples/abm_parameter_study.cpp @@ -206,7 +206,8 @@ int main() auto copy = model_; // using half of the RNG counter for the run index (soft-) limits both the number of runs and RNG draws // per person to 2^16 = 65536 - copy.reset_rng(mio::Counter(run_idx << 16)); + const auto ctr = mio::Counter(static_cast(run_idx) << 16); + copy.reset_rng(ctr); return mio::abm::ResultSimulation(std::move(copy), t0_); }, [&result_dir](auto&& sim, auto&& run_idx) { From 47f20a9cfb29fcf37f8c25a69dd8186c73057d38 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:30:33 +0200 Subject: [PATCH 10/10] add test for reset_rng --- cpp/tests/test_abm_model.cpp | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/cpp/tests/test_abm_model.cpp b/cpp/tests/test_abm_model.cpp index 60e6fcb3b2..fe7926ff06 100644 --- a/cpp/tests/test_abm_model.cpp +++ b/cpp/tests/test_abm_model.cpp @@ -17,6 +17,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "abm/location_id.h" +#include "abm/person_id.h" +#include "memilio/utils/random_number_generator.h" #include "utils.h" #include "abm/location.h" #include "abm/location_type.h" @@ -30,7 +33,9 @@ #include "memilio/utils/abstract_parameter_distribution.h" #include "memilio/utils/parameter_distributions.h" #include "random_number_test.h" +#include #include +#include using TestModel = RandomNumberTest; @@ -1103,3 +1108,48 @@ TEST_F(TestModel, personCanDieInHospital) model.evolve(t + dt, dt); EXPECT_EQ(model.get_location(p_severe.get_id()).get_type(), mio::abm::LocationType::Cemetery); } + +TEST_F(TestModel, reset_rng) +{ + const int num_samples = 100; // number of samples used for comparing random sequences + auto model = mio::abm::Model(num_age_groups); + model.get_rng() = this->get_rng(); + // add two generic persons + { + // use DefaultFactory to avoid using the RNG in the Person ctor + auto p = mio::DefaultFactory::create(); + p.set_location(mio::abm::LocationType::Cemetery, mio::abm::LocationId(0), 0); + model.add_person(mio::abm::Person(p, mio::abm::PersonId(0))); + model.add_person(mio::abm::Person(p, mio::abm::PersonId(1))); + } + auto& p0 = model.get_persons()[0]; + auto& p1 = model.get_persons()[1]; + // shorthand for drawing a random vector using a personal RNG + const auto draw_samples = [](mio::abm::PersonalRandomNumberGenerator&& rng) { + std::vector samples(num_samples); + std::ranges::generate(samples, rng); + return samples; + }; + + // case: draw independent samples from each person; expect the same random sequence for both persons after a reset + const auto reference_samples0 = draw_samples({model.get_rng(), p0}); + const auto reference_samples0_cont = draw_samples({model.get_rng(), p0}); // used for next case + const auto reference_samples1 = draw_samples({model.get_rng(), p1}); + model.reset_rng(); + EXPECT_EQ(reference_samples1, draw_samples({model.get_rng(), p1})); // reverse order to check independence + EXPECT_EQ(reference_samples0, draw_samples({model.get_rng(), p0})); + + // case: draw samples twice from one person; + // expect the same random sequence after resetting with the counter set to num_samples + model.reset_rng(mio::Counter(num_samples)); + EXPECT_EQ(reference_samples0_cont, draw_samples({model.get_rng(), p0})); + + // case: reset with seed and counter; expect seed and counter to be set accordingly + // this is a very simplistic test, as the main behaviour of the reset function is covered sufficiently above + const std::vector test_seed{1, 2, 3, 4, 5, 6}; + const mio::Counter test_ctr(7); + model.reset_rng(test_seed, test_ctr); + EXPECT_EQ(model.get_rng().get_seeds(), test_seed); + EXPECT_EQ(p0.get_rng_counter(), test_ctr); + EXPECT_EQ(p1.get_rng_counter(), test_ctr); +}