@@ -46,8 +46,8 @@ mio::abm::Model make_model(const mio::RandomNumberGenerator& rng)
4646 const auto age_group_15_to_34 = mio::AgeGroup (2 );
4747 const auto age_group_35_to_59 = mio::AgeGroup (3 );
4848 // Create the model with 4 age groups.
49- auto model = mio::abm::Model (num_age_groups);
50- model.get_rng () = rng;
49+ auto model = mio::abm::Model (num_age_groups);
50+ model.reset_rng ( rng. get_seeds ()) ;
5151
5252 // Set same infection parameter for all age groups. For example, the incubation period is log normally distributed with parameters 4 and 1.
5353 model.parameters .get <mio::abm::TimeExposedToNoSymptoms>() = mio::ParameterDistributionLogNormal (4 ., 1 .);
@@ -131,8 +131,8 @@ mio::abm::Model make_model(const mio::RandomNumberGenerator& rng)
131131 std::vector<ScalarType> infection_distribution{0.5 , 0.3 , 0.05 , 0.05 , 0.05 , 0.05 , 0.0 , 0.0 };
132132 for (auto & person : model.get_persons ()) {
133133 mio::abm::InfectionState infection_state = mio::abm::InfectionState (
134- mio::DiscreteDistribution<size_t >::get_instance ()(mio::thread_local_rng (), infection_distribution));
135- auto person_rng = mio::abm::PersonalRandomNumberGenerator (person);
134+ mio::DiscreteDistribution<size_t >::get_instance ()(model. get_rng (), infection_distribution));
135+ auto person_rng = mio::abm::PersonalRandomNumberGenerator (model. get_rng (), person);
136136 if (infection_state != mio::abm::InfectionState::Susceptible) {
137137 person.add_new_infection (mio::abm::Infection (person_rng, mio::abm::VirusVariant::Wildtype, person.get_age (),
138138 model.parameters , start_date, infection_state));
@@ -175,53 +175,64 @@ int main()
175175 auto tmax = t0 + mio::abm::days (5 );
176176 // Set the number of simulations to run in the study
177177 const size_t num_runs = 3 ;
178+ // Set up an RNG
179+ mio::RandomNumberGenerator rng;
178180
179- // Create a parameter study.
180- // Note that the study for the ABM currently does not make use of the arguments "parameters" or "dt", as we create
181- // a new model for each simulation. Hence we set both arguments to 0.
182- // This is mostly due to https://github.com/SciCompMod/memilio/issues/1400
183- mio::ParameterStudy study (0 , t0, tmax, mio::abm::TimeSpan (0 ), num_runs);
181+ // Optional: set seeds to get reproducible results
182+ // rng.seed({1234, 2345, 124324, 7567, 34534, 7});
183+
184+ // Always synchronise seeds before creating the model, otherwise different MPI ranks will create different models
185+ // If you do want to use a different model each run, move the model setup from the ParameterStudy creation into the
186+ // simulation setup (i.e. into the create_simulation function argument of ParameterStudy::run)
187+ rng.synchronize ();
188+ auto model = make_model (rng);
184189
185- // Optional: set seeds to get reproducable results
186- // study.get_rng().seed({12341234, 53456, 63451, 5232576, 84586, 52345});
190+ // Create a parameter study.
191+ // Note that the ABM's step size is fixed, hence we set the effectively unused "dt" argument to 0
192+ mio::ParameterStudy study (std::move (model), t0, tmax, mio::abm::TimeSpan (0 ), num_runs);
193+ study.get_rng () = rng; // use the same RNG as the model
187194
188195 const std::string result_dir = mio::path_join (mio::base_dir (), " example_results" );
189196 if (!mio::create_directory (result_dir)) {
190197 mio::log_error (" Could not create result directory \" {}\" ." , result_dir);
191198 return 1 ;
192199 }
193200
201+ // Run the study
202+ // The first lambda ("create_simulation" argument) sets up the simulation, the second ("process_simulation_result")
203+ // allows us to process each simulations result. Be mindful of the memory used for storing these results!
194204 auto ensemble_results = study.run (
195- [](auto , auto t0_, auto , size_t ) {
196- return mio::abm::ResultSimulation (make_model (mio::thread_local_rng ()), t0_);
205+ [](auto && model_, auto t0_, auto , size_t run_idx) {
206+ auto copy = model_;
207+ // using half of the RNG counter for the run index (soft-) limits both the number of runs and RNG draws
208+ // per person to 2^16 = 65536
209+ const auto ctr = mio::Counter<uint32_t >(static_cast <uint32_t >(run_idx) << 16 );
210+ copy.reset_rng (ctr);
211+ return mio::abm::ResultSimulation (std::move (copy), t0_);
197212 },
198- [result_dir](auto && sim, auto && run_idx) {
213+ [& result_dir](auto && sim, auto && run_idx) {
199214 auto interpolated_result = mio::interpolate_simulation_result (sim.get_result ());
200215 std::string outpath = mio::path_join (result_dir, " abm_minimal_run_" + std::to_string (run_idx) + " .txt" );
201216 std::ofstream outfile_run (outpath);
202217 sim.get_result ().print_table (outfile_run, {" S" , " E" , " I_NS" , " I_Sy" , " I_Sev" , " I_Crit" , " R" , " D" }, 7 , 4 );
203218 std::cout << " Results written to " << outpath << std::endl;
204- auto params = std::vector<mio::abm::Model>{};
205219 return std::vector{interpolated_result};
206220 });
207221
208- if (ensemble_results.size () > 0 ) {
209- auto ensemble_results_p05 = ensemble_percentile (ensemble_results, 0.05 );
210- auto ensemble_results_p25 = ensemble_percentile (ensemble_results, 0.25 );
211- auto ensemble_results_p50 = ensemble_percentile (ensemble_results, 0.50 );
212- auto ensemble_results_p75 = ensemble_percentile (ensemble_results, 0.75 );
213- auto ensemble_results_p95 = ensemble_percentile (ensemble_results, 0.95 );
214-
215- mio::unused (save_result (ensemble_results_p05, {0 }, num_age_groups,
216- mio::path_join (result_dir, " Results_" + std::string (" p05" ) + " .h5" )));
217- mio::unused (save_result (ensemble_results_p25, {0 }, num_age_groups,
218- mio::path_join (result_dir, " Results_" + std::string (" p25" ) + " .h5" )));
219- mio::unused (save_result (ensemble_results_p50, {0 }, num_age_groups,
220- mio::path_join (result_dir, " Results_" + std::string (" p50" ) + " .h5" )));
221- mio::unused (save_result (ensemble_results_p75, {0 }, num_age_groups,
222- mio::path_join (result_dir, " Results_" + std::string (" p75" ) + " .h5" )));
223- mio::unused (save_result (ensemble_results_p95, {0 }, num_age_groups,
224- mio::path_join (result_dir, " Results_" + std::string (" p95" ) + " .h5" )));
222+ // The study collects all results on the root rank, so we only process the results there
223+ if (mio::mpi::is_root ()) {
224+ const auto write_percentile = [&](double p) {
225+ std::ofstream out (mio::path_join (result_dir, fmt::format (" Results_p{:0<4.2}.txt" , p)));
226+ auto ensemble_percentiles = ensemble_percentile (ensemble_results, p);
227+ ensemble_percentiles.front ().print_table (out, {" S" , " E" , " I_NS" , " I_Sy" , " I_Sev" , " I_Crit" , " R" , " D" }, 7 ,
228+ 4 );
229+ };
230+
231+ write_percentile (0.05 );
232+ write_percentile (0.25 );
233+ write_percentile (0.50 );
234+ write_percentile (0.75 );
235+ write_percentile (0.95 );
225236 }
226237
227238 mio::mpi::finalize ();
0 commit comments