Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
840303d
Add scripts for analyzing infection states and ICU data from simulati…
xsaschako May 27, 2025
c7ffab3
Remove unused imports and enable plotting functions in infection stat…
xsaschako May 27, 2025
e986d96
Refactor infection states plotting script: enhance documentation, imp…
xsaschako May 27, 2025
6e4c6fe
Refactor plotAbmInfectionStates.py: update authorship, enhance module…
xsaschako May 27, 2025
663a189
Add unit tests for plotAbmInfectionStates: implement comprehensive te…
xsaschako May 27, 2025
28c3187
Refactor setup.py: enhance PylintCommand documentation and update com…
xsaschako May 27, 2025
2ba672e
Fix comment in setup.py: update example for Excel file types in insta…
xsaschako May 27, 2025
baa272b
Remove plotAbmICUAndDeadComp.py: delete unused script for ICU and dea…
xsaschako May 27, 2025
20b651e
formatting
xsaschako May 27, 2025
706aea0
Implement martins suggestions
xsaschako May 28, 2025
ab2e0e8
Refactor plotAbmInfectionStates.py: update paths to use command line …
xsaschako May 28, 2025
e4df6cb
Julias suggestions p1
xsaschako Jun 14, 2025
c5cf7dc
julia review p2
xsaschako Jun 14, 2025
f5dffd6
julia review pt3
xsaschako Jun 15, 2025
f124f04
Merge branch 'main' into 1290-add-abm-visualization-from-paper-to-mem…
xsaschako Jun 15, 2025
75e1105
format
xsaschako Jun 15, 2025
ddb9cb4
fix tests
xsaschako Jun 15, 2025
c2433fc
julia review pt5
xsaschako Jun 21, 2025
606ba7f
julia review pt7
xsaschako Jun 21, 2025
e316b6a
fix: improve error message for missing file in fake filesystem
xsaschako Jun 21, 2025
a446792
Merge branch 'main' into 1290-add-abm-visualization-from-paper-to-mem…
xsaschako Jun 23, 2025
8a002c7
Update test_surrogatemodel_ode_secir_groups.py
xsaschako Jun 23, 2025
392ec54
Update test_surrogatemodel_ode_secir_groups.py
xsaschako Jun 23, 2025
cb08197
Merge branch '1210-abm-paper-logger' into 1290-add-abm-visualization-…
xsaschako Mar 9, 2026
7ccdb45
update
xsaschako Mar 9, 2026
2132e8a
update example
xsaschako Mar 9, 2026
0c5c13a
Merge branch '1290-add-abm-visualization-from-paper-to-memilio-plot' …
xsaschako Mar 9, 2026
0fdbb15
update
xsaschako Mar 9, 2026
e22dd77
redo
xsaschako Mar 9, 2026
7d544bf
update
xsaschako Mar 9, 2026
9724423
fix comment formatting in plotAbmInfectionStates.py
xsaschako Mar 9, 2026
22044c6
update
xsaschako Mar 9, 2026
6c6879f
update
xsaschako Mar 9, 2026
49d0587
update
xsaschako Mar 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 54 additions & 21 deletions cpp/examples/abm_parameter_study.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "memilio/utils/stl_util.h"

#include <string>
#include <filesystem>

constexpr size_t num_age_groups = 4;

Expand Down Expand Up @@ -173,9 +174,9 @@ int main()

// Set start and end time for the simulation.
auto t0 = mio::abm::TimePoint(0);
auto tmax = t0 + mio::abm::days(5);
auto tmax = t0 + mio::abm::days(10);
// Set the number of simulations to run in the study
const size_t num_runs = 3;
const size_t num_runs = 10;

// 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
Expand All @@ -187,42 +188,74 @@ int main()
// study.get_rng().seed({12341234, 53456, 63451, 5232576, 84586, 52345});

const std::string result_dir = mio::path_join(mio::base_dir(), "example_results");
std::filesystem::remove_all(result_dir);
if (!mio::create_directory(result_dir)) {
mio::log_error("Could not create result directory \"{}\".", result_dir);
return 1;
}

const std::string result_directory_standard = mio::path_join(result_dir, "standart_results");
if (!mio::create_directory(result_directory_standard)) {
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directory name "standart_results" contains a typo: it should be "standard_results". This typo will appear in the output directory path visible to users.

Copilot uses AI. Check for mistakes.
mio::log_error("Could not create result directory \"{}\".", result_directory_standard);
return 1;
}

const std::string result_dir_detailed = mio::path_join(result_dir, "detailed");
if (!mio::create_directory(result_dir_detailed)) {
mio::log_error("Could not create result directory \"{}\".", result_dir_detailed);
return 1;
}

auto ensemble_results = study.run(
[](auto, auto t0_, auto, size_t) {
return mio::abm::ResultSimulation(make_model(mio::thread_local_rng()), t0_);
},
[result_dir](auto&& sim, auto&& run_idx) {
auto interpolated_result = mio::interpolate_simulation_result(sim.get_result());
auto interpolated_result = mio::interpolate_simulation_result(sim.get_result());
auto interpolated_result_detailed = mio::interpolate_simulation_result(sim.get_result_detailed());

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<mio::abm::Model>{};
return std::vector{interpolated_result};

return std::vector<mio::TimeSeries<double>>{interpolated_result, interpolated_result_detailed};
});

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")));

std::vector<std::vector<mio::TimeSeries<double>>> ensemble_new, ensemble_detailed;
for (auto& run : ensemble_results) {
ensemble_new.push_back({std::move(run[0])});
ensemble_detailed.push_back({std::move(run[1])});
}

// Percentiles for aggregated results
auto new_p05 = ensemble_percentile(ensemble_new, 0.05);
auto new_p25 = ensemble_percentile(ensemble_new, 0.25);
auto new_p50 = ensemble_percentile(ensemble_new, 0.50);
auto new_p75 = ensemble_percentile(ensemble_new, 0.75);
auto new_p95 = ensemble_percentile(ensemble_new, 0.95);

mio::unused(save_result(new_p05, {0}, num_age_groups, mio::path_join(result_directory_standard, "Results_p05.h5")));
mio::unused(save_result(new_p25, {0}, num_age_groups, mio::path_join(result_directory_standard, "Results_p25.h5")));
mio::unused(save_result(new_p50, {0}, num_age_groups, mio::path_join(result_directory_standard, "Results_p50.h5")));
mio::unused(save_result(new_p75, {0}, num_age_groups, mio::path_join(result_directory_standard, "Results_p75.h5")));
mio::unused(save_result(new_p95, {0}, num_age_groups, mio::path_join(result_directory_standard, "Results_p95.h5")));

// Percentiles for detailed results
auto det_p05 = ensemble_percentile(ensemble_detailed, 0.05);
auto det_p25 = ensemble_percentile(ensemble_detailed, 0.25);
auto det_p50 = ensemble_percentile(ensemble_detailed, 0.50);
auto det_p75 = ensemble_percentile(ensemble_detailed, 0.75);
auto det_p95 = ensemble_percentile(ensemble_detailed, 0.95);

mio::unused(save_result(det_p05, {0}, num_age_groups, mio::path_join(result_dir_detailed, "Results_p05.h5")));
mio::unused(save_result(det_p25, {0}, num_age_groups, mio::path_join(result_dir_detailed, "Results_p25.h5")));
mio::unused(save_result(det_p50, {0}, num_age_groups, mio::path_join(result_dir_detailed, "Results_p50.h5")));
mio::unused(save_result(det_p75, {0}, num_age_groups, mio::path_join(result_dir_detailed, "Results_p75.h5")));
mio::unused(save_result(det_p95, {0}, num_age_groups, mio::path_join(result_dir_detailed, "Results_p95.h5")));
}

mio::mpi::finalize();
Expand Down
61 changes: 61 additions & 0 deletions cpp/models/abm/common_abm_loggers.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,67 @@ struct LogInfectionState : mio::LogAlways {
}
};

struct LogInfectionStatePerAgeGroup : mio::LogAlways {
using Type = std::pair<mio::abm::TimePoint, Eigen::VectorXd>;
/**
* @brief Log the TimeSeries of the number of Person%s in an #InfectionState.
* @param[in] sim The simulation of the abm.
* @return A pair of the TimePoint and the TimeSeries of the number of Person%s in an #InfectionState.
*/
static Type log(const mio::abm::Simulation<>& sim)
{

Eigen::VectorXd sum = Eigen::VectorXd::Zero(
Eigen::Index((size_t)mio::abm::InfectionState::Count * sim.get_model().parameters.get_num_groups()));
const auto curr_time = sim.get_time();
const auto persons = sim.get_model().get_persons();


for (auto i = size_t(0); i < persons.size(); ++i) {
auto& p = persons[i];
auto index = (((size_t)(mio::abm::InfectionState::Count)) * ((uint32_t)p.get_age().get())) +
((uint32_t)p.get_infection_state(curr_time));
// PRAGMA_OMP(atomic)
sum[index] += 1;
}
return std::make_pair(curr_time, sum);
}
};
Comment on lines +192 to +217
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LogInfectionStatePerAgeGroup struct is defined in common_abm_loggers.h but is not used anywhere in the codebase — result_simulation.h uses LogInfectionState and LogInfectionPerLocationTypePerAgeGroup, and there are no other usages. Additionally, there is a commented-out PRAGMA_OMP(atomic) on line 212 that is dead code. These should either be removed or actually used.

Copilot uses AI. Check for mistakes.

struct LogInfectionPerLocationTypePerAgeGroup : mio::LogAlways {
using Type = std::pair<mio::abm::TimePoint, Eigen::VectorXd>;
Comment on lines +192 to +220
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both LogInfectionStatePerAgeGroup and LogInfectionPerLocationTypePerAgeGroup are missing class-level Doxygen /** @brief ... */ comment blocks before the struct declaration. All other logger structs in this file (LogLocationInformation, LogPersonInformation, LogDataForMobility, LogInfectionState) have these class-level doc comments. Adding them would be consistent with the established pattern in this file.

Copilot uses AI. Check for mistakes.
/**
* @brief Log the TimeSeries of the number of Person%s in an #InfectionState.
* @param[in] sim The simulation of the abm.
* @return A pair of the TimePoint and the TimeSeries of the number of Person%s in an #InfectionState.
*/
Comment on lines +219 to +225
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstrings for both LogInfectionStatePerAgeGroup and LogInfectionPerLocationTypePerAgeGroup are copy-pasted from LogInfectionState and describe "the number of Person%s in an #InfectionState" for both, but LogInfectionPerLocationTypePerAgeGroup actually logs newly infected persons per location type per age group. The docstring for LogInfectionPerLocationTypePerAgeGroup should be updated to accurately describe what it logs: the count of persons who newly transitioned to the Exposed state in the current time step, grouped by location type and age group.

Copilot uses AI. Check for mistakes.
static Type log(const mio::abm::Simulation<>& sim)
{

Eigen::VectorXd sum = Eigen::VectorXd::Zero(
Eigen::Index((size_t)mio::abm::LocationType::Count * sim.get_model().parameters.get_num_groups()));
auto curr_time = sim.get_time();
auto prev_time = sim.get_prev_time();
const auto persons = sim.get_model().get_persons();


for (auto i = size_t(0); i < persons.size(); ++i) {
auto& p = persons[i];


if ((p.get_infection_state(prev_time) != mio::abm::InfectionState::Exposed) &&
(p.get_infection_state(curr_time) == mio::abm::InfectionState::Exposed)) {
auto index = (((size_t)(mio::abm::LocationType::Count)) * ((uint32_t)p.get_age().get())) +
((uint32_t)p.get_location_type());
sum[index] += 1;

}
}
return std::make_pair(curr_time, sum);
}
};


/**
* @brief This is like the DataWriterToMemory, but it only logs time series data.
* @tparam Loggers The loggers that are used to log data. The loggers must return a touple with a TimePoint and a value.
Expand Down
22 changes: 18 additions & 4 deletions cpp/models/abm/result_simulation.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,33 @@ class ResultSimulation : public Simulation<M>
*/
void advance(TimePoint tmax)
{
Simulation<Model>::advance(tmax, history);
Simulation<Model>::advance(tmax, history, history_detailed);
}

/**
* @brief Return the simulation result aggregated by infection states.
*/
const mio::TimeSeries<double>& get_result() const
mio::TimeSeries<double> get_result() const
{
return get<0>(history.get_log());
return std::get<0>(history.get_log());
}
Comment on lines +54 to 57
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_result() function's return type was changed from const mio::TimeSeries<double>& to mio::TimeSeries<double> (returning by value). This causes an unnecessary copy of the entire TimeSeries object on every call, including the two calls in abm_parameter_study.cpp lines 215 and 220 (sim.get_result() is called twice). Since TimeSeries can be large (many time points × many elements), this is a performance regression. The original const& return was intentional and efficient; changing it to return by value should be justified or reverted.

Copilot uses AI. Check for mistakes.

/**
* @brief Return the detailed simulation result aggregated by infection states.
*/
mio::TimeSeries<double> get_result_detailed() const
{
return std::get<0>(history_detailed.get_log());
}
Comment on lines +62 to +65
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new LogInfectionPerLocationTypePerAgeGroup logger and get_result_detailed() method in ResultSimulation have no unit tests. The existing test_abm_simulation.cpp tests LogInfectionState and ResultSimulation::get_result() but does not test the new detailed result. Given the comprehensive testing of other loggers in test_abm_simulation.cpp, the new logger and the get_result_detailed() function should be covered as well, particularly verifying correct detection of new infection events and their assignment to location types.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +65
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring for get_result_detailed() is incorrectly indented (extra leading spaces before /**) and its content says "aggregated by infection states" which is inaccurate — the detailed result is aggregated by location type per age group (using LogInfectionPerLocationTypePerAgeGroup), not by infection states.

Copilot uses AI. Check for mistakes.


mio::History<TimeSeriesWriter, LogInfectionState> history{
Eigen::Index(InfectionState::Count)}; ///< History used to create the result TimeSeries.
Eigen::Index(InfectionState::Count)};
Comment on lines 68 to +69
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline doc comment ///< History used to create the result TimeSeries. was removed from the history member declaration. Given the style of the existing codebase and result_simulation.h, member variables should have doc comments. The history member lost its documentation in this PR without replacement.

Copilot uses AI. Check for mistakes.

mio::History<TimeSeriesWriter, LogInfectionPerLocationTypePerAgeGroup> history_detailed{
Eigen::Index(LocationType::Count) * this->get_model().parameters.get_num_groups()};


};

} // namespace abm
Expand Down
13 changes: 13 additions & 0 deletions cpp/models/abm/simulation.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Simulation
Simulation(TimePoint t0, Model&& model)
: m_model(std::move(model))
, m_t(t0)
, m_t_prev(t0-hours(1))
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The m_t_prev member is initialized to t0 - hours(1) in the constructor. The Simulation::advance() function logs the initial system state before entering the loop by calling history.log(*this). At this initial log call, LogInfectionPerLocationTypePerAgeGroup uses prev_time = m_t_prev = t0 - hours(1) and curr_time = t0. For any person who starts in the Exposed state at t0, get_infection_state(prev_time) will return Susceptible (since prev_time is before their infection course starts), and get_infection_state(curr_time) returns Exposed. This causes all initially Exposed persons to be incorrectly counted as "new infections" in the very first log entry, even though they were set up as initially infected — not newly infected at that time step.

Copilot uses AI. Check for mistakes.
, m_dt(hours(1))
{
}
Expand Down Expand Up @@ -85,6 +86,15 @@ class Simulation
return m_t;
}

/**
* @brief Get the previous time of the Simulation.
*/
TimePoint get_prev_time() const
{
return m_t_prev;
}


/**
* @brief Get the Model that this Simulation evolves.
*/
Expand All @@ -103,11 +113,14 @@ class Simulation
{
auto dt = std::min(m_dt, tmax - m_t);
m_model.evolve(m_t, dt);
m_t_prev = m_t;
m_t += m_dt;

}

Model m_model; ///< The Model to simulate.
TimePoint m_t; ///< The current TimePoint of the Simulation.
TimePoint m_t_prev; ///< The previous TimePoint of the Simulation.
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a double space in the member comment: ///< The previous TimePoint of the Simulation.

Copilot uses AI. Check for mistakes.
TimeSpan m_dt; ///< The length of the time steps.
};

Expand Down
Loading
Loading