Skip to content

Commit 8eb7f81

Browse files
committed
Merge branch 'main' of github.com:SciCompMod/memilio-tutorials
2 parents 72b35cd + 00ad934 commit 8eb7f81

24 files changed

Lines changed: 501 additions & 549 deletions

.github/workflows/run-tutorials.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# Install memilio-simulation from the cloned repo (C++ extension, needs cmake/ninja)
3737
uv add --dev ./memilio/pycode/memilio-simulation
3838
# Install tutorial dependencies
39-
uv add marimo matplotlib pandas numpy pyabc[pyarrow] bayesflow tensorflow nbformat
39+
uv add -r requirements.txt -c requirements.txt
4040
4141
- name: Run tutorials and export to HTML
4242
run: |

cpp-tutorials/abm/parameter_setter.cpp

Lines changed: 48 additions & 182 deletions
Large diffs are not rendered by default.

cpp-tutorials/abm/tutorial_abm_households.cpp

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
*
2525
* MEmilio provides an agent-based model (ABM) where each individual ("agent")
2626
* has an age, a home, and assigned locations they visit during the day.
27-
* Agents transition between infection states (Susceptible, Exposed, ..., Dead)
28-
* based on stochastic contact events at their current location.
27+
* Agents' transitions between infection states (Susceptible, Exposed, ..., Dead)
28+
* are based on stochastic contact events at their current location.
2929
*
3030
* This tutorial demonstrates how to:
3131
* 1. Define age groups and configure model parameters.
@@ -37,9 +37,9 @@
3737
*
3838
* Key concept -- HouseholdMember age weights:
3939
* Each HouseholdMember carries an integer weight for every AgeGroup.
40-
* When a person is created for that member slot, the model draws its age
40+
* When a person is created as a HouseholdMember, the model draws its age
4141
* from a discrete distribution proportional to these weights.
42-
* Example: weights {1, 1, 0, 0} give a 50/50 chance of AgeGroup 0 or 1.
42+
* Example: weights {1, 1, 0, 0} gives a 50/50 chance of AgeGroup 0 or 1.
4343
*/
4444

4545
#include "abm/household.h"
@@ -56,7 +56,7 @@ int main(int argc, char* argv[])
5656
{
5757
// Usage: tutorial_abm_household [n_households] [infected_frac] [sim_days]
5858
// n_households : number of each household type (default: 125)
59-
// infected_frac : fraction initially infected (default: 0.2)
59+
// infected_frac : fraction of initially infected (default: 0.2)
6060
// sim_days : simulation duration in days (default: 30)
6161
int arg_n_households = (argc > 1) ? std::atoi(argv[1]) : 125;
6262
double arg_infected_frac = (argc > 2) ? std::atof(argv[2]) : 0.2;
@@ -77,14 +77,10 @@ int main(int argc, char* argv[])
7777
const auto age_group_60_to_79 = mio::AgeGroup(4); // seniors
7878
const auto age_group_80_plus = mio::AgeGroup(5); // elderly
7979

80-
// *** Create the model and set infection parameters. ***
81-
// The Model holds all persons, locations, and parameters. We pass in the
82-
// number of age groups so that all parameter arrays are sized correctly.
83-
// `set_local_parameters` and `set_world_parameters` fill in realistic
84-
// epidemiological values (see parameter_setter.h).
80+
// *** Create the model. ***
81+
// The Model holds all persons, locations, and parameters. We need to add the locations
82+
// before we set the parameters as some parameters depend on the locations.
8583
auto model = mio::abm::Model(num_age_groups);
86-
set_local_parameters(model);
87-
set_world_parameters(model.parameters);
8884

8985
// Define which age groups are eligible to go to school and to work.
9086
// The AgeGroupGotoSchool / AgeGroupGotoWork arrays default to false for
@@ -121,7 +117,7 @@ int main(int argc, char* argv[])
121117

122118
// *** Compose households and add them to the model. ***
123119
// A Household collects (member_type, count) pairs. A HouseholdGroup bundles
124-
// many copies of a Household template. `add_household_group_to_model`
120+
// many copies of a Household template. `add_household_group_to_model`,
125121
// creates the actual persons and their home locations.
126122
//
127123
// CLI parameters (see usage at top of main):
@@ -187,10 +183,16 @@ int main(int argc, char* argv[])
187183
// One workplace for all working adults.
188184
auto work = model.add_location(mio::abm::LocationType::Work);
189185

186+
// *** Set paramters for all locations. ***
187+
// `set_local_parameters` and `set_world_parameters` fill in realistic
188+
// epidemiological values (see parameter_setter.h).
189+
set_local_parameters(model);
190+
set_world_parameters(model.parameters);
191+
190192
// *** Assign initial infection states. ***
191193
// Each person draws a random infection state from the distribution below.
192194
// Persons who are not Susceptible receive a full Infection object so their
193-
// viral-load course and state transitions are properly initialised.
195+
// viral-load course and state transitions are properly initialized.
194196
//
195197
// Index | InfectionState | Probability
196198
// ------|--------------------------------|------------
@@ -202,7 +204,7 @@ int main(int argc, char* argv[])
202204
// 5 | InfectedCritical (I_Crit) | 0.0
203205
// 6 | Recovered | 0.0
204206
// 7 | Dead | 0.0
205-
auto start_date = mio::abm::TimePoint(0); // t = 0 s from the simulation epoch
207+
auto start_date = mio::abm::TimePoint(0); // t = 0
206208

207209
// Build infection distribution from the infected fraction.
208210
// The non-susceptible portion is split: 25% Exposed, 50% I_NS, 25% I_Sy.
@@ -264,4 +266,4 @@ int main(int argc, char* argv[])
264266
std::cout << "Results written to abm_household.txt\n";
265267

266268
return 0;
267-
}
269+
}

cpp-tutorials/abm/tutorial_abm_testing.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ const auto age_group_80_plus = mio::AgeGroup(5);
6464
// - Is active for the full 30-day simulation window.
6565
// - Uses a PCR test (sensitivity/specificity come from TestData defaults).
6666
// - Tests are valid for `validity_days`; a negative result exempts retesting for validity_days days (Default: 3).
67-
// - Targets persons in any infected state (Exposed through Critical).
67+
// - Targets persons in any infected state (Exposed to Critical).
6868
// - Applies to all age groups except school children (5-14).
6969
// - Tests with `testing_probability` probability upon entry (Default: 100%).
7070
// - Applies to all public locations (SocialEvent, School, Work, BasicsShop)
@@ -90,7 +90,7 @@ void add_npi_testing_strategies_to_world(mio::abm::Model& model, double testing_
9090

9191
auto testing_criteria = mio::abm::TestingCriteria(ages_to_test, states_to_test);
9292
auto testing_scheme = mio::abm::TestingScheme(testing_criteria, validity, start_date_test, end_date_test,
93-
pcr_test_parameters, testing_probability);
93+
pcr_test_parameters, testing_probability);
9494

9595
// Attach the scheme to all public location types.
9696
model.get_testing_strategy().add_scheme(mio::abm::LocationType::SocialEvent, testing_scheme);
@@ -117,8 +117,6 @@ int main(int argc, char* argv[])
117117

118118
// *** Create the model and set infection parameters. ***
119119
auto model = mio::abm::Model(num_age_groups);
120-
set_local_parameters(model);
121-
set_world_parameters(model.parameters);
122120

123121
// Define which age groups are eligible to go to school and to work.
124122
// The AgeGroupGotoSchool / AgeGroupGotoWork arrays default to false for
@@ -218,6 +216,10 @@ int main(int argc, char* argv[])
218216
// One workplace for all working adults.
219217
auto work = model.add_location(mio::abm::LocationType::Work);
220218

219+
// *** Set paramters for all locations (same as Tutorial 1). ***
220+
set_local_parameters(model);
221+
set_world_parameters(model.parameters);
222+
221223
// *** Assign initial infection states. ***
222224
//
223225
// Index | InfectionState | Probability
@@ -291,4 +293,4 @@ int main(int argc, char* argv[])
291293
std::cout << "Results written to abm_tests.txt\n";
292294

293295
return 0;
294-
}
296+
}

cpp-tutorials/abm/tutorial_abm_vaccination.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ int main(int argc, char* argv[])
186186
// use_data_vacc : 1 = apply JSON data-driven vaccination, 0 = skip (default: 0)
187187
// Note that no vaccination takes place if json file should be used but cannot be found!
188188
double arg_vacc_rate = (argc > 1) ? std::atof(argv[1]) : 0.0;
189-
int arg_n_households = (argc > 2) ? std::atoi(argv[2]) : 1000;
189+
int arg_n_households = (argc > 2) ? std::atoi(argv[2]) : 125;
190190
double arg_protection_peak = (argc > 3) ? std::atof(argv[3]) : 0.67;
191191
int arg_use_data_vacc = (argc > 4) ? std::atoi(argv[4]) : 0;
192192

@@ -202,10 +202,8 @@ int main(int argc, char* argv[])
202202
const auto age_group_60_to_79 = mio::AgeGroup(4); // seniors
203203
const auto age_group_80_plus = mio::AgeGroup(5); // elderly
204204

205-
// *** Create the model and set infection parameters (same as Tutorial 1). ***
205+
// *** Create the model (same as Tutorial 1). ***
206206
auto model = mio::abm::Model(num_age_groups);
207-
set_local_parameters(model);
208-
set_world_parameters(model.parameters);
209207

210208
// Define which age groups are eligible to go to school and to work.
211209
// The AgeGroupGotoSchool / AgeGroupGotoWork arrays default to false for
@@ -238,7 +236,7 @@ int main(int argc, char* argv[])
238236
model.parameters.get<mio::abm::SeverityProtectionFactor>()[{mio::abm::ProtectionType::GenericVaccine, age,
239237
mio::abm::VirusVariant::Wildtype}] =
240238
mio::TimeSeriesFunctor<ScalarType>{mio::TimeSeriesFunctorType::LinearInterpolation,
241-
{{0, 0.0}, {1, 0.85}, {180, 0.70}}};
239+
{{0, 0.0}, {14, 0.85}, {180, 0.70}}};
242240
}
243241

244242
std::cout << "Vaccination protection factors configured.\n";
@@ -321,6 +319,10 @@ int main(int argc, char* argv[])
321319
// One workplace for all working adults.
322320
auto work = model.add_location(mio::abm::LocationType::Work);
323321

322+
// *** Set paramters for all locations (same as Tutorial 1). ***
323+
set_local_parameters(model);
324+
set_world_parameters(model.parameters);
325+
324326
// *** Assign initial infection states. ***
325327
//
326328
// Index | InfectionState | Probability
@@ -419,10 +421,9 @@ int main(int argc, char* argv[])
419421
// ── Data-driven mode ─────────────────────────────────────────
420422
// Read real-world vaccination counts from a JSON file and apply
421423
// them day-by-day to the model's persons.
422-
const std::string vacc_json_path =
423-
"/Users/saschakorf/Documents/Promotion/memilio-tutorials/cpp-tutorials/abm/vacc_county_ageinf_ma7.json";
424-
const int county_id = 1002;
425-
const mio::Date sim_start = mio::Date(2020, 10, 1);
424+
const std::string vacc_json_path = "./vacc_county_ageinf_ma7.json";
425+
const int county_id = 1002;
426+
const mio::Date sim_start = mio::Date(2020, 10, 1);
426427

427428
auto vacc_map = prepare_vaccination_data(vacc_json_path, num_age_groups, county_id);
428429

@@ -452,4 +453,4 @@ int main(int argc, char* argv[])
452453
std::cout << "Results written to abm_vaccination.txt\n";
453454

454455
return 0;
455-
}
456+
}

cpp-tutorials/exercises/exercise03.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ int main()
4848
// all currently active dampings of one level and different types are summed up. If two dampings have different
4949
// levels (independent of the type) they are combined multiplicatively. In the following we apply a `Damping`
5050
// of 0.9 after 10 days and another damping of 0.6 after 20 days which means that the contacts are reduced
51-
// by 10% and 40%, respectively. To always retain a minimum level of contacts, a minimum contact frequency can
51+
// by 90% and 60%, respectively. To always retain a minimum level of contacts, a minimum contact frequency can
5252
// be set that is never deceeded. In our example we set this minimum contact rate to 0.
5353
contact_matrix[0].add_damping(0.9, mio::SimulationTime<ScalarType>(10.));
5454
contact_matrix[0].add_damping(0.6, mio::SimulationTime<ScalarType>(20.));
5555

56-
//EXERCISE: Please create a damping that replaces the 10% reduction after 10 days by a 20% reduction. Additionally, add a damping after 40 days that increases the contact rate by 50%.
56+
//EXERCISE: Please create a damping that replaces the 90% reduction after 10 days by a 20% reduction. Additionally, add a damping after 40 days that increases the contact rate by 50%.
5757

5858
// Again, we start with 0.5% of the population initially in `Exposed` and 0.5% initially in `InfectedNoSymptoms`
5959
// while the remaining 99% is `Susceptible`.

cpp-tutorials/git_tag.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
set(GIT_TAG_MEMILIO "70648381af0d37ff0f6ab6be8995bcae1a110fc6")
1+
set(GIT_TAG_MEMILIO "20cee90e7ce55b0107f0607df36e50b9e4a49889")

cpp-tutorials/tutorial03.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ int main()
4646
// all currently active dampings of one level and different types are summed up. If two dampings have different
4747
// levels (independent of the type) they are combined multiplicatively. In the following we apply a `Damping`
4848
// of 0.9 after 10 days and another damping of 0.6 after 20 days which means that the contacts are reduced
49-
// by 10% and 40%, respectively. To always retain a minimum level of contacts, a minimum contact frequency can
49+
// by 90% and 60%, respectively. To always retain a minimum level of contacts, a minimum contact frequency can
5050
// be set that is never deceeded. In our example we set this minimum contact rate to 0.
5151
contact_matrix[0].add_damping(0.9, mio::SimulationTime<ScalarType>(10.));
5252
contact_matrix[0].add_damping(0.6, mio::SimulationTime<ScalarType>(20.));

exercises/exercise01.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def _(mo):
250250

251251
@app.cell
252252
def _(result):
253-
result.print_table()
253+
print(result.print_table(return_string=True))
254254
return
255255

256256

@@ -280,7 +280,7 @@ def _(mo):
280280

281281
@app.cell
282282
def _(interpolated_result):
283-
interpolated_result.print_table()
283+
print(interpolated_result.print_table(return_string=True))
284284
return
285285

286286

exercises/exercise02.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def _(mo):
4747
def _(np):
4848
import memilio.simulation.osecir as osecir
4949
from memilio.simulation import AgeGroup, LogLevel, set_log_level
50-
set_log_level(LogLevel.Off)
50+
set_log_level(LogLevel.Error)
5151

5252
# Initialize total population, simulation start time, timeframe, and step size
5353
total_population = 100000
@@ -76,11 +76,14 @@ def _(np):
7676
model.parameters.DeathsPerCritical[group] = 0.3
7777

7878
# Set contact frequency
79-
model.parameters.ContactPatterns.cont_freq_mat[0].baseline = np.ones((1, 1)) * 10
79+
model.parameters.ContactPatterns.cont_freq_mat[0].baseline = np.ones(
80+
(1, 1)) * 10
8081

8182
# Initialize compartments
82-
model.populations[group, osecir.InfectionState.Exposed] = 0.005 * total_population
83-
model.populations[group, osecir.InfectionState.InfectedNoSymptoms] = 0.005 * total_population
83+
model.populations[group,
84+
osecir.InfectionState.Exposed] = 0.005 * total_population
85+
model.populations[group,
86+
osecir.InfectionState.InfectedNoSymptoms] = 0.005 * total_population
8487
model.populations.set_difference_from_total(
8588
(group, osecir.InfectionState.Susceptible), total_population)
8689
return dt, model, osecir, t0, tmax
@@ -122,7 +125,8 @@ def _(dt, model, osecir, t0, tmax):
122125

123126
@app.cell
124127
def _(results):
125-
compartments = results[0] # The first element of results contains the compartment data
128+
# The first element of results contains the compartment data
129+
compartments = results[0]
126130
flows = results[1] # The second element of results contains the flow data
127131
return compartments, flows
128132

@@ -162,7 +166,6 @@ def _(flows, np, osecir):
162166
plot_time = time_days[1:]
163167

164168
print(flow_array.shape)
165-
print("ji")
166169
return daily_flows, plot_time
167170

168171

@@ -196,12 +199,13 @@ def _(mo):
196199
def _(compartments, daily_flows, osecir, plot_time, plt):
197200
# 1. Prepare compartment data for comparison
198201
# Interpolate to match the time grid of our flows
199-
comp_array = osecir.interpolate_simulation_result(compartments).as_ndarray()
202+
comp_array = osecir.interpolate_simulation_result(
203+
compartments).as_ndarray()
200204

201205
# Extract the "InfectedSevere" compartment (Currently Hospitalized)
202206
# Adding 1 because row 0 is the time axis
203207
state_severe_idx = 1 + int(osecir.InfectionState.InfectedSevere)
204-
current_severe = comp_array[state_severe_idx, 1:]
208+
current_severe = comp_array[state_severe_idx, 1:]
205209

206210
# 2. Flow indices
207211
new_symptomatic_idx = 2
@@ -216,8 +220,10 @@ def _(compartments, daily_flows, osecir, plot_time, plt):
216220
fig, ax = plt.subplots(1, 2, figsize=(14, 6))
217221

218222
# --- Subplot 1: Disease Progression (The Time Lag) ---
219-
ax[0].bar(plot_time, daily_flows[new_symptomatic_idx, :], color='coral', alpha=0.5, label='New Symptomatic Cases')
220-
ax[0].bar(plot_time, daily_flows[new_hospitalized_idx, :], color='darkred', alpha=0.8, label='New Hospitalizations')
223+
ax[0].bar(plot_time, daily_flows[new_symptomatic_idx, :],
224+
color='coral', alpha=0.5, label='New Symptomatic Cases')
225+
ax[0].bar(plot_time, daily_flows[new_hospitalized_idx, :],
226+
color='darkred', alpha=0.8, label='New Hospitalizations')
221227
# EXERCISE: Extend subplot 1 to also show new ICU admissions.
222228
# Hint: add a third bar for daily_flows[new_icu_idx, :] with a suitable color and label,
223229
# e.g. color='maroon', alpha=0.9, label='New ICU Admissions'.
@@ -228,14 +234,16 @@ def _(compartments, daily_flows, osecir, plot_time, plt):
228234

229235
# --- Subplot 2: Incidence vs. Bed Occupancy ---
230236
# Axis 1: Daily New Cases (Incidence)
231-
ax[1].bar(plot_time, daily_flows[new_hospitalized_idx, :], color='steelblue', alpha=0.5, label='Daily Admissions (Incidence)')
237+
ax[1].bar(plot_time, daily_flows[new_hospitalized_idx, :],
238+
color='steelblue', alpha=0.5, label='Daily Admissions (Incidence)')
232239
ax[1].set_xlabel('Time [days]')
233240
ax[1].set_ylabel('Daily New Hospitalizations [#]', color='steelblue')
234241
ax[1].tick_params(axis='y', labelcolor='steelblue')
235242

236243
# Axis 2: Current Occupancy
237244
ax2 = ax[1].twinx()
238-
ax2.plot(plot_time, current_severe, color='black', linewidth=3, label='Currently Hospitalized')
245+
ax2.plot(plot_time, current_severe, color='black',
246+
linewidth=3, label='Currently Hospitalized')
239247
ax2.set_ylabel('Total Hospital Bed Occupancy [#]', color='black')
240248
ax2.tick_params(axis='y', labelcolor='black')
241249

0 commit comments

Comments
 (0)