From 79907ee38c1f48289699eecb9ce6070487b57373 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:18:42 +0200 Subject: [PATCH 001/147] a lot more instructions for the getting started/installation --- docs/source/getting_started.rst | 137 ++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 23 deletions(-) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 364a3192c5..436552a7de 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -37,37 +37,132 @@ Usage .. _installation: Installation -~~~~~~~~~~~~~ +~~~~~~~~~~~~ -You can download the latest code version of MEmilio from our `github repository `_ -in a Linux terminal via: +There are two main ways to set up MEmilio on your computer, depending on what you want to do: + +1. **Using the Python packages:** This is the recommended path for most users. You can run simulations using python bindings, download epidemiological data, and create plots all from Python, without needing to compile any C++ code yourself. +2. **Directly building the C++ Core:** This is for developers who want to modify the functionality, contribute new models etc. by running C++ code directly. + +Below, we will give you a step-by-step guide for both methods. If you're new to MEmilio, we recommend starting with the Python packages, as they are easier to use. + +Required Tools +***************** + +Before you can install MEmilio, you need to install some common development tools. + +* **Git:** This is a version control system used to download the project's source code. + + * **Windows:** Here, Git is not installed by default. Download and install it from `git-scm.com `_. + * **macOS & Linux:** Git is usually pre-installed. You can check by opening a terminal and typing ``git --version``. + +* **Python:** Required for the Python packages. + + * We recommend installing the latest version from the official website `python.org `_. + +* **C++ Compiler and CMake:** + + * **Windows:** The easiest way is to install **Visual Studio Community**. This includes a C++ compiler, CMake, and Git all in one. + * **macOS:** One option is installing the **Xcode Command Line Tools** by running ``xcode-select --install`` in your terminal. + * **Linux:** Install the essential build tools and CMake. On Debian/Ubuntu, you can do this with by running ``sudo apt-get install cmake gcc g++`` in your terminal. + +Step 1: Download the MEmilio Source Code +***************************************** + +Once the required tools are installed, open a terminaland download the MEmilio code with this command: .. code-block:: console git clone https://github.com/SciCompMod/memilio.git -You can now build the C++ code: +This command copies the entire MEmilio project into a new folder named ``memilio`` on your computer. -.. code-block:: console +.. note:: A Quick Note on HTTPS vs. SSH - cd memilio/cpp - mkdir build && cd build - cmake .. - cmake --build . + The ``git clone`` command above uses an **HTTPS** URL. This is the simplest method and works perfectly for downloading the code. -For details on the possible compile flags, help with errors and general a more detailed instruction, see the -:doc:`C++-interface ` section of this documentation. + However, if you plan to contribute code back to the project (i.e., "push" your changes), we recommend using **SSH**. To set this up, you can follow `GitHub's official guide on adding an SSH key `_. -For the installation of Python packages, e.g. ``memilio-epidata``, do + +Now, navigate into that folder: .. code-block:: console - - cd memilio/pycode - cd memilio-epidata - pip install . - -For more information, we refere to the :ref:`Python Interace Part ` of this documentation. + cd memilio + +From here, choose one of the following options. + +Option A: Installing the Python Packages (Recommended) +**************************************************** + +If you want to run simulations, download data, or create plots using Python, you only need to install our Python packages. + +1. Navigate to the directory containing our Python code: + + .. code-block:: console + + cd pycode + +2. From here, you can install the packages you need. For example, to install the ``memilio-epidata`` package for data downloading and handling, run: + + .. code-block:: console + + cd memilio-epidata + pip install -e . + +.. tip:: For Contributors: Installing development packages + + The ``-e`` flag installs the package in a mode, which links the installation to your local source code folder. + + If you plan to contribute to MEmilio, you can also install all the necessary development dependencies by adding ``[dev]`` to the command: + + .. code-block:: console + + pip install -e .[dev] + + For regular use, the simple ``pip install -e .`` is sufficient. + + +3. To install other packages, like ``memilio-simulation``, repeat the process: + + .. code-block:: console + + cd .. # Go back to the pycode directory + cd memilio-simulation + pip install -e . + + + +Option B: Building the C++ Core (Advanced) +**************************************** + +If you are a developer and want to build the C++ executables yourself, follow these instructions. + +1. Navigate to the C++ source code directory: + + .. code-block:: console + + cd cpp + +2. Create a separate directory for the build files. + + .. code-block:: console + + mkdir build && cd build + +3. Run CMake. This tool prepares the project for compilation on your specific system. + + .. code-block:: console + + cmake .. + +4. Compile the code and create the executables. + + .. code-block:: console + + cmake --build . + +For more detailed instructions, help with errors, and a list of compile options, please see the full :doc:`C++ Installation Guide `. Running simulations ~~~~~~~~~~~~~~~~~~~~~ @@ -84,10 +179,6 @@ Out of the box this works for all examples in the ``cpp/examples`` folder of our that do not depend on user-provided external libraries. Additional explanations for our models are linked at the corresponding sites of this documentation. -For the Python interface, you can find a short introduction in the :doc:`Python Interface ` section. - -Additionally we provide a python package for :doc:`surrogate models `, which can be used to c -reate fast approximations of our models. Loading data ~~~~~~~~~~~~~~~~~~~~~ @@ -112,4 +203,4 @@ For the latter we don't take any responsibilities! Further questions ~~~~~~~~~~~~~~~~~~~~~ -If you have any further questions, please take a look at our :doc:`faq` and feel free to contact us via `github `_. \ No newline at end of file +If you have any further questions, please take a look at our :doc:`faq` and feel free to contact us via `github `_. From c167266e3d640846275de9e2a8828fdf13dffb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Tue, 1 Jul 2025 15:29:21 +0200 Subject: [PATCH 002/147] getting started, index and ref --- docs/source/citation.rst | 2 +- docs/source/getting_started.rst | 43 ++++++++++++++++++--------------- docs/source/references.rst | 10 ++++++-- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/docs/source/citation.rst b/docs/source/citation.rst index a8e0a26779..3036d0e41b 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -3,7 +3,7 @@ Citing MEmilio If you use MEmilio, please cite our work -- Kühn, Martin Joachim et al. (2025). *MEmilio - a High Performance Modular Epidemics Simulation Software (2022)*. Available at `https://github.com/SciCompMod/memilio `_ and `https://elib.dlr.de/213614/ `_. +- Bicker J, Kerkmann D, Korf S, Ploetzke L, Schmieding R, Wendler A, Zunker H et al. (2025). *MEmilio - a High Performance Modular Epidemics Simulation Software*. Available at `https://github.com/SciCompMod/memilio `_ and `https://elib.dlr.de/213614/ `_. and, in particular, for diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 436552a7de..eafde3d996 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -7,21 +7,23 @@ Overview .. note:: This project is under active development. -MEmilio is an extensive framework for tasks around infectious disease modelling. It supports various :ref:`model ` types +MEmilio is an extensive framework for tasks around infectious disease modeling. It supports a multitude of :ref:`model ` types including :doc:`equation-based`, :doc:`agent-based `, -and :doc:`hybrid graph-ODE-based models ` as well as data integration and visualizations. +and :doc:`hybrid graph-ODE-based models `. It furthermore provides ready-to-use tools for data integration and visualizations. Among the equation-based models, we provide models based on :doc:`ordinary differential equations `, -:doc:`the linear chain trick, ` and its :doc:`generalisation `, :doc:`integro-differential equations ` -and :doc:`stochastic differential equations `. The MEmilio framework is written in two languages: C++ and Python. +:doc:`the linear chain trick (LCT), ` and a recent :doc:`generalized LCT `, :doc:`integro-differential equations ` +and :doc:`stochastic differential equations `. With simple definitions, models can be spatially or demograpically resolved. -- The C++ backend powers the model and simulation components for optimal efficiency. -- Data acquisition, plotting, and machine-learning models are handled via Python. +The MEmilio framework is written in two languages: C++ and Python. -For more details on the C++ implementation, see the sections on :doc:`model usage ` if you are interested -in using or applying our models and :doc:`model creation ` if you want to write new models inside our framework. +- The C++ backend contains efficient and optimized model implementations that further use parallelization to speed up execution and reduce waiting times. +- Python is used for data acquisition, plotting, and machine-learning models. +- We, furthermore, provide Python interfaces to selected models (implemented in C++) to allow the use and study of advanced models by users less experience in programming or computer science. -If you prefer using Python, you can use our :doc:`memilio-simulation ` package to run simulations -in our C++ backend; this package uses ``pybind11`` to bind our C++ model code. +For more details on using models implemented in C++ directly, see the sections on :doc:`model usage `. +For more details on implementing new infection dynamics models that could then be combined with, e.g., our mobility patterns, see :doc:`model creation `. + +If you prefer using Python to call or run our models, you can use our :doc:`memilio-simulation ` package to run simulations. The :doc:`memilio-epidata ` package provides tools to download and structure important data such as infection or mobility data. More about this and our other Python packages can be found in the :doc:`Python Interface Section ` of this documentation. @@ -39,12 +41,15 @@ Usage Installation ~~~~~~~~~~~~ -There are two main ways to set up MEmilio on your computer, depending on what you want to do: +There are two main ways to set up MEmilio on your computer or on a remote cluster or supercomputer, depending on what you want to do: -1. **Using the Python packages:** This is the recommended path for most users. You can run simulations using python bindings, download epidemiological data, and create plots all from Python, without needing to compile any C++ code yourself. +1. **Using the Python packages:** This is the recommended path for many users not familiar with C++. Here, you can run simulations using python bindings. 2. **Directly building the C++ Core:** This is for developers who want to modify the functionality, contribute new models etc. by running C++ code directly. -Below, we will give you a step-by-step guide for both methods. If you're new to MEmilio, we recommend starting with the Python packages, as they are easier to use. +In addition, we provide several Python packages to download epidemiological data or create plots from Python. + +Below, we will give you a step-by-step guide for both methods. If you are new to MEmilio and more familiar with Python, Julia, or R than with C++, we recommend starting with the Python packages, +as they provide an easy access to simulate infection dynamics models from and collect experiences with MEmilio. Required Tools ***************** @@ -53,23 +58,23 @@ Before you can install MEmilio, you need to install some common development tool * **Git:** This is a version control system used to download the project's source code. - * **Windows:** Here, Git is not installed by default. Download and install it from `git-scm.com `_. - * **macOS & Linux:** Git is usually pre-installed. You can check by opening a terminal and typing ``git --version``. + * **Windows:** By default, Git is not installed. Download and install it from `git-scm.com `_. + * **macOS & Linux:** Git is usually preinstalled. You can check by opening a terminal and typing ``git --version``. * **Python:** Required for the Python packages. - * We recommend installing the latest version from the official website `python.org `_. + * MEmilio is tested daily with Python 3.8 and 3.11. While other versions might also work, we recommend installing the latest version tested daily from the official website `python.org `_. * **C++ Compiler and CMake:** * **Windows:** The easiest way is to install **Visual Studio Community**. This includes a C++ compiler, CMake, and Git all in one. * **macOS:** One option is installing the **Xcode Command Line Tools** by running ``xcode-select --install`` in your terminal. - * **Linux:** Install the essential build tools and CMake. On Debian/Ubuntu, you can do this with by running ``sudo apt-get install cmake gcc g++`` in your terminal. + * **Linux:** On Linux, essential build tools and CMake might be preinstalled. Otherwise, on Debian/Ubuntu, you could execute the installation by running ``sudo apt-get install cmake gcc g++`` in your terminal. Step 1: Download the MEmilio Source Code ***************************************** -Once the required tools are installed, open a terminaland download the MEmilio code with this command: +Once the required tools are installed, open a terminal and download the MEmilio code with this command: .. code-block:: console @@ -92,7 +97,7 @@ Now, navigate into that folder: From here, choose one of the following options. -Option A: Installing the Python Packages (Recommended) +Option A: Installing the Python Packages (Recommended for nonexperienced users or for data download and visualizations) **************************************************** If you want to run simulations, download data, or create plots using Python, you only need to install our Python packages. diff --git a/docs/source/references.rst b/docs/source/references.rst index aeef0f97c7..4511b59fc1 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -1,15 +1,15 @@ References =========== +The following gives an overview on (peer-reviewed) publications extending or using MEmilio. If you would like to add a publication, `contact us `. + Recently Submitted Publications -------------------------------------- - Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. arXiv. `arXiv:2502.14428 `_ - Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024). *Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ - Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `arXiv:2408.12228 `_ -- Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, et al. (2024). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. arXiv. `arXiv:2410.08050 `_ - Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ -- Schmid N, Bicker J, Hofmann AF, Wallrafen-Sam K, Kerkmann D, et al. (2024). *Integrative Modeling of the Spread of Serious Infectious Diseases and Corresponding Wastewater Dynamics*. medRxiv. `DOI:10.1101/2024.11.10.24317057 `_ Peer-Reviewed Publications @@ -17,6 +17,12 @@ Peer-Reviewed Publications **2025** +- Schmid N, Bicker J , Hofmann AF, Wallrafen-Sam K, Kerkmann D, Wieser A, Kühn MJ, Hasenauer J (2025). *Integrative Modeling of the Spread of Serious Infectious Diseases and Corresponding Wastewater Dynamics*. *Epidemics* 51:100836. `DOI:10.1016/j.epidem.2025.100836 `_ + +- Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, Gerstein C, Göbbert JH, Basermann A, Kühn MJ, Meyer-Hermann M (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193:110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ + +- Diallo D, Schoenfeld J, Schmieding R, Korf S, Kühn MJ, Hecking T (2025). *Integrating Human Mobility Models with Epidemic Modeling: A Framework for Generating Synthetic Temporal Contact Networks*. *Entropy* 27(5), 507. `DOI:10.3390/e27050507 `_ + - Bicker J, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `DOI:10.1016/j.idm.2024.12.015 `_ **2024** From d56db6073138f706225b3478717cbb4df08d6b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Tue, 1 Jul 2025 17:13:11 +0200 Subject: [PATCH 003/147] getting started --- docs/source/getting_started.rst | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index eafde3d996..60191cd741 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -100,7 +100,7 @@ From here, choose one of the following options. Option A: Installing the Python Packages (Recommended for nonexperienced users or for data download and visualizations) **************************************************** -If you want to run simulations, download data, or create plots using Python, you only need to install our Python packages. +You can run simulations, download data, or create plots, by only installing our Python packages. 1. Navigate to the directory containing our Python code: @@ -108,10 +108,18 @@ If you want to run simulations, download data, or create plots using Python, you cd pycode -2. From here, you can install the packages you need. For example, to install the ``memilio-epidata`` package for data downloading and handling, run: +2. To install the simulation package ``memilio-simulation``, from here you can do: .. code-block:: console + cd memilio-simulation + pip install -e . + +3. For afterwards installing the ``memilio-epidata`` package for data downloading and handling, run: + + .. code-block:: console + + cd .. # Go back to the pycode directory cd memilio-epidata pip install -e . @@ -127,21 +135,12 @@ If you want to run simulations, download data, or create plots using Python, you For regular use, the simple ``pip install -e .`` is sufficient. - -3. To install other packages, like ``memilio-simulation``, repeat the process: - - .. code-block:: console - - cd .. # Go back to the pycode directory - cd memilio-simulation - pip install -e . - - +To install other packages, see the items below *Python Interface* in the menu on the left hand side. Option B: Building the C++ Core (Advanced) **************************************** -If you are a developer and want to build the C++ executables yourself, follow these instructions. +For experienced developers and C++ programmers, we offer the C++ backend to fully benefit from all functionality and parallel performance. 1. Navigate to the C++ source code directory: @@ -172,7 +171,7 @@ For more detailed instructions, help with errors, and a list of compile options, Running simulations ~~~~~~~~~~~~~~~~~~~~~ You can run simulations either via the C++ interface where they are originally implemented or via the python bindings. -For the C++ Interface you can find explanations of the models as well as guides on their usage in the :doc:`C++ model usage ` section. +For the C++ Interface, you can find explanations of the models as well as guides on their usage in the :doc:`C++ model usage ` section. In short, the executables for different model instatiations are build as described above and can be run via .. code-block:: console @@ -202,10 +201,10 @@ the :doc:`C++ model creation ` section of this documentation Visualizations ~~~~~~~~~~~~~~~~~~~~~ -For visualizations we first of all recommend our :doc:`python package `. Apart from that we have +For visualizations, we provide our :doc:`python package MEmilio-plot `. Apart from that, we have collected some scripts that we used for visualizations in the `tools folder in our github repository `_. -For the latter we don't take any responsibilities! +For the latter, no regular testing is conducted. If you encounter errors, please `contact us `. Further questions ~~~~~~~~~~~~~~~~~~~~~ -If you have any further questions, please take a look at our :doc:`faq` and feel free to contact us via `github `_. +If you have any further questions, please take a look at our :doc:`faq` and feel free to contact us via `e-mail ` or open an issue or discusion on `github ` From 76f2cc058b4c449e90b562f38d3bb8e28eb16efd Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 15 Jul 2025 10:15:03 +0200 Subject: [PATCH 004/147] review glct and getting_started --- docs/source/cpp/glct.rst | 25 +++++++------------------ docs/source/getting_started.rst | 4 ++-- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/docs/source/cpp/glct.rst b/docs/source/cpp/glct.rst index e0d1639e19..7735c457b7 100644 --- a/docs/source/cpp/glct.rst +++ b/docs/source/cpp/glct.rst @@ -1,10 +1,10 @@ Generalized Linear Chain Trick model ==================================== -MEmilio implements a SECIR-type model utilizing the Generalized Linear Chain Trick (GLCT). This is a generalization of -the LCT model and allows for phase-type distributed stay times in the compartments. Phase-type distributions are dense +MEmilio implements a SECIR-type model utilizing the Generalized Linear Chain Trick (GLCT). This is a generalization of +the LCT model. In contrast to simpler ODE models that assume (possibly unrealistic) exponentially distributed stay times, the GLCT allows for more realistic, phase-type distributed stay times in the compartments through the use of subcompartments. Phase-type distributions are dense in the field of all positive-valued distributions. Therefore, for any positive-valued distribution, a phase-type -distribution of arbitrary precision can be identified. Note that the resulting system can still be described by ODEs. +distribution of arbitrary precision can be identified. Note that the resulting system can still be described by an ordinary differential equation system. In the following, we present the general structure of the GLCT model. The particular model documentation with examples is linked at the bottom of this page. @@ -12,7 +12,7 @@ is linked at the bottom of this page. Infection states ---------------- -The model contains a list of **InfectionState**\s that define particular features of the subpopulations in the particular state. +The model contains a list of **InfectionState**s that define particular features of the subpopulations in the particular state. .. code-block:: RST @@ -43,10 +43,10 @@ a set of contact matrices of arbitrary length and which can represent the differ schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced -to one value if no stratifcation is used. The values can be adjusted during the simulation, e.g., through implementing +to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. -Parameters can get accessed via ``model.parameters.get>()`` and set via either -``model.parameters.get>() = value`` or ``model.parameters.set>(value)``. +Parameters can be accessed via ``model.parameters.get>()`` and set via either +``model.parameters.set>(value)`` or ``model.parameters.get>() = value``. Initial conditions @@ -114,17 +114,6 @@ and its documentation. In the following, we give detailed explanations of the GLCT-SECIR model. -Introduction -------------- - -This model is based on the Generalized Linear Chain Trick (GLCT). - -The GLCT provides the option to use phase-type distributed stay times in the compartments through the use of subcompartments. The Generalized Linear Chain Trick is an extension of the Linear Chain Trick (as the name already suggests). Phase-type distributions are dense in the field of all positive-valued distributions. Therefore, for any positive-valued distribution, a phase-type distribution of arbitrary precision can be identified. -The normal ODE models have (possibly unrealistic) exponentially distributed stay times. -The GLCT model can still be described by an ordinary differential equation system. - - - List of models ----------------------------- diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index 60191cd741..cce7cf2fa5 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -12,7 +12,7 @@ including :doc:`equation-based`, :doc:`agent-based `. It furthermore provides ready-to-use tools for data integration and visualizations. Among the equation-based models, we provide models based on :doc:`ordinary differential equations `, :doc:`the linear chain trick (LCT), ` and a recent :doc:`generalized LCT `, :doc:`integro-differential equations ` -and :doc:`stochastic differential equations `. With simple definitions, models can be spatially or demograpically resolved. +and :doc:`stochastic differential equations `. With simple definitions, models can be spatially or demographically resolved. The MEmilio framework is written in two languages: C++ and Python. @@ -172,7 +172,7 @@ Running simulations ~~~~~~~~~~~~~~~~~~~~~ You can run simulations either via the C++ interface where they are originally implemented or via the python bindings. For the C++ Interface, you can find explanations of the models as well as guides on their usage in the :doc:`C++ model usage ` section. -In short, the executables for different model instatiations are build as described above and can be run via +In short, the executables for different model instantiations are build as described above and can be run via .. code-block:: console From 556266c6f73dc522beacde237e69fb56a302af11 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:29:35 +0200 Subject: [PATCH 005/147] diffusive abm, lct, model creation, epidata review --- docs/source/cpp/diffusive_abm.rst | 2 +- docs/source/cpp/lct.rst | 6 ++-- docs/source/cpp/lct_creation.rst | 47 +++++++++++++------------- docs/source/cpp/model_creation.rst | 2 +- docs/source/cpp/ode_creation.rst | 4 +-- docs/source/python/memilio_epidata.rst | 19 ++++++----- 6 files changed, 41 insertions(+), 39 deletions(-) diff --git a/docs/source/cpp/diffusive_abm.rst b/docs/source/cpp/diffusive_abm.rst index dd2413ddcd..6d7c9c285d 100644 --- a/docs/source/cpp/diffusive_abm.rst +++ b/docs/source/cpp/diffusive_abm.rst @@ -122,7 +122,7 @@ There are no non-pharmaceutical interventions (NPIs) explicitly implemented in t .. code-block:: cpp //Reduce the transmission risk by 10% - model.get_adoption_rates().at({mio::mpm::Region(0), Status::S, Status::E}).factor *= 0.9; + model.get_adoption_rates().at({mio::regions::Region(0), Status::S, Status::E}).factor *= 0.9; Simulation ----------- diff --git a/docs/source/cpp/lct.rst b/docs/source/cpp/lct.rst index c4b27dfd47..58741d5173 100644 --- a/docs/source/cpp/lct.rst +++ b/docs/source/cpp/lct.rst @@ -12,7 +12,7 @@ is linked at the bottom of this page. Infection states ---------------- -The model contains a list of **InfectionState**\s that define particular features of the subpopulations in the particular state. +The model contains a list of **InfectionState**s that define particular features of the subpopulations in the particular state. .. code-block:: RST @@ -51,9 +51,9 @@ a set of contact matrices of arbitrary length and which can represent the differ schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced -to one value if no stratifcation is used. The values can be adjusted during the simulation, e.g., through implementing +to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. -Parameters can get accessed via ``model.parameters.get>()`` and set via either +Parameters can be accessed via ``model.parameters.get>()`` and set via either ``model.parameters.get>() = value`` or ``model.parameters.set>(value)``. diff --git a/docs/source/cpp/lct_creation.rst b/docs/source/cpp/lct_creation.rst index c487cc3849..5cb5a9560f 100644 --- a/docs/source/cpp/lct_creation.rst +++ b/docs/source/cpp/lct_creation.rst @@ -4,16 +4,16 @@ LCT model creation The mathematical model ---------------------- -Before implementing a model in MEmilio, we need to do a some math, in particular, define an initial value problem +Before implementing a model in MEmilio, we need to do some math, in particular, define an initial value problem given by a system of ordinary differential equations. In the here considered example, we consider a SIRD model where we -divide the Infectious compartment into :math`n` subcompartments. The model is defined by +divide the Infectious compartment into :math:`n` subcompartments. The model is defined by .. math:: \begin{aligned} - S'(t) & = -\rho\phi\ \frac{S(t)*I(t)}{N_{\perp D}} \\ - I_1'(t) & = \rho\phi\ \frac{S(t)*I(t)}{N_{\perp D}} - \frac{n}{T_I}I_1(t) \\ - I_j'(t) & = \frac{n}{T_I}I_{j-1}(t) - \frac{n}{T_I}I_j(t) \quad \text{for } j\in\{2,\dots,n}\\ + S'(t) & = -\rho\phi\ \frac{S(t)I(t)}{N_{\perp D}} \\ + I_1'(t) & = \rho\phi\ \frac{S(t)I(t)}{N_{\perp D}} - \frac{n}{T_I}I_1(t) \\ + I_j'(t) & = \frac{n}{T_I}I_{j-1}(t) - \frac{n}{T_I}I_j(t) \quad \text{for } j\in\{2,\dots,n\}\\ R'(t) & = \frac{\mu_R}{T_I}I(t) \\ D'(t) & = \frac{\mu_D}{T_I}I(t) \\ \end{aligned} @@ -21,7 +21,7 @@ divide the Infectious compartment into :math`n` subcompartments. The model is de with :math:`I(t) = \sum_{j=1}^n I_j(t)` and some initial values for :math:`t=0`. Here :math:`N_{\perp D} := S(t) + I(t) + R(t)`. This type of model belongs to the class of compartmental models because the model population is represented by discrete infection -states **S** usceptible, **I** nfectious, **R** ecovered, **D** eceased, also called compartments. +states **S**usceptible, **I**nfectious, **R**ecovered, **D**eceased, also called compartments. Infection states ~~~~~~~~~~~~~~~~ @@ -46,7 +46,7 @@ Parameters Next, we define the parameters in "parameters.h", which consist of a struct for each constant used in the mathematical model. This struct must define the data type, name and default value of the constant. For example, for the time a -person stays infectious :math:`T_I` we define a struct +person stays infectious :math:`T_I` we define a struct: .. code-block:: cpp @@ -67,23 +67,23 @@ person stays infectious :math:`T_I` we define a struct and for the contact rate :math:`\phi` a struct -We also define a parameter ``ContactPatterns`` determining the contacts of the different groups by +We also define a parameter ``ContactPatterns`` determining the contacts of the different groups by: .. code-block:: cpp struct ContactPatterns { - using Type = UncertainContactMatrix; + using Type = UncertainContactMatrix; - static Type get_default() - { - mio::ContactMatrixGroup contact_matrix(1, 1); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10.)); - return Type(contact_matrix); - } - static std::string name() - { - return "ContactPatterns"; - } + static Type get_default() + { + mio::ContactMatrixGroup contact_matrix(1, 1); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10.)); + return Type(contact_matrix); + } + static std::string name() + { + return "ContactPatterns"; + } }; Avoid using the mathematical symbols of the constant as names for the struct. Their connection can be noted in the @@ -144,12 +144,13 @@ Now we can define the model as a **CompartmentalModel** in the file model.h: const auto N = y[InfectionState::Susceptible] + y[InfectionState::Infectious] + y[InfectionState::Recovered]; - dydt[InfectionState::Susceptible] = params.template get>() * - params.template get>() * - y[InfectionState::Susceptible] * y[InfectionState::Infectious] / N; + dydt[InfectionState::Susceptible] = -params.template get>() * + params.template get>() * + y[InfectionState::Susceptible] * y[InfectionState::Infectious] / N; . . . } + }; Note that this class has a template parameter **LctStates** that defines the number of subcompartments per infection state. For LCT models, the class **CompartmentalModel** requires the following template arguments: @@ -165,4 +166,4 @@ It is also useful to implement the following methods within the model: - A function ``calculate_compartments()`` that accumulates the TimeSeries containing simulation results that are divided into subcompartments to a TimeSeries that conatins the simulation results according to the infection states without subcompartments. -- A function ``check_constraints()`` that checks that the model satisfies sensible constraints regarding parameters and initial conditions. \ No newline at end of file +- A function ``check_constraints()`` that checks that the model satisfies sensible constraints regarding parameters and initial conditions. diff --git a/docs/source/cpp/model_creation.rst b/docs/source/cpp/model_creation.rst index f6cec88541..5d4f6e1242 100644 --- a/docs/source/cpp/model_creation.rst +++ b/docs/source/cpp/model_creation.rst @@ -10,5 +10,5 @@ Here we collect information on how to create models. Ordinary differential equations Linear Chain Trick Stochastic-differential equations - Integro-differenatial equations + Integro-differential equations \ No newline at end of file diff --git a/docs/source/cpp/ode_creation.rst b/docs/source/cpp/ode_creation.rst index eacbb564b3..93ff9c12ae 100644 --- a/docs/source/cpp/ode_creation.rst +++ b/docs/source/cpp/ode_creation.rst @@ -146,7 +146,7 @@ Now we can define the model: const auto N = y[InfectionState::Susceptible] + y[InfectionState::Infectious] + y[InfectionState::Recovered]; - dydt[InfectionState::Susceptible] = params.template get>() * + dydt[InfectionState::Susceptible] = -params.template get>() * params.template get>() * y[InfectionState::Susceptible] * y[InfectionState::Infectious] / N; @@ -221,4 +221,4 @@ With the flows and classes also used by the CompartmentalModel, we can define a . . . } - }; \ No newline at end of file + }; diff --git a/docs/source/python/memilio_epidata.rst b/docs/source/python/memilio_epidata.rst index fe9d484f77..ca8874f22d 100644 --- a/docs/source/python/memilio_epidata.rst +++ b/docs/source/python/memilio_epidata.rst @@ -46,15 +46,16 @@ Usage ----- After installation the following functions are available: - * getcasedata: Downloads SARS-CoV-2 case data from Robert Koch-Institut (RKI-C). - * getpopuldata: Downloads population data for German federal states and counties from various public sources (P). - * getjhdata: Downloads COVID-19 case data from John Hopkins University (JH). - * getdividata: Downloads ICU data from German DIVI Intensivregister (DIVI). - * getsimdata: Downloads all data required for a simulation with the graph-metapopulation model which are SARS-CoV-2 case data(RKI-C), population data (P), ICU data (DIVI) and COVID-19 vaccination data from Robert Koch-Institut (RKI-V). - * getcommutermobility: Downloads data about commuter mobility from German Federal Employment Agency (BAA). - * gettestingdata: Downloads data about SARS-CoV-2 PCR tests from Robert Koch-Institut (RKI-T). - * gethospitalizationdata: Downloads data about COVID-19 hospitalizations data from Robert Koch-Institut (RKI-H) - * cleandata: Deletes all data files generated by the MEmilio Epidata package. + +* ``getcasedata``: Downloads SARS-CoV-2 case data from Robert Koch-Institut (RKI-C). +* ``getpopuldata``: Downloads population data for German federal states and counties from various public sources (P). +* ``getjhdata``: Downloads COVID-19 case data from John Hopkins University (JH). +* ``getdividata``: Downloads ICU data from German DIVI Intensivregister (DIVI). +* ``getsimdata``: Downloads all data required for a simulation with the graph-metapopulation model which are SARS-CoV-2 case data(RKI-C), population data (P), ICU data (DIVI) and COVID-19 vaccination data from Robert Koch-Institut (RKI-V). +* ``getcommutermobility``: Downloads data about commuter mobility from German Federal Employment Agency (BAA). +* ``gettestingdata``: Downloads data about SARS-CoV-2 PCR tests from Robert Koch-Institut (RKI-T). +* ``gethospitalizationdata``: Downloads data about COVID-19 hospitalizations data from Robert Koch-Institut (RKI-H) +* ``cleandata``: Deletes all data files generated by the MEmilio Epidata package. For a detailed description of the run options and the resulting data files written see the `epidata subfolder `_. From e15dda1e6065039daee17de8ba7d54357c1b5da8 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:34:35 +0200 Subject: [PATCH 006/147] [ci skip] Stochastic metapop review --- docs/source/cpp/smm.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/cpp/smm.rst b/docs/source/cpp/smm.rst index 0fdb670adf..f1892f530f 100644 --- a/docs/source/cpp/smm.rst +++ b/docs/source/cpp/smm.rst @@ -53,9 +53,9 @@ Using the infection states from above and two regions, there are five first-orde adoption_rates.push_back({InfectionState::I, InfectionState::D, mio::regions::Region(r), 0.01 / 5., {}}); } - //Set second-order adoption rate different for the two regions + //Set second-order adoption rate different for the two regions adoption_rates.push_back({InfectionState::S, InfectionState::E, mio::regions::Region(0), 0.1, {{InfectionState::C, 1}, {InfectionState::I, 0.5}}}); - adoption_rates.push_back({InfectionState::S, InfectionState::E, mio::regions::Region(0), 0.2, {{InfectionState::C, 1}, {InfectionState::I, 0.5}}}); + adoption_rates.push_back({InfectionState::S, InfectionState::E, mio::regions::Region(1), 0.2, {{InfectionState::C, 1}, {InfectionState::I, 0.5}}}); //Initialize model parameter model.parameters.get>() = adoption_rates; @@ -95,7 +95,7 @@ These populations have the class type **Populations** and can be set via: .. code-block:: cpp - double pop = 1000, numE = 0.001 * pop, numC = 0.0001 * pop, numI = 0.0001 * pop; + double pop = 1000, numE = 0.001 * pop, numC = 0.0001 * pop, numI = 0.0001 * pop, numR = 0, numD = 0; //Population is distributed equally to the regions for (size_t r = 0; r < num_regions; ++r) { @@ -118,11 +118,11 @@ As the spatial transition rates are dependent on infection state, region changes for (size_t i = 0; i < num_regions; ++i) { for (size_t j = 0; j < num_regions; ++j) if (i != j) { - transition_rates.push_back( - {InfectionState(s), mio::regions::Region(i), mio::regions::Region(j), 0.01}); - transition_rates.push_back( - {InfectionState(s), mio::regions::Region(j), mio::regions::Region(i), 0.01}); - } + transition_rates.push_back( + {InfectionState(s), mio::regions::Region(i), mio::regions::Region(j), 0.01}); + transition_rates.push_back( + {InfectionState(s), mio::regions::Region(j), mio::regions::Region(i), 0.01}); + } } } From 3d7238d441b582f945feb898afae33cef3b026b6 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 15 Jul 2025 11:38:47 +0200 Subject: [PATCH 007/147] [ci skip] update citation --- README.md | 2 +- docs/source/citation.rst | 3 ++- docs/source/references.rst | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 722e8423cf..ad4d164be7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and, in particular, for - Hybrid agent-metapopulation-based models: Bicker J, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `https://doi.org/10.1016/j.idm.2024.12.015` - Graph Neural Networks: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024). *Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `https://arxiv.org/abs/2411.06500` - ODE-based models with Linear Chain Trick: Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `https://doi.org/10.48550/arXiv.2412.09140` -- Behavior-based ODE models: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. arXiv. `https://arxiv.org/abs/2502.14428` +- Behavior-based ODE models: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. *Chaos, Solitons & Fractals* 199:116782. `https://doi.org/10.1016/j.chaos.2025.116782` **Getting started** diff --git a/docs/source/citation.rst b/docs/source/citation.rst index 3036d0e41b..bacc2c2a5d 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -13,4 +13,5 @@ and, in particular, for - **Hybrid agent-metapopulation-based models**: Bicker J, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `DOI:10.1016/j.idm.2024.12.015 `_ - **Graph Neural Networks**: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024).*Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ - **ODE-based models with Linear Chain Trick**: Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ -- **Behavior-based ODE models**: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. arXiv. `arXiv:2502.14428 `_ +- **Behavior-based ODE models**: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. *Chaos, Solitons & Fractals* 199:116782. `DOI:10.1016/j.chaos.2025.116782 `_ + diff --git a/docs/source/references.rst b/docs/source/references.rst index 4511b59fc1..eb19c550a0 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -6,7 +6,6 @@ The following gives an overview on (peer-reviewed) publications extending or usi Recently Submitted Publications -------------------------------------- -- Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. arXiv. `arXiv:2502.14428 `_ - Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024). *Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ - Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `arXiv:2408.12228 `_ - Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ @@ -17,6 +16,8 @@ Peer-Reviewed Publications **2025** +- Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. *Chaos, Solitons & Fractals* 199:116782. `DOI:10.1016/j.chaos.2025.116782 `_ + - Schmid N, Bicker J , Hofmann AF, Wallrafen-Sam K, Kerkmann D, Wieser A, Kühn MJ, Hasenauer J (2025). *Integrative Modeling of the Spread of Serious Infectious Diseases and Corresponding Wastewater Dynamics*. *Epidemics* 51:100836. `DOI:10.1016/j.epidem.2025.100836 `_ - Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, Gerstein C, Göbbert JH, Basermann A, Kühn MJ, Meyer-Hermann M (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193:110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ From 8e22e55719f3da9ae69c810049ad6b04e6f0940f Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:59:58 +0200 Subject: [PATCH 008/147] first draft --- docs/source/_static/custom.css | 47 +++++++++++++++ docs/source/_static/team/max_mustermann.jpg | Bin 0 -> 4566 bytes docs/source/index.rst | 1 + docs/source/team.rst | 62 ++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 docs/source/_static/team/max_mustermann.jpg create mode 100644 docs/source/team.rst diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css index fc86d8fd05..ca1d631a6a 100644 --- a/docs/source/_static/custom.css +++ b/docs/source/_static/custom.css @@ -25,11 +25,13 @@ dl.class > dt:first-of-type { .rst-content dl .sig { width: -webkit-fill-available; } + .rst-content .viewcode-link { display: inline-flex; float: inline-end; margin-right: 1.5em; } + .rst-content .headerlink { position: absolute; right: 0.5em; @@ -39,3 +41,48 @@ dl.class > dt:first-of-type { .wy-table-responsive table th { white-space: normal; } + +/* Team page styles */ +.developer-photo { + border-radius: 8px; + margin-right: 20px; + margin-bottom: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Clear floats after each developer section */ +.section h3 { + clear: both; +} + +/* Add some spacing between developer sections */ +.section>.section { + margin-bottom: 40px; + padding-bottom: 20px; + border-bottom: 1px solid #e1e4e5; +} + +/* Style the links section */ +.section ul { + list-style-type: none; + padding-left: 0; +} + +.section ul li { + margin-bottom: 5px; +} + +.section ul li a { + color: #2980b9; + text-decoration: none; + font-weight: 500; +} + +.section ul li a:hover { + text-decoration: underline; +} + +.section p { + text-align: justify; + line-height: 1.6; +} diff --git a/docs/source/_static/team/max_mustermann.jpg b/docs/source/_static/team/max_mustermann.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c56a981959b62181335e287df2c0078fbaecb22 GIT binary patch literal 4566 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 +* `ORCID: 0000-0000-0000-0000 `_ + +---- + +.. _contributors: + +Contributors +------------ + +We thank all contributors, who have contributed to MEmilio. TBD + +For a complete list of contributors, please see our `GitHub Contributors page `_. + +.. _collaboration: + +Participating research groups +------------- + +MEmilio is developed in collaboration with various research institutions and partners: + +* **123** - Institue 1 + + +.. _acknowledgments: + +Acknowledgments +--------------- + +TBD From 887614aae18fedbd28ce58095547bc83de8d75f2 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:59:15 +0200 Subject: [PATCH 009/147] CHG: Rename Getting started to About --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 9e7c0f0dd9..c51b94620b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -34,7 +34,7 @@ Contents .. toctree:: :maxdepth: 1 - :caption: Getting Started + :caption: About getting_started citation From 0064b6c098ee32d9eb29776c77c039723b46c616 Mon Sep 17 00:00:00 2001 From: Sascha <51127093+xsaschako@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:06:03 +0200 Subject: [PATCH 010/147] add figure description --- docs/source/development.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/source/development.rst b/docs/source/development.rst index 53a96e105e..0fb2dd7e2d 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -237,6 +237,32 @@ to your corresponding ``settings.json``. Docstrings in Python should be added for every function, as detailed in the C++ coding guidelines. However, the syntax is slightly different than for C++ code. An overview and examples can be found at https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html . +Figure colors and settings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to ensure that figures in the documentation and in the code have a consistent look, we use the following settings: + +**Default color scheme** + +- For figures in the documentation, we usually use the `matplotlib `_ library. +- The default color cycle is set to the `Set1 `_ colormap. + +**Colorblind-friendly alternatives** + +For better accessibility and when creating figures with many categories, consider using colorblind-friendly alternatives: + +- Use the `tab10 `_ colormap for up to 10 distinct categories +- For sequential data, prefer `viridis `_, `plasma`, or `cividis` colormaps +- For diverging data, use `RdBu `_ or `RdYlBu` colormaps +- Avoid using red-green color combinations without additional visual cues (patterns, shapes, etc.) + +**General figure guidelines** + +- Use consistent font sizes across all figures (typically 10-12pt for labels, 8-10pt for tick labels) +- Ensure sufficient contrast between colors and background +- Add appropriate legends and axis labels with units +- For line plots with multiple series, vary both color and line style (solid, dashed, dotted) for better distinction +- When possible, test figures with a colorblind simulator to ensure accessibility Git workflow ---------------------- From 9c410fa2302f8196bec2f45af8d45087c5b0b404 Mon Sep 17 00:00:00 2001 From: Sascha <51127093+xsaschako@users.noreply.github.com> Date: Fri, 18 Jul 2025 00:21:23 +0200 Subject: [PATCH 011/147] add review changes --- docs/source/cpp/io.rst | 2 +- docs/source/python/memilio_simulation.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/cpp/io.rst b/docs/source/cpp/io.rst index 325cac418a..a0a51a0342 100644 --- a/docs/source/cpp/io.rst +++ b/docs/source/cpp/io.rst @@ -101,7 +101,7 @@ The default serialization is intentionally less flexible than the serialize and - Every class member itself must be serializable, deserializable and assignable. -This feature is primarily meant to make data classes easy to (de)serialize, avoiding some repitition that is necessary +This feature is primarily meant to make data classes easy to (de)serialize, avoiding some repetition that is necessary when writing both a serialize and deserialize function. It can, however, be used for any class that should be serialized in its entirety, and that does not need to make any decisions or computations while doing so. For example, default serialization cannot be used if your class has optional members or values, or if one of its members is stored diff --git a/docs/source/python/memilio_simulation.rst b/docs/source/python/memilio_simulation.rst index ff37112e6b..f917330d03 100644 --- a/docs/source/python/memilio_simulation.rst +++ b/docs/source/python/memilio_simulation.rst @@ -13,8 +13,8 @@ Required Python packages: * scikit-build -For a succesful build, the development libraries for Python need to be installed, i.e. python3.x-dev. -Additionally, as this packages builds upon the MEmilio C++ library, +For a successful build, the development libraries for Python need to be installed, i.e. python3.x-dev. +Additionally, as this package builds upon the MEmilio C++ library, all dependencies of the main library need to be met. Read more about the C++ dependencies at :doc:`cpp <../getting_started>`. From 32febe9cfab3135adcbee07fc1682f87f867dc13 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 09:57:49 +0200 Subject: [PATCH 012/147] FIX: broken link in memilio-generation docs --- docs/source/python/memilio_generation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/python/memilio_generation.rst b/docs/source/python/memilio_generation.rst index 83fae181c7..ed7dbbf1a0 100644 --- a/docs/source/python/memilio_generation.rst +++ b/docs/source/python/memilio_generation.rst @@ -8,7 +8,7 @@ For a particular example, see the SEIR model with its files `oseir.cpp` and `ose This generating software was developed as a part of the Bachelor thesis `Automatische Codegenerierung für nutzerfreundliche mathematisch-epidemiologische Modelle `_. The following figure from Chapter 5 outlines the workflow of the generator. Blue boxes represent parts of the code generator and orange ones the input and output. Rectangular boxes contain classes with logic, the rest represent data. -.. image:: https://github.com/SciCompMod/memilio/main/blob/pycode/memilio-generation/generator_workflow.png +.. image:: ../../../pycode/memilio-generation/generator_workflow.png :alt: tikzGeneratorWorkflow Dependencies From ddd5f62bf9e6c37e16cef03215b7f9d37d27e2e7 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:56:56 +0200 Subject: [PATCH 013/147] FIX: broken enumerations in memilio-generation docs --- docs/source/python/memilio_generation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/python/memilio_generation.rst b/docs/source/python/memilio_generation.rst index ed7dbbf1a0..af2946f600 100644 --- a/docs/source/python/memilio_generation.rst +++ b/docs/source/python/memilio_generation.rst @@ -32,6 +32,7 @@ During the installation the package creates a compilation database (compile_comm The package provides an example script on how to use it in `memilio/tools`. The example uses the ode_seir model. Before running the example you have to do these steps of setup: + * Change `config.json.txt `_. * Check if the parameters set in __post_init__() of the [ScannerConfig class](./memilio/generation/scanner_config.py) match with the cpp-class names. @@ -60,6 +61,7 @@ Development ----------- When implementing new model features you can follow these steps: + * Add necessary configurations to `config.json.txt `_ and add corresponding attributes to the ``ScannerConfig``. * For the features you want to implement, find the nodes in the abstract syntax tree (AST) (use method Scanner.output_ast_file(); see the example in tools/). * Add the extraction of those features. Therefore you need to change the "check_..."-methods corresponding to the ``CursorKind`` of your nodes in the ``Scanner``. If there is no corresponding "check_..."-method you need to write a new one and add it to the switch-method (scanner.switch_node_kind()). From e963ab07244ed27afbaf1920e83bce111d33ca7c Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 11:15:20 +0200 Subject: [PATCH 014/147] FIX: links and grammar in memeilio-generation docs --- docs/source/python/memilio_generation.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/python/memilio_generation.rst b/docs/source/python/memilio_generation.rst index af2946f600..09b0177389 100644 --- a/docs/source/python/memilio_generation.rst +++ b/docs/source/python/memilio_generation.rst @@ -34,23 +34,23 @@ The package provides an example script on how to use it in `memilio/tools`. The Before running the example you have to do these steps of setup: * Change `config.json.txt `_. -* Check if the parameters set in __post_init__() of the [ScannerConfig class](./memilio/generation/scanner_config.py) match with the cpp-class names. +* Check if the parameters set in ``__post_init__()`` of the `ScannerConfig class `_ match with the cpp-class names. Example: -After processing as described in the previous paragraph, run the example with the command (path according to the current folder): +After processing as described in the previous paragraph, run the example with the command (adjust the path according to your current folder): .. code-block:: console python memilio/tools/example_oseir.py -When working on a new model you can copy the example script and add an additional segment to the config.json.txt. The setup works similar to the example. Additionaly you can print the AST of your model into a file (Usefull for development/debugging). +When working on a new model, you can copy the example script and add an additional segment to the config.json.txt. The setup works similar to the example. Additionaly, you can print the AST of your model into a file for development or debugging. Testing ------- -The package provides a test suite in `memilio/generation_test `_. -To run the tests, simply run the following command: +The package provides a test suite in `memilio/generation_test `_. +To run the tests, simply use the following command: .. code-block:: console @@ -64,9 +64,9 @@ When implementing new model features you can follow these steps: * Add necessary configurations to `config.json.txt `_ and add corresponding attributes to the ``ScannerConfig``. * For the features you want to implement, find the nodes in the abstract syntax tree (AST) (use method Scanner.output_ast_file(); see the example in tools/). -* Add the extraction of those features. Therefore you need to change the "check_..."-methods corresponding to the ``CursorKind`` of your nodes in the ``Scanner``. If there is no corresponding "check_..."-method you need to write a new one and add it to the switch-method (scanner.switch_node_kind()). +* Add the extraction of those features. Therefore you need to change the "check_..."-methods corresponding to the ``CursorKind`` of your nodes in the ``Scanner``. If there is no corresponding "check\_..."-method you need to write a new one and add it to the switch-method (``scanner.switch_node_kind()``). * Extend the ``IntermediateRepresentation`` for the new model features. -* Adjust the `cpp-template `_ and the `string-template-methods `_. If needed, use new identifiers and write new string-template-methods for them. +* Adjust the `cpp-template `_ and the `string-template-methods `_. If needed, use new identifiers and write new string-template-methods for them. * Adjust the substitution dictionaries in the ``Generator``. -* Write new/Adjust script in the `tool folder `_ for the model and try to run. +* Write new/Adjust scripts in the `tool folder `_ for the model and try to run. * Update tests. \ No newline at end of file From 82756b726e17526c2b3079036c4d1ecdb01a4adc Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:42:43 +0200 Subject: [PATCH 015/147] CHG: Add CI benchmarking issue to Development --- docs/source/cpp/development.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/development.rst b/docs/source/cpp/development.rst index ff19249dae..31acfe0003 100644 --- a/docs/source/cpp/development.rst +++ b/docs/source/cpp/development.rst @@ -34,7 +34,7 @@ Agent-based model benchmarks There is a suite of benchmarks for the ABM that can be used to check performance. The suite contains setups of different sizes. If you added a new feature (i.e., you didn't just fix a bug in an existing feature), make sure the feature is actually used by the benchmark. Add it to the benchmark if necessary, then run the benchmark to see if the cost for the new feature is acceptable and as expected. -Most new features will add some overhead, but this needs to be limited and in proportion to the added value of the feature so runtime doesn't grow out of control. Optional features that can be disabled should only incur minimal overhead. If you did not add any new feature, just run the benchmark before and after your changes to make sure there are no performance regressions. This process will hopefully be automated soon by running benchmarks in the CI. +Most new features will add some overhead, but this needs to be limited and in proportion to the added value of the feature so runtime doesn't grow out of control. Optional features that can be disabled should only incur minimal overhead. If you did not add any new feature, just run the benchmark before and after your changes to make sure there are no performance regressions. This process will hopefully be automated soon by running benchmarks in the CI (see the `corresponding issue `_ for progress). Build the benchmarks by defining the CMake variable ``MEMILIO_BUILD_BENCHMARKS=ON`` in the build. Make sure to use a **Release** build to test performance. From 296fa609281cf93ad8b6cdb696b9e88110edc0b1 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:17:54 +0200 Subject: [PATCH 016/147] FIX: Typos in ode_creation --- docs/source/cpp/ode_creation.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/cpp/ode_creation.rst b/docs/source/cpp/ode_creation.rst index 93ff9c12ae..d71f86d0f4 100644 --- a/docs/source/cpp/ode_creation.rst +++ b/docs/source/cpp/ode_creation.rst @@ -19,12 +19,12 @@ given by a system of ordinary differential equations. For example we consider a and some initial values for :math:`t=0`. Here :math:`N_{\perp D} := S(t) + I(t) + R(t)`. This type of model is called compartmental model, because the model population is represented by discrete infection -states **S** usceptible, **I** nfectious, **R** ecovered, **D** eceased, also called compartments. +states **S**\usceptible, **I**\nfectious, **R**\ecovered, **D**\eceased, also called compartments. How to define an ODE model -------------------------- -To define an ODE model in MEmilio, there are two options. You can define a CompartmentalModel or FlowModel, which +To define an ODE model in MEmilio, there are two options. You can define a CompartmentalModel or a FlowModel, which use different methods to define the right hand side of the mathematical model above. Both classes need definitions for the infection states, population and parameters it uses. The FlowModel additionally requires a list of flows. @@ -35,9 +35,9 @@ other noteworthy features of the model in shortened form. All files in the follo Infection states ~~~~~~~~~~~~~~~~ -First we define an :code:`enum class` called InfectionState in the file "infection_state.h", which contains an entry +First we define an :code:`enum class` called ``InfectionState`` in the file "infection_state.h", which contains an entry for each infection state of the mathematical model, followed by an entry called :code:`Count`. This enumerates the -compartments starting from 0, with Count being equal to the number of compartments. For example: +compartments starting from 0, with Count being equal to the number of compartments. In our example we have: .. code-block:: cpp @@ -55,7 +55,7 @@ Parameters Next, we define the parameters in "parameters.h", which consist of a struct for each constant used in the mathematical model. This struct must define the data type, name and default value of the constant. For example, for the time a -person stays infectious :math:`T_I` we define a struct +person stays infectious, :math:`T_I`, we define a struct .. code-block:: cpp @@ -113,7 +113,7 @@ Population ~~~~~~~~~~ The population will be stored in a vector, with a component for each infection state. We define it using the class -`mio::Population`. +``mio::Population``. .. code-block:: cpp From e68edd1cf49be8ca0df657537c86387bec371f29 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:58:55 +0200 Subject: [PATCH 017/147] CHG: Add Review changes to LCT creation --- docs/source/cpp/lct_creation.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/source/cpp/lct_creation.rst b/docs/source/cpp/lct_creation.rst index 5cb5a9560f..4013760d0d 100644 --- a/docs/source/cpp/lct_creation.rst +++ b/docs/source/cpp/lct_creation.rst @@ -5,7 +5,7 @@ The mathematical model ---------------------- Before implementing a model in MEmilio, we need to do some math, in particular, define an initial value problem -given by a system of ordinary differential equations. In the here considered example, we consider a SIRD model where we +given by a system of ordinary differential equations. Here, we consider a SIRD model where we divide the Infectious compartment into :math:`n` subcompartments. The model is defined by .. math:: @@ -21,7 +21,7 @@ divide the Infectious compartment into :math:`n` subcompartments. The model is d with :math:`I(t) = \sum_{j=1}^n I_j(t)` and some initial values for :math:`t=0`. Here :math:`N_{\perp D} := S(t) + I(t) + R(t)`. This type of model belongs to the class of compartmental models because the model population is represented by discrete infection -states **S**usceptible, **I**nfectious, **R**ecovered, **D**eceased, also called compartments. +states **S**\usceptible, **I**\nfectious, **R**\ecovered, **D**\eceased, also called compartments. Infection states ~~~~~~~~~~~~~~~~ @@ -46,7 +46,7 @@ Parameters Next, we define the parameters in "parameters.h", which consist of a struct for each constant used in the mathematical model. This struct must define the data type, name and default value of the constant. For example, for the time a -person stays infectious :math:`T_I` we define a struct: +person stays infectious, :math:`T_I`, we define a struct: .. code-block:: cpp @@ -65,7 +65,6 @@ person stays infectious :math:`T_I` we define a struct: } }; -and for the contact rate :math:`\phi` a struct We also define a parameter ``ContactPatterns`` determining the contacts of the different groups by: @@ -86,9 +85,11 @@ We also define a parameter ``ContactPatterns`` determining the contacts of the d } }; -Avoid using the mathematical symbols of the constant as names for the struct. Their connection can be noted in the +Avoid using the mathematical symbol of a constant as name for the struct. Their connection can be noted down in the documentation of these structs. -Additionally, we can determine parameters influencing the infection dynamics. In the case of the LCT-SECIR model we use the parameters ``TransmissionProbabilityOnContact``, ``RelativeTransmissionNoSymptoms``, ``RiskOfInfectionFromSymptomatic``, ``StartDay`` and ``Seasonality``. For each parameter, we need to define a default value and a name as for the above parameters. +Additionally, we can determine parameters influencing the infection dynamics. In the case of the LCT-SECIR model we use +the parameters ``TransmissionProbabilityOnContact``, ``RelativeTransmissionNoSymptoms``, ``RiskOfInfectionFromSymptomatic``, ``StartDay`` and ``Seasonality``. +For each parameter, we need to define a default value and a name as for the above parameters. Finally, define a type :code:`Parameters` by listing all parameter structs as template arguments of a :code:`mio::ParameterSet`: @@ -107,14 +108,14 @@ Population ~~~~~~~~~~ The population will be stored in a vector, with a component for each subcompartment of every infection state. We define -it using the class **LctPopulations**. +it using the class ``LctPopulations``. .. code-block:: cpp template using Population = mio::LctPopulations; -where **LctStates** contains the number of subcompartments per infection state. +where ``LctStates`` contains the number of subcompartments per infection state. Importantly, this class allows further stratifying the population vector, with the most common example being adding age groups. @@ -152,18 +153,17 @@ Now we can define the model as a **CompartmentalModel** in the file model.h: } }; -Note that this class has a template parameter **LctStates** that defines the number of subcompartments per infection state. -For LCT models, the class **CompartmentalModel** requires the following template arguments: +Note that this class has a template parameter ``LctStates`` that defines the number of subcompartments per infection state. +For LCT models, the class ``CompartmentalModel`` requires the following template arguments: -- type of floating point type, here **ScalarType**, -- a class **InfectionState** containing the compartments, see above, -- the class **LctPopulations** which is a class template for compartment populations of LCT models depending on the floating point type and the considered **LctStates**, -- the class **Parameters** containing all required parameters, see above. +- type of floating point type, here ``ScalarType``, +- a class ``InfectionState`` containing the compartments, see above, +- the class ``LctPopulations`` which is a class template for compartment populations of LCT models depending on the floating point type and the considered ``LctStates``, +- the class ``Parameters`` containing all required parameters, see above. The function ``get_derivatives()`` evaluates the right-hand-side of the ODE :math:`dydt = f(y, t)` that we want to solve, see above. It is also useful to implement the following methods within the model: -- A function ``calculate_compartments()`` that accumulates the TimeSeries containing simulation results that are divided -into subcompartments to a TimeSeries that conatins the simulation results according to the infection states without subcompartments. +- A function ``calculate_compartments()`` that accumulates the TimeSeries containing simulation results that are divided into subcompartments to a TimeSeries that conatins the simulation results according to the infection states without subcompartments. - A function ``check_constraints()`` that checks that the model satisfies sensible constraints regarding parameters and initial conditions. From 8ffec42bf8c6fd04e3fe7bfd2c4b2876f81763a5 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:18:11 +0200 Subject: [PATCH 018/147] FIX: All links in Input/Output were broken --- docs/source/cpp/io.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/cpp/io.rst b/docs/source/cpp/io.rst index a0a51a0342..a48b5500fb 100644 --- a/docs/source/cpp/io.rst +++ b/docs/source/cpp/io.rst @@ -6,7 +6,7 @@ This document describes utilities for reading and writing data from and to files - The serialization framework, that can be used to define the structure of data without using a specific file format. There are implementations of the framework for different formats. The framework is described in detail below, also - see the `serialization example <../../examples/serialize.cpp>`__. + see the `serialization example `__. - The command line interface, that can be used to set (and get) values of a ``ParameterSet``. @@ -272,8 +272,8 @@ Working with the History object The History object provides a way to save data throughout the simulation process. It offers an interface where users can define the data to be saved from a given object using Loggers and the method of saving it using Writers. Afterward, the user can access this data from the History object and manipulate it. For a basic Logger use case, refer to -`this example <../../examples/history.cpp>`__. For an example demonstrating using a Logger in the ABM, refer to -`this example <../../examples/abm_history_example.cpp>`__. +`this example `__. For an example demonstrating using a Logger in the ABM, refer to +`this example `__. Loggers ~~~~~~~ @@ -293,7 +293,7 @@ functions is the same as the one given to the ``History`` member-function ``Hist Users can derive their Loggers from ``LogOnce`` or ``LogAlways`` to use a predefined ``should_log`` function. ``LogOnce`` logs only at the first call of ``Logger::log()``, while ``LogAlways`` logs every time ``log`` is called. All implemented Loggers must be default constructible/destructible. For user-defined examples in the ABM, refer to -`this file <../../models/abm/common_abm_loggers.h>`__. +`this file `__. .. code-block:: cpp @@ -324,9 +324,9 @@ user-implemented ``Writer`` must have a ``Data`` Type and implement the - ``add_record``: This manipulates the passed Data member of the ``History`` class to store the value ``t`` returned by the Loggers. It is used whenever ``History::log`` is called and ``Logger::should_log`` is true. -A predefined universal ``Writer`` called ``DataWriterToMemory`` is already implemented in `history.h `__. +A predefined universal ``Writer`` called ``DataWriterToMemory`` is already implemented in `history.h `__. This stores the data from the loggers in a tuple of vectors every time the Logger is called. Another ``Writer`` named -``TimeSeriesWriter`` can be found in `this file <../../models/abm/common_abm_loggers.h>`__, which saves data in a +``TimeSeriesWriter`` can be found in `this file `__, which saves data in a Timeseries. The according Logger has to have a suitable return type. .. code-block:: cpp @@ -354,9 +354,9 @@ For this, the lifetime of the ``History`` has to be as long as one wants to have should not be constructed in the function it is called in when data is needed later. To access data from a specific Logger, one can use ``std::get`` where x is the position of the Logger in the template -argument list of the ``History`` object. Refer to `this example <../../examples/history.cpp>`__ for a simple -implementation of a history object and `this full ABM example <../../simulation/abm.cpp>`__ for a more advanced use case +argument list of the ``History`` object. Refer to `this example `__ for a simple +implementation of a history object and `this full ABM example `__ for a more advanced use case of the History object with several History objects in use. As mentioned, if multiple Writers have to be used simultaneously, a separate History object is needed for each Writer. -For a use case of this, refer to `the ABM Simulation advance function <../../models/abm/simulation.cpp>`__. +For a use case of this, refer to `the ABM Simulation advance function `__. From fc3a1d868d19e9b666a4c4e42d31a38a67603b68 Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:08:45 +0200 Subject: [PATCH 019/147] clarifications on ABM benchmarking --- docs/source/cpp/development.rst | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/source/cpp/development.rst b/docs/source/cpp/development.rst index 31acfe0003..d6c7813d87 100644 --- a/docs/source/cpp/development.rst +++ b/docs/source/cpp/development.rst @@ -1,10 +1,10 @@ Development -================================= +=========== .. _performance-monitoring-cpp: Performance monitoring ---------------------------------- +---------------------- LIKWID ~~~~~~ @@ -30,11 +30,15 @@ Set the CMake variable ``MEMILIO_USE_LIKWID=ON`` to enable LIKWID support and ru For more details see the LIKWID documentation, available `here `_. Agent-based model benchmarks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -There is a suite of benchmarks for the ABM that can be used to check performance. The suite contains setups of different sizes. If you added a new feature (i.e., you didn't just fix a bug in an existing feature), make sure the feature is actually used by the benchmark. Add it to the benchmark if necessary, then run the benchmark to see if the cost for the new feature is acceptable and as expected. +There is a suite of benchmarks for the ABM that are used to check its performance. The suite contains setups of different sizes, to check that the model maintains its linear scaling. -Most new features will add some overhead, but this needs to be limited and in proportion to the added value of the feature so runtime doesn't grow out of control. Optional features that can be disabled should only incur minimal overhead. If you did not add any new feature, just run the benchmark before and after your changes to make sure there are no performance regressions. This process will hopefully be automated soon by running benchmarks in the CI (see the `corresponding issue `_ for progress). +When you make any changes to the ABM or code used by it, run the benchmarks to check that its performance did not degrade. What exactly impacts the ABM's performance can be hard to tell (even parameter values may change its runtime), so it is best to run the bencharks on any change to the main library or the ABM specific code. + +If you added a new feature (i.e., you didn't just fix a bug in an existing feature), make sure the feature is actually used by the benchmark. Add it to the benchmark if necessary, then run the benchmark to see if the cost for the new feature is acceptable and as expected. + +Most new features will add some overhead, but this needs to be limited and in proportion to the added value of the feature so runtime doesn't grow out of control. Optional features that can be disabled should only incur minimal overhead. Always make sure there are no major performance regressions compared to the code in the current *main* branch. Build the benchmarks by defining the CMake variable ``MEMILIO_BUILD_BENCHMARKS=ON`` in the build. Make sure to use a **Release** build to test performance. @@ -91,6 +95,10 @@ However, it can be expensive because long-running benchmarks are repeated. **Suggested workflow:** -1. Run with 5–10 repetitions to check variance. -2. Increase ``benchmark_min_time`` until variance is acceptable. -3. Continue benchmarking with 1 repetition and the adjusted minimum time. \ No newline at end of file +1. Use the benchmark to check the performance of your current changes: + + 1. Run with 5–10 repetitions to check variance. + 2. Increase ``benchmark_min_time`` until variance is acceptable. + 3. Continue benchmarking with 1 repetition and the adjusted minimum time. + +2. Repeat the benchmark *on the main branch* with the same ``benchmark_min_time`` and compare the results. From bdffc95e5b2fb23c18ce2d32e2a8b165842d7cfe Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:22:23 +0200 Subject: [PATCH 020/147] Restructure Development section to add types page --- docs/source/cpp/data_types.rst | 22 ++++ docs/source/cpp/development.rst | 104 +-------------- ..._timers.rst => performance_monitoring.rst} | 122 ++++++++++++++++-- docs/source/index.rst | 1 - 4 files changed, 141 insertions(+), 108 deletions(-) create mode 100644 docs/source/cpp/data_types.rst rename docs/source/cpp/{performance_timers.rst => performance_monitoring.rst} (62%) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst new file mode 100644 index 0000000000..6f381b7114 --- /dev/null +++ b/docs/source/cpp/data_types.rst @@ -0,0 +1,22 @@ +Common data types +----------------- + +Here we want to explain the intention behind the non-standard data types that are used throughout MEmilio. + +FP +~~ + +The template :code:`FP` and the derived types like :code:`UncertainValue` in these examples are commonly used throughout MEmilio. +:code:`FP` is a floating point type, usually :code:`double`. An :code:`UncertainValue` holds a value of type +:code:`FP` as well as (optionally) a distribution to sample new values from, e.g. for a parameter study. + +.. dropdown:: :fa:`gears` Expert's settings + + The type ``mio::AgeGroup`` is a typesafe ``size_t``, meaning an integer that cannot be confused with other integer + types. So assignment, addition, etc. only works with another ``mio::AgeGroup``, not ``size_t`` or another integer + type. This is useful for function interfaces or indexing, as it makes it (nearly) impossible to mix up, e.g., age + groups with infection states. Check out ``mio::Index`` if you want to learn more. + + The type ``mio::Populations`` is an extension of a ``mio::CustomIndexArray``, which is a template type that manages + a flat array. Its main purpose is to allow multidimensional indexing into this array, using typesafe indices like + a ``mio::Index`` or a ``enum class``. \ No newline at end of file diff --git a/docs/source/cpp/development.rst b/docs/source/cpp/development.rst index d6c7813d87..75e5a0524c 100644 --- a/docs/source/cpp/development.rst +++ b/docs/source/cpp/development.rst @@ -1,104 +1,10 @@ Development =========== -.. _performance-monitoring-cpp: +Here we collect some information that is relevant for the development processes of MEmilio. -Performance monitoring ----------------------- +.. toctree:: + :maxdepth: 2 -LIKWID -~~~~~~ - -To measure the performance of the code, we use LIKWID. We recommend measuring the performance on an HPC system with LIKWID installed or refer to ``_ on how to get LIKWID. -Run ``likwid-perfctr ./bin/my_example`` to measure the performance of the entire example. To measure the performance of a specific part, use the LIKWID marker API: - -.. code-block:: cpp - - #include - - // ... - - LIKWID_MARKER_INIT; - - LIKWID_MARKER_START("my_marker"); - // code to be measured - LIKWID_MARKER_STOP("my_marker"); - - LIKWID_MARKER_CLOSE; - -Set the CMake variable ``MEMILIO_USE_LIKWID=ON`` to enable LIKWID support and run ``likwid-perfctr -m ./bin/my_example``. -For more details see the LIKWID documentation, available `here `_. - -Agent-based model benchmarks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There is a suite of benchmarks for the ABM that are used to check its performance. The suite contains setups of different sizes, to check that the model maintains its linear scaling. - -When you make any changes to the ABM or code used by it, run the benchmarks to check that its performance did not degrade. What exactly impacts the ABM's performance can be hard to tell (even parameter values may change its runtime), so it is best to run the bencharks on any change to the main library or the ABM specific code. - -If you added a new feature (i.e., you didn't just fix a bug in an existing feature), make sure the feature is actually used by the benchmark. Add it to the benchmark if necessary, then run the benchmark to see if the cost for the new feature is acceptable and as expected. - -Most new features will add some overhead, but this needs to be limited and in proportion to the added value of the feature so runtime doesn't grow out of control. Optional features that can be disabled should only incur minimal overhead. Always make sure there are no major performance regressions compared to the code in the current *main* branch. - -Build the benchmarks by defining the CMake variable ``MEMILIO_BUILD_BENCHMARKS=ON`` in the build. Make sure to use a **Release** build to test performance. - -.. code-block:: bash - - cmake .. -DMEMILIO_BUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release - cmake --build . - -Run the benchmark executable: - -.. code-block:: bash - - ./build/bin/abm_benchmark - -Each benchmark is run for a number of iterations and the average time is reported. - -.. code-block:: text - - Benchmark Time CPU Iterations - --------------------------------------------------------------------------- - abm_benchmark/abm_benchmark_50k 7583 ms 7583 ms 1 - abm_benchmark/abm_benchmark_100k 18216 ms 18214 ms 1 - abm_benchmark/abm_benchmark_200k 41492 ms 41489 ms 1 - -You may get a warning: - -.. code-block:: text - - ***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. - -If possible, disable CPU scaling to improve the consistency of results. See the Google Benchmark documentation here: -https://google.github.io/benchmark/reducing_variance.html - -Also, try to reduce other system load during the benchmark run. - -If it is not possible to disable frequency scaling, increase the runtime of the benchmark using the commands below. Constant CPU frequency is necessary to get the most reliable results and to measure small differences. - -**REMINDER:** Don't forget to re-enable CPU scaling after you ran the benchmarks to save energy. Rebooting may restore the settings as well. - -The benchmark executable has a number of command line arguments that customize execution. Use ``--help`` to list them all. - -Two important options for consistency and stability: - -- ``--benchmark_min_time=``: Iterate each benchmark so that the total runtime is at least ``T`` seconds. - Default is 1 second, which may not be enough. - Try 60 seconds for better stability (you may need to experiment). - -- ``--benchmark_repetitions=``: Repeat each benchmark ``N`` times and report mean, median, and variance. - (Repetitions are **not** iterations; a benchmark can be repeated 10 times with 5 iterations each. Each repetition runs for at least the minimum time.) - -``benchmark_repetitions`` is useful to check timing consistency, as it reports variance. -However, it can be expensive because long-running benchmarks are repeated. -``benchmark_min_time`` increases iterations only for fast-running benchmarks, which tend to be less stable. - -**Suggested workflow:** - -1. Use the benchmark to check the performance of your current changes: - - 1. Run with 5–10 repetitions to check variance. - 2. Increase ``benchmark_min_time`` until variance is acceptable. - 3. Continue benchmarking with 1 repetition and the adjusted minimum time. - -2. Repeat the benchmark *on the main branch* with the same ``benchmark_min_time`` and compare the results. + performance_monitoring + data_types \ No newline at end of file diff --git a/docs/source/cpp/performance_timers.rst b/docs/source/cpp/performance_monitoring.rst similarity index 62% rename from docs/source/cpp/performance_timers.rst rename to docs/source/cpp/performance_monitoring.rst index 6d7f39cc90..7959a02b56 100644 --- a/docs/source/cpp/performance_timers.rst +++ b/docs/source/cpp/performance_monitoring.rst @@ -1,11 +1,40 @@ +.. _performance-monitoring-cpp: + +Performance monitoring +====================== + +LIKWID +------ + +To measure the performance of the code, we use LIKWID. We recommend measuring the performance on an HPC system with LIKWID installed or refer to ``_ on how to get LIKWID. +Run ``likwid-perfctr ./bin/my_example`` to measure the performance of the entire example. To measure the performance of a specific part, use the LIKWID marker API: + +.. code-block:: cpp + + #include + + // ... + + LIKWID_MARKER_INIT; + + LIKWID_MARKER_START("my_marker"); + // code to be measured + LIKWID_MARKER_STOP("my_marker"); + + LIKWID_MARKER_CLOSE; + +Set the CMake variable ``MEMILIO_USE_LIKWID=ON`` to enable LIKWID support and run ``likwid-perfctr -m ./bin/my_example``. +For more details see the LIKWID documentation, available `here `_. + + Performance Timers -================== +------------------ Here we present MEmilio's own timing framework. Timer usage ------------ +~~~~~~~~~~~ In this section we present how to use the AutoTimer class. This is the preferred way of using the timing framework, as the class takes care of running the timer, managing its lifetime for later evaluation, and ensuring thread safety with @@ -20,7 +49,7 @@ You can also try out the `code example ``: Iterate each benchmark so that the total runtime is at least ``T`` seconds. + Default is 1 second, which may not be enough. + Try 60 seconds for better stability (you may need to experiment). + +- ``--benchmark_repetitions=``: Repeat each benchmark ``N`` times and report mean, median, and variance. + (Repetitions are **not** iterations; a benchmark can be repeated 10 times with 5 iterations each. Each repetition runs for at least the minimum time.) + +``benchmark_repetitions`` is useful to check timing consistency, as it reports variance. +However, it can be expensive because long-running benchmarks are repeated. +``benchmark_min_time`` increases iterations only for fast-running benchmarks, which tend to be less stable. + +**Suggested workflow:** + +1. Use the benchmark to check the performance of your current changes: + + 1. Run with 5–10 repetitions to check variance. + 2. Increase ``benchmark_min_time`` until variance is acceptable. + 3. Continue benchmarking with 1 repetition and the adjusted minimum time. + +2. Repeat the benchmark *on the main branch* with the same ``benchmark_min_time`` and compare the results. diff --git a/docs/source/index.rst b/docs/source/index.rst index 4f0e2e25d9..1831069186 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -52,7 +52,6 @@ Contents cpp/model_usage cpp/model_creation cpp/development - cpp/performance_timers .. toctree:: :maxdepth: 2 From 71be7ae6352bb04594eacbfcad295b00849ef389 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:37:32 +0200 Subject: [PATCH 021/147] Docu review --- docs/source/cpp/models/oseair.rst | 6 +++--- docs/source/cpp/models/osecir.rst | 26 +++++++++++--------------- docs/source/cpp/models/oseir.rst | 28 ++++++++++++++-------------- docs/source/cpp/ode.rst | 31 +++++++++++++++---------------- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/docs/source/cpp/models/oseair.rst b/docs/source/cpp/models/oseair.rst index 24d15a60ce..c4ad6b4312 100644 --- a/docs/source/cpp/models/oseair.rst +++ b/docs/source/cpp/models/oseair.rst @@ -1,8 +1,8 @@ -ODE SEAIR Compartment Model +ODE-based SEAIR-type model =========================== -This model is an extended ODE type model. The six compartments +This model is an extended ODE-type model. The six compartments - **Susceptible** (:math:`S`): may become Exposed at any time. - **Exposed** (:math:`E`): becomes Asymptomatic after some time. @@ -65,4 +65,4 @@ Below is an overview of the model architecture and its compartments. Overview of the ``oseair`` namespace: ----------------------------------------- -.. doxygennamespace:: mio::oseair \ No newline at end of file +.. doxygennamespace:: mio::oseair diff --git a/docs/source/cpp/models/osecir.rst b/docs/source/cpp/models/osecir.rst index 0b81b68632..b04b663907 100644 --- a/docs/source/cpp/models/osecir.rst +++ b/docs/source/cpp/models/osecir.rst @@ -1,12 +1,11 @@ ODE-based SECIR-type model =========================== -The ODE-SECIR module models and simulates an epidemic using an approach based on ordinary differential equations. +The ODE-SECIR module models and simulates an epidemic using an ODE-based SECIR-type model approach. The model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical -states are possible. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. -In the following, we present the model in detail. +symptoms are possible. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. -The infection states and the transitions (also see next to sections) are visualized in the following image. +The infection states and the transitions (also see next two sections) are visualized in the following graph. .. image:: https://github.com/SciCompMod/memilio/assets/70579874/46b09e8a-d083-4ef9-8328-21975890b60f :alt: secir_model @@ -69,7 +68,7 @@ see :doc:`Model Creation <../ode_creation>`. Parameters ---------- -The model implements the following parameters. +The model implements the following parameters: .. list-table:: :header-rows: 1 @@ -165,7 +164,7 @@ For age-resolved models, you need to set the initial conditions for each age gro Nonpharmaceutical Interventions ------------------------------- -In the SECIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions. +In the SECIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings to the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions. Basic dampings can be added to the contact matrix as follows: @@ -188,7 +187,7 @@ For age-resolved models, you can apply different dampings to different groups: contact_matrix.add_damping(Eigen::VectorX::Constant((size_t)nb_groups, 0.7).asDiagonal(), mio::SimulationTime(30.)); -The SECIR model also supports dynamic NPIs based on epidemic thresholds. These are implemented in the model specific Simulation class and are automatically triggered based on predefined criteria, such as the percentage of infected individuals in the population. +The SECIR model also supports dynamic NPIs based on epidemic thresholds. These are implemented in the model specific **Simulation** class and are automatically triggered based on predefined criteria, such as the percentage of infected individuals in the population. For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific dampings. The SECIR model supports contact matrices for different locations (e.g., home, school, work, other) and can apply different dampings to each location. @@ -238,7 +237,7 @@ You can create intervention types that target specific locations with different Holidays, }; -For example, to implement a complex lockdown scenario with multiple interventions starting on a specific date: +A complex lockdown scenario with multiple interventions starting on a specific date can be implemented via: .. code-block:: cpp @@ -252,7 +251,7 @@ For example, to implement a complex lockdown scenario with multiple intervention contact_dampings.push_back(social_events(start_lockdown, 0.6, 0.8)); contact_dampings.push_back(physical_distancing(start_lockdown, 0.4, 0.6)); -For dynamic NPIs that activate automatically based on thresholds: +For dynamic NPIs that are automatically activated based on thresholds: .. code-block:: cpp @@ -272,7 +271,7 @@ The SECIR model offers two simulation functions: 1. **simulate**: Standard simulation that tracks the compartment sizes over time 2. **simulate_flows**: Extended simulation that additionally tracks the flows between compartments -Basic simulation: +Standard simulation: .. code-block:: cpp @@ -322,7 +321,7 @@ The output of the simulation is a `TimeSeries` object containing the sizes of ea Eigen::VectorXd last_value = secir.get_last_value(); double last_time = secir.get_last_time(); -For flow simulations, the result consists of two `TimeSeries` objects, one for compartment sizes and one for flows: +For flow simulations, the result consists of two `mio::TimeSeries` objects, one for compartment sizes and one for flows: .. code-block:: cpp @@ -352,7 +351,7 @@ Additionally, you can export the results to a CSV file: // Export results to CSV with default settings secir.export_csv("simulation_results.csv"); -The SECIR model also provides utility functions to extract specific measures, such as the reproduction number: +The ODE-SECIR model also provides utility functions to extract specific measures, such as the reproduction number: .. code-block:: cpp @@ -369,9 +368,6 @@ Visualization To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` and its documentation. -You can export your simulation results to CSV format as described above. - - Examples -------- diff --git a/docs/source/cpp/models/oseir.rst b/docs/source/cpp/models/oseir.rst index 30c6b637e9..a262300512 100644 --- a/docs/source/cpp/models/oseir.rst +++ b/docs/source/cpp/models/oseir.rst @@ -1,11 +1,11 @@ ODE-based SEIR-type model ========================= -The ODE-based SEIR-type module models and simulates the epidemic using an ODE-based SEIR-type model approach. The model +The ODE-SEIR module models and simulates the epidemic using an ODE-based SEIR-type model approach. The model is particularly suited for simple simulations of infectious diseases in a population and getting started with the MEmilio code. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. -The infection states and the transitions are visualized in the following image. +The infection states and the transitions are visualized in the following graph. .. image:: https://github.com/SciCompMod/memilio/assets/69154294/80a36be5-57d9-4012-9b5f-25eb08ec8837 :alt: SEIR_model @@ -27,7 +27,7 @@ Infection State Transitions --------------------------- The ODE-SEIR model is implemented as **FlowModel**. With just minimal overhead, the **FlowModel** computes the new -transmissions, infections, and hospitalizations explicitly in every time step instead of only computing the aggregated +transmissions, infections, and recoveries explicitly in every time step instead of only computing the aggregated compartment values. The defined transitions `FromState, ToState` are: .. code-block:: RST @@ -54,7 +54,7 @@ The number of age groups is specified in the model constructor and the model can Parameters ---------- -The model implements the following parameters. +The model implements the following parameters: .. list-table:: :header-rows: 1 @@ -115,7 +115,7 @@ and all other compartments: Nonpharmaceutical Interventions ------------------------------- -In the ODE-SEIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. +In the ODE-SEIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings to the contact matrix. These dampings reduce the contact rates between different sociodemographic groups to simulate interventions. Basic dampings can be added to the ContactPatterns as follows: @@ -148,13 +148,13 @@ The ODE-SEIR model offers two simulation functions: 1. **simulate**: Standard simulation that tracks the compartment sizes over time 2. **simulate_flows**: Extended simulation that additionally tracks the flows between compartments -Basic simulation: +Standard simulation: .. code-block:: cpp double t0 = 0; // Start time double tmax = 50; // End time - double dt = 0.1; // Time step + double dt = 0.1; // Initial step size // Run a standard simulation mio::TimeSeries result_sim = mio::oseir::simulate(t0, tmax, dt, model); @@ -182,8 +182,8 @@ For both simulation types, you can also specify a custom integrator: Output ------ -The output of the **Simulation** is a ``TimeSeries`` containing the sizes of each compartment at each time point. For A -basic simulation, you can access the results as follows: +The output of the **Simulation** is a ``mio::TimeSeries`` containing the sizes of each compartment at each time point. For A +standard simulation, you can access the results as follows: .. code-block:: cpp @@ -191,14 +191,14 @@ basic simulation, you can access the results as follows: auto num_points = static_cast(result_sim.get_num_time_points()); // Access data at specific time point - Eigen::VectorXd value_at_time_i = result_sim.get_value(i); + Eigen::VectorXd value_at_time_point_i = result_sim.get_value(i); double time_i = result_sim.get_time(i); // Access the last time point Eigen::VectorXd last_value = result_sim.get_last_value(); double last_time = result_sim.get_last_time(); -For flow simulations, the result consists of two `TimeSeries` objects, one for compartment sizes and one for flows: +For flow simulations, the result consists of two `mio::TimeSeries` objects, one for compartment sizes and one for flows: .. code-block:: cpp @@ -208,7 +208,7 @@ For flow simulations, the result consists of two `TimeSeries` objects, one for c // Access flows between compartments auto flows = result_flowsim[1]; -You can print the simulation results as a formatted table: +You can print the simulation results as a formatted table via: .. code-block:: cpp @@ -226,7 +226,7 @@ Additionally, you can export the results to a CSV file: // Export results to CSV with default settings result_sim.export_csv("simulation_results.csv"); -The SEIR model also provides utility functions to extract specific measures, such as the reproduction number: +The ODE-SEIR model also provides utility functions to extract specific measures, such as the reproduction number: .. code-block:: cpp // Calculate R value at a specific time index @@ -253,4 +253,4 @@ An example can be found at Overview of the ``oseir`` namespace: ----------------------------------------- -.. doxygennamespace:: mio::oseir \ No newline at end of file +.. doxygennamespace:: mio::oseir diff --git a/docs/source/cpp/ode.rst b/docs/source/cpp/ode.rst index ee435716c7..446568eb05 100644 --- a/docs/source/cpp/ode.rst +++ b/docs/source/cpp/ode.rst @@ -1,9 +1,9 @@ -ODE models -========== +ODE-based models +================ MEmilio implements various models based on ordinary differential equations (ODEs). ODE-based models are a subclass of -compartmental models in which individuals are grouped into compartments. MEmilio's ODE-based models range from most simple -SIR structure to complex models with multi-layer and waning immunity. These models can be stratified by age groups or +compartmental models in which individuals are grouped into subpopulations called compartments. MEmilio's ODE-based models range from most simple +SIR structures to complex models with multiple layers and waning immunity. These models can be stratified by age groups or other sociodemographic factors. In the following, we present the general structure for all simple ODE-based models. For generalizations via the Linear @@ -27,7 +27,7 @@ Infection state transitions Our ODE-based models are either implemented as **FlowModel** or as **CompartmentalModel**. In a **FlowModel**, flows ``Flow`` between **InfectionState**\s **State1** and **State2** are defined. Instead of a standard -solution to an ODE-based model, this implementation additionally realizes the solution of the transitions or flows +solution of an ODE-based model, this implementation additionally realizes the solution of the transitions or flows between states and directly enables users to access new transmissions or hospitalizations at any time point. The simpler class **CompartmentalModel** only considers the states of the system and not the flows. @@ -35,7 +35,7 @@ The simpler class **CompartmentalModel** only considers the states of the system Sociodemographic stratification ------------------------------- -For most models, the population can also be stratified by one sociodemographic dimension. This dimension is denoted +For most ODE-based models, the population can be stratified by one sociodemographic dimension. This dimension is denoted **AgeGroup** but can also be used for other interpretations. For stratifications with two or more dimensions, see :doc:`Model Creation `. @@ -49,10 +49,10 @@ compartment or the contact rates between different age groups. Most model parame pathogen-specific characteristics (possibly resolved by sociodemographic groups) and are represented by a vector with a value for each sociodemographic group. To model different contact rates between different sociodemographic groups, we use a parameter denoted **ContactPatterns** of type **UncertainContactMatrix**. The **UncertainContactMatrix** contains -a set of contact matrices of arbitrary length and which can represent the different contact locations in the model like +a set of contact matrices of arbitrary length which can represent different contact locations in the model like schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. -In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic -group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced +In the **ContactPatterns** parameter, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic +group :math:`i` and group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced to one value if no stratifcation is used. The values can be adjusted during the simulation, e.g., through implementing nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. Parameters can get accessed via ``model.parameters.get>()`` and set via either @@ -102,13 +102,12 @@ reduction factor :math:`r`, the reduced contact rate is :math:`(1-r) * c_{i,j}`. Simulation ---------- -Once the model is setup, run a simple simulation from time ``t0`` to ``tmax`` with an initial step size ``dt`` using the -``mio::simulation()`` function. This will run a simulation of type **Simulation** that does not save the flows between -compartments but only the sizes of each compartment over time. To use the flow information, make sure to use a -**FlowModel** and run a simulation of type **FlowSimulation** with the ``mio::simulate_flows()`` function. -You can run a simulation using either fixed or adaptive solution schemes with an absolute or relative tolerance. By -default, the simulation uses an adaptive solution scheme of the boost library and an absolute tolerance of 1e-10 and a -relative tolerance of 1e-5. For more details on the possible integration schemes, see . +Once the model is set up, one can run a simple simulation from time ``t0`` to ``tmax`` with an initial step size ``dt`` using the +``mio::simulation()`` function. This will run a simulation of type **Simulation** that saves the sizes of each compartment over time. +To also save the flow information, make sure to use a **FlowModel** and run a simulation of type **FlowSimulation** with the ``mio::simulate_flows()`` function. +You can run a simulation using either fixed or adaptive integration schemes with an absolute or relative tolerance. By +default, the simulation uses an adaptive integration scheme of the boost library with an absolute tolerance of 1e-10 and a +relative tolerance of 1e-5. Output From ec3cd8ed7e0e90a53ce92e8848064f600ae0ac55 Mon Sep 17 00:00:00 2001 From: Carlotta Gerstein Date: Tue, 22 Jul 2025 15:12:36 +0200 Subject: [PATCH 022/147] start review ide --- docs/source/cpp/ide.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/cpp/ide.rst b/docs/source/cpp/ide.rst index b5dc11ea9b..84a7d0f922 100644 --- a/docs/source/cpp/ide.rst +++ b/docs/source/cpp/ide.rst @@ -45,16 +45,16 @@ Parameters The parameters of the model are defined as structs and are combined in a class ``ParameterSet``. We use different types of parameters to represent epidemiological parameters such as the distributions of the stay times in a -compartment or the contact rates between different age groups. Most model parameters are constants that describe +compartment or the contact rates between different sociodemographic groups. Most model parameters are constants that describe pathogen-specific characteristics (possibly resolved by sociodemographic groups) and are represented by a vector with a -value for each sociodemographic group. To model different contact rates between different sociodemographic groups, we +value for each group. To model different contact rates between different sociodemographic groups, we use a parameter denoted **ContactPatterns** of type **UncertainContactMatrix**. The **UncertainContactMatrix** contains a set of contact matrices of arbitrary length and which can represent the different contact locations in the model like schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic -group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced -to one value if no stratifcation is used. The values can be adjusted during the simulation, e.g., through implementing +group :math:`i` and group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced +to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. An important feature of our IDE-based model is that we can choose the transition distributions in a flexible way. The From b8b6e5c5f5176c867b1d530caeb0fc59e4fcafd8 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:43:15 +0200 Subject: [PATCH 023/147] Secirvvs and surrogate model review --- docs/source/cpp/models/osecirvvs.rst | 37 +++++++++++----------- docs/source/python/memilio_surrogate.rst | 39 +++++++++++++++--------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/docs/source/cpp/models/osecirvvs.rst b/docs/source/cpp/models/osecirvvs.rst index e19ebc0264..f5db982b0d 100644 --- a/docs/source/cpp/models/osecirvvs.rst +++ b/docs/source/cpp/models/osecirvvs.rst @@ -1,9 +1,9 @@ -SECIR model with COVID-19 variants and vaccinations -===================================================== +ODE-based SECIR-type model with COVID-19 variants and vaccinations +==================================================================== -This model extends the basic SECIR model by adding vaccinations and allowing the implicit modeling of a newly arriving variant that takes hold. +This model extends the basic ODE-SECIR model by adding vaccinations and allowing the implicit modeling of a newly arriving variant that takes hold. -Vaccinations are modeled by adding compartments for partially and fully vaccinated persons. ``Partially and fully vaccinated`` is to be understood in this context as the person having received a first and second vaccine shot as in 2021. These model lines can be reused by resetting parameters. Persons that have recovered from the disease are treated as fully vaccinated from that time forward. Vaccinated persons are added on every day of simulation, see parameters ``DailyPartialVaccinations`` and ``DailyFullVaccinations``. All groups can get an infection or get reinfected. Vaccinated persons are less likely to develop symptoms. For example, the probability to develop symptoms when carrying the virus is the base probability from the SECIR model multiplied with the ``ReducInfectedSymptomsPartialImmunity`` parameter. +Vaccinations are modeled by adding compartments for partially and fully vaccinated persons. **Partially** and **fully vaccinated** is to be understood in this context as the person having received a first and second vaccine shot as in 2021. Persons that have recovered from the disease are treated as fully vaccinated from that time forward. Vaccinated persons are added on every day of simulation, see parameters ``DailyPartialVaccinations`` and ``DailyFullVaccinations``. All groups can get an infection or get reinfected. Vaccinated persons are less likely to develop symptoms. For example, the probability to develop symptoms when carrying the virus is the base probability from the ODE-SECIR model multiplied with the ``ReducInfectedSymptomsPartialImmunity`` parameter. The ratio of two variants can change over time, which affects the average transmissibility of the disease. Infectiousness of different variants can be set in the parameters. @@ -15,7 +15,7 @@ Below is an overview of the model architecture and its compartments. Infection States ---------------- -The model extends the basic SECIR model by dividing the compartments based on immunity levels. It contains the following list of **InfectionState**\s: +The model extends the basic ODE-SECIR model by dividing the compartments based on immunity levels. It contains the following list of **InfectionState**\s: .. code-block:: RST @@ -57,7 +57,7 @@ All compartments with the same base state (e.g., ExposedNaive, ExposedPartialImm Infection State Transitions --------------------------- -The ODE-SECIRVVS model is implemented as a **FlowModel**, which computes the flows between compartments explicitly. The model follows the same flow pattern as the basic SECIR model but with three parallel sets of compartments representing different immunity levels. +The ODE-SECIRVVS model is implemented as a **FlowModel**, which computes the flows between compartments explicitly. The model follows the same flow pattern as the basic ODE-SECIR model but with three parallel sets of compartments representing different immunity levels. The key characteristic of this model is that recovered individuals always end up in the improved immunity level, regardless of their starting immunity level. This represents the immunity gained after infection. @@ -83,18 +83,19 @@ For each immunity level (Naive, PartialImmunity, ImprovedImmunity), the followin Where * stands for the immunity level suffix (Naive, PartialImmunity, or ImprovedImmunity). -**Important:** Vaccinations are not implemented as flows between compartments but are handled discretely by the simulation. At the beginning of each simulated day, susceptible individuals are moved between immunity levels according to the specified daily vaccination parameters. This discrete process is separate from ODE system and is managed by the `apply_vaccination` function in the model specific Simulation class. +**Important:** Vaccinations are not implemented as flows between compartments but are handled discretely by the simulation. At the beginning of each simulated day, susceptible individuals are moved between immunity levels according to the specified daily vaccination parameters. This discrete process is separate from the ODE system and is managed by the `apply_vaccination` function in the model specific **Simulation** class. Sociodemographic Stratification ------------------------------- -Like the basic SECIR model, the SECIRVVS model can be stratified by one sociodemographic dimension, typically age groups. This stratification is important for modeling different vaccination rates, symptom severities, and mortality risks across age groups. +Like the basic ODE-SECIR model, the ODE-SECIRVVS model can be stratified by one sociodemographic dimension, typically age groups. This stratification is important for modeling different vaccination rates, symptom severities, and mortality risks across age groups. The dimension is denoted +**AgeGroup** but can also be used for other interpretations For stratifications with two or more dimensions, see :doc:`Model Creation <../ode_creation>`. Parameters ---------- -The model includes all parameters from the basic SECIR model plus additional parameters specific to vaccination and variant modeling. Here is a comprehensive list of the key parameters: +The model includes all parameters from the basic ODE-SECIR model plus additional parameters specific to vaccination and variant modeling. .. list-table:: :header-rows: 1 @@ -217,7 +218,7 @@ Initial conditions The initial conditions of the model are represented by the class **Populations** which defines the number of individuals in each sociodemographic group and **InfectionState**. Before running a simulation, you should set the initial values for each compartment across all immunity levels. -Below is a example showing how to initialize all compartments for the SECIRVVS model: +Below is an example showing how to initialize all compartments for the ODE-SECIRVVS model: .. code-block:: cpp @@ -280,7 +281,7 @@ After setting the initial populations, you also need to set the vaccination para Nonpharmaceutical Interventions ------------------------------- -The SECIRVVS model supports nonpharmaceutical interventions (NPIs) through dampings in the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions like lockdowns. +The ODE-SECIRVVS model supports nonpharmaceutical interventions (NPIs) through dampings to the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions like e.g. lockdowns. Basic dampings can be added to the contact matrix as follows: @@ -295,7 +296,7 @@ Basic dampings can be added to the contact matrix as follows: // Add a damping that reduces contacts by 30% starting at day 5 contact_matrix[0].add_damping(0.3, mio::SimulationTime(5.0)); -For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific dampings as in the SECIR model. The SECIRVVS model supports the same contact locations (e.g., home, school, work, other) and can apply different dampings to each location. +For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific dampings as in the ODE-SECIR model. The ODE-SECIRVVS model supports the same contact locations (e.g., home, school, work, other) and can apply different dampings to each location. Example of defining locations and interventions for a detailed scenario: @@ -326,9 +327,9 @@ The model also supports dynamic NPIs based on epidemic thresholds: // Configure dynamic NPIs auto& dynamic_npis = params.get>(); - dynamic_npis.set_interval(mio::SimulationTime(3.0)); // Check every 3 days - dynamic_npis.set_duration(mio::SimulationTime(14.0)); // Apply for 14 days - dynamic_npis.set_base_value(100'000); // Per 100,000 population + dynamic_npis.set_interval(mio::SimulationTime(3.0)); // Check NPI every 3 days + dynamic_npis.set_duration(mio::SimulationTime(14.0)); // Apply NPI for 14 days + dynamic_npis.set_base_value(100'000); // Base value to trigger NPI is population of 100,000 dynamic_npis.set_threshold(200.0, dampings); // Trigger at 200 cases per 100,000 Simulation @@ -371,7 +372,7 @@ For both simulation types, you can also specify a custom integrator: Output ------ -The output of the simulation is a `TimeSeries` object containing the sizes of each compartment at each time point. For a basic simulation, you can access the results as follows: +The output of the simulation is a `mio::TimeSeries` object containing the sizes of each compartment at each time point. For a standard simulation, you can access the results as follows: .. code-block:: cpp @@ -409,7 +410,7 @@ Visualization ------------- To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` -and its documentation. You can export your simulation results to CSV format as described above. +and its documentation. Examples -------- @@ -417,7 +418,7 @@ Examples The extended model is used in the ``2021_vaccination_sarscov2_delta_germany`` simulation. An easier example can be found in the `examples/ode_secirvvs.cpp `_. -Examples of the basic SECIR model can be found at: +Examples of the basic ODE-SECIR model can be found at: - `examples/ode_secir.cpp `_ - `examples/ode_secir_ageres.cpp `_ diff --git a/docs/source/python/memilio_surrogate.rst b/docs/source/python/memilio_surrogate.rst index c804d53d8b..354f230dc2 100644 --- a/docs/source/python/memilio_surrogate.rst +++ b/docs/source/python/memilio_surrogate.rst @@ -2,11 +2,11 @@ MEmilio Surrogate Model Package =============================== MEmilio Surrogate Model contains machine learning based surrogate models that make predictions based on the MEmilio simulation models. -Currently there are only surrogate models for ODE models. These simulations of these equation-based models are used for data generation. -The goal is to create a powerful tool that predicts the dynamics faster than a simulation of an expert model, +Currently there are only surrogate models for ODE-type models. The simulations of these models are used for data generation. +The goal is to create a powerful tool that predicts the infection dynamics faster than a simulation of an expert model, e.g., a metapopulation or agent-based model while still having acceptable errors with respect to the original simulations. -The package is contained inside the folder `pycode/memilio-surrogatemodel `_. +The package can be found in `pycode/memilio-surrogatemodel `_. For more details, we refer to: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024).*Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ @@ -29,25 +29,25 @@ Usage The package currently provides the following modules: -- `models`: models for different specific tasks +- `models`: models for different tasks Currently we have the following models: - `ode_secir_simple`: A simple model allowing for asymptomatic as well as symptomatic infection not stratified by age groups. - `ode_secir_groups`: A model allowing for asymptomatic as well as symptomatic infection stratified by age groups and including one damping. Each model folder contains the following files: - - `data_generation`: data generation from expert model simulation. - - `model`: training and evaluation of the model. - - `network_architectures`: multiple network architectures are saved in this file. - - `grid_search`: utilities for hyperparameter optimization. - - `hyperparameter_tuning`: scripts for tuning model hyperparameters. + - `data_generation`: Data generation from expert model simulation outputs. + - `model`: Training and evaluation of the model. + - `network_architectures`: Contains multiple network architectures. + - `grid_search`: Utilities for hyperparameter optimization. + - `hyperparameter_tuning`: Scripts for tuning model hyperparameters. - `tests`: this file contains all tests -SECIR Simple Model +ODE-SECIR Simple Model ----------------- -The `ode_secir_simple` module provides surrogate models for the basic SECIR epidemiological model. This model is not stratified by age groups and simulates disease progression through the following compartments: +The `ode_secir_simple` module provides surrogate models for the basic ODE-SECIR epidemiological model. This model is not stratified by age groups and simulates disease progression through the following compartments: - **S**: Susceptible - **E**: Exposed @@ -58,12 +58,12 @@ The `ode_secir_simple` module provides surrogate models for the basic SECIR epid - **U**: ICU (critical cases) - **D**: Dead -For more details on the model structure and parameters, we refer to the ODE SECIR model documentation. +For more details on the model structure and parameters, we refer to the ODE-SECIR model documentation. Data Generation ~~~~~~~~~~~~~~ -The `data_generation.py` module provides functionality to generate training data for the surrogate models by running multiple simulations of the SECIR model with randomized initial conditions. The data generation process involves: +The `data_generation.py` module provides functionality to generate training data for the surrogate models by running multiple simulations of the basic ODE-SECIR model with randomized initial conditions. The data generation process involves: .. code-block:: python @@ -78,10 +78,10 @@ The `data_generation.py` module provides functionality to generate training data save_data=True ) -The data generation process: +The data generation process can be summarized as follows: 1. Randomly initializes the model parameters and initial compartment populations -2. Runs the SECIR simulation using the C++ backend via Python bindings +2. Runs the ODE-SECIR simulation using the C++ backend via Python bindings 3. Applies logarithmic normalization to improve training stability 4. Splits each time series into input and label segments 5. Saves the dataset as a pickle file for later use @@ -92,14 +92,17 @@ Network Architectures The `network_architectures.py` module provides different neural network architectures for time series prediction: 1. **MLP (Multi-Layer Perceptron)**: + - Simple feedforward networks that take flattened time series as input - Available in both single-output and multi-output variants 2. **LSTM (Long Short-Term Memory)**: + - Recurrent neural networks specialized for sequence modeling - Can process variable-length time series while maintaining temporal information 3. **CNN (Convolutional Neural Network)**: + - Uses 1D convolutions to detect patterns in time series data - Particularly efficient for capturing local temporal patterns @@ -109,14 +112,17 @@ Model Training and Evaluation The `model.py` module provides functionality for: 1. **Preparing data**: + - Splitting data into training, validation, and test sets - Processing data for different model architectures (classic vs. time series) 2. **Model training**: + - Initializing models with customizable hyperparameters - Training with early stopping and customizable loss functions 3. **Evaluation**: + - Computing error metrics (MAE, MAPE) across compartments - Visualizing predictions versus ground truth @@ -143,14 +149,17 @@ Hyperparameter Optimization The `grid_search.py` and `hyperparameter_tuning.py` modules provide tools for systematic hyperparameter optimization: 1. **Cross-validation**: + - K-fold cross-validation to prevent overfitting - Evaluation of multiple model architectures and training configurations 2. **Grid search**: + - Systematic exploration of hyperparameter space - Tracking and storage of performance metrics 3. **Result analysis**: + - Visualization of hyperparameter importance - Selection of optimal model configurations From 7f55da85a191b243c08f9a4c1bccf87434c2e938 Mon Sep 17 00:00:00 2001 From: Carlotta Gerstein Date: Wed, 23 Jul 2025 10:23:30 +0200 Subject: [PATCH 024/147] review graph metapop --- docs/source/cpp/graph_metapop.rst | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/source/cpp/graph_metapop.rst b/docs/source/cpp/graph_metapop.rst index b5498fe946..e10ed9dc2b 100644 --- a/docs/source/cpp/graph_metapop.rst +++ b/docs/source/cpp/graph_metapop.rst @@ -15,7 +15,7 @@ During the simulation, graph nodes are advanced independently from each other ac The instant mobility approach was introduced by Kühn et al. (2021) and mainly represents daily instant commuting activities between different regions. Using realistic commuting data, such as the absolute number of commuters between regions, we can model realistic exchanges of individuals in the metapopulation context. -In this approach, the commuters are exchanged twice daily in an instantaneous manner, allowing the integration of age-specific mobility restrictions. The implementation also allows the restriction of mobility activities for certain infection states, such as symptomatic infected individuals. An important feature implemented in Kühn et al. (2022) is the testing of commuters, which enables the detection and potential isolation of asymptomatic or symptomatic infected individuals before they can infect people in other regions. +In this approach, the commuters are exchanged twice daily in an instantaneous manner, allowing the integration of age-specific mobility restrictions. The implementation also allows for the restriction of mobility activities for certain infection states, such as symptomatic infected individuals. An important feature implemented in Kühn et al. (2022) is the testing of commuters, which enables the detection and potential isolation of asymptomatic or symptomatic infected individuals before they can infect people in other regions. The instant mobility approach assumes instant exchange between regions without considering travel times. The length of stay is fixed to half a day. Since all regions exchange commuters simultaneously, this scheme offers great properties for parallelization and has fewer constraints on the integration time step compared to more detailed mobility approaches. @@ -29,9 +29,9 @@ For further details, please refer to: The detailed mobility scheme was introduced in Zunker et al. (2024) and further develops the instant approach. The detailed mobility model addresses some of the constraints of the instant approach by adding realistic travel and stay times. -The core idea of this detailed approach is that each region has a second, local model, which can be parameterized independently from the existing model, to represent mobility within the region. This allows for more detailed modeling of population movement. +The core idea of this detailed approach is that each region has a second local model, which can be parameterized independently from the existing model, to represent mobility within the region. This allows for more detailed modeling of population movement. -To have a realistic representation of the exchange process, we calculate the centroids of each region, which is represented by a polygon. When modeling travel between two non-adjacent regions (nodes k and l), the model creates a path connecting their centroids, which may pass through other regions. The predefined travel time between regions is then distributed across all regions along this path. +To have a realistic representation of the exchange process, we calculate the centroids of each region, which is represented by a polygon. When modeling travel between two non-adjacent regions (nodes :math:`k` and :math:`l`), the model creates a path connecting their centroids, which may pass through other regions. The predefined travel time between regions is then distributed across all regions along this path. Commuters move along these paths and pass through various mobility models during their trip. This allows for potential interactions with other commuters during transit. The detailed mobility approach enables modeling of scenarios that cannot be addressed with the instant approach, such as the impact of different mask type usage in public transport. However, this increased realism comes at the cost of greater computational effort and requires more detailed input data. For further details, please refer to: @@ -40,25 +40,24 @@ For further details, please refer to: **Stochastic mobility approach** -In the stochastic mobility approach, transitions of individuals between regions, i.e. graph nodes, are modeled as stochastic jumps. The frequency of individuals transitioning from region i to region j is determined by a rate :math:`\lambda_{ij}` which can be interpreted as the (expected) fraction of the population in region i that commutes to region j within one day. In contrast to the instant and detailed mobility approach, the time points and the number of transitions between two regions are stochastic. +In the stochastic mobility approach, transitions of individuals between regions, i.e. graph nodes, are modeled as stochastic jumps. The frequency of individuals transitioning from region :math:`i` to region :math:`j` is determined by a rate :math:`\lambda_{ij}` which can be interpreted as the (expected) fraction of the population in region :math:`i` that commutes to region :math:`j` within one day. In contrast to the instant and detailed mobility approach, the time points and the number of transitions between two regions are stochastic. How to: Set up a graph and run a graph simulation ------------------------------------------------- -The graph simulation couples multiple simulation instances representing different geographic regions via a graph-based approach. In this example, a compartment model based on ODEs (ODE-SECIR) is used for each region. The simulation proceeds by advancing each region independently and then exchanging portions of the population between regions along the graph edges. +The graph simulation couples multiple simulation instances representing different geographic regions via a graph-based approach. In this example, a compartmental model based on ODEs (ODE-SECIR) is used for each region. The simulation proceeds by advancing each region independently and then exchanging portions of the population between regions along the graph edges. The following steps detail how to configure and execute a graph simulation: -1. **Initialize the local compartment model:** +1. **Initialize the local compartmental model:** - First, set up the compartment model by initializing the populations and parameters. In this example, we use a single age group and start with 10,000 susceptible individuals. Additionally, the necessary epidemiological parameters (e.g. time periods, transmission probabilities) are set. + First, set up the compartmental model by initializing the parameters that are equal in every region. In this example, we use a single age group and set the necessary epidemiological parameters (e.g. time periods, transmission probabilities). .. code-block:: cpp const size_t num_groups = 1; mio::osecir::Model model(num_groups); - model.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}] = 10000; model.parameters.set(0); model.parameters.set>(0.2); @@ -83,7 +82,7 @@ The following steps detail how to configure and execute a graph simulation: 2. **Create simulation groups and adjust contact patterns:** - To represent different geographic regions, clone the base model into separate model groups. In this example, two model groups are created. The first group is modified by applying a contact damping to simulate contact restrictions. + To represent different geographic regions, clone the base model into separate model groups. In this example, two model groups are created. The first group is modified by applying a contact damping to simulate contact restrictions. Additionally, set the populations in all models. .. code-block:: cpp @@ -100,6 +99,8 @@ The following steps detail how to configure and execute a graph simulation: model_group1.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}] = 9990; model_group1.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Exposed}] = 100; + model_group2.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible}] = 10000; + 3. **Define compartments to save from edges:** To extract the mobility results, define the compartments to save from the edges. In this example, the compartments for infected individuals with and without symptoms are saved for each region. @@ -136,7 +137,7 @@ The following steps detail how to configure and execute a graph simulation: g.add_edge(1, 0, Eigen::VectorXd::Constant((size_t)mio::osecir::InfectionState::Count, 0.1), indices_save_edges); -For the stochastic mobility, ``mio::MobilityEdgeStochastic`` has to be used as edge type for the graph. The rates or mibility coefficients can be set as follows: + For the stochastic mobility, ``mio::MobilityEdgeStochastic`` has to be used as edge type for the graph. The rates or mobility coefficients can be set as follows: .. code-block:: cpp @@ -161,7 +162,7 @@ For the stochastic mobility, ``mio::MobilityEdgeStochastic`` has to be used as e .. code-block:: cpp const auto tmax = 30.; - const auto dt = 0.5; // time step or Mobility (daily mobility occurs every second step) + const auto dt = 0.5; // time step for Mobility (daily mobility occurs every second step) auto sim = mio::make_mobility_sim(t0, dt, std::move(g)); sim.advance(tmax); @@ -174,6 +175,4 @@ For the stochastic mobility, ``mio::MobilityEdgeStochastic`` has to be used as e auto& edge_1_0 = sim.get_graph().edges()[1]; auto& results = edge_1_0.property.get_mobility_results(); results.print_table({"Commuter INS", "Commuter ISy", "Commuter Total"}); - - return 0; - } + From ff11382d59928d5295cb488eab646d7117a95098 Mon Sep 17 00:00:00 2001 From: Carlotta Gerstein Date: Wed, 23 Jul 2025 10:55:06 +0200 Subject: [PATCH 025/147] review model creation ode --- docs/source/cpp/ode_creation.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/source/cpp/ode_creation.rst b/docs/source/cpp/ode_creation.rst index d71f86d0f4..3be94de6f5 100644 --- a/docs/source/cpp/ode_creation.rst +++ b/docs/source/cpp/ode_creation.rst @@ -4,8 +4,8 @@ ODE model creation The mathematical model ---------------------- -Before implementing a model in MEmilio, we need to do a some math, in particular, define an initial value problem -given by a system of ordinary differential equations. For example we consider a SIRD model given by +Before implementing a model in MEmilio, we need to do some math, in particular, define an initial value problem +given by a system of ordinary differential equations. For example, we consider a SIRD model given by .. math:: @@ -16,7 +16,7 @@ given by a system of ordinary differential equations. For example we consider a D'(t) & = \frac{\mu_D}{T_I}I(t) \\ \end{aligned} -and some initial values for :math:`t=0`. Here :math:`N_{\perp D} := S(t) + I(t) + R(t)`. +and some initial values for :math:`t=0`. Here, :math:`N_{\perp D} := S(t) + I(t) + R(t)`. This type of model is called compartmental model, because the model population is represented by discrete infection states **S**\usceptible, **I**\nfectious, **R**\ecovered, **D**\eceased, also called compartments. @@ -29,15 +29,15 @@ use different methods to define the right hand side of the mathematical model ab the infection states, population and parameters it uses. The FlowModel additionally requires a list of flows. We start by creating a new directory for our model under "cpp/models", in this case we can call it "ode_sird". The name -must be unique and start with "ode\_", so the type of model is obvious. The rest usually contains the compartments or +must be unique and start with "ode\_", so the type of model is obvious. The rest of the name usually contains the compartments or other noteworthy features of the model in shortened form. All files in the following are put into this directory. Infection states ~~~~~~~~~~~~~~~~ -First we define an :code:`enum class` called ``InfectionState`` in the file "infection_state.h", which contains an entry +First, we define an :code:`enum class` called ``InfectionState`` in the file "infection_state.h", which contains an entry for each infection state of the mathematical model, followed by an entry called :code:`Count`. This enumerates the -compartments starting from 0, with Count being equal to the number of compartments. In our example we have: +compartments starting from 0, with :code:`Count` being equal to the number of compartments. In our example we have: .. code-block:: cpp @@ -154,6 +154,8 @@ Now we can define the model: } }; +Here, the :code:`get_derivatives` function implements the right hand side of the differential equations. + Define a flow model ^^^^^^^^^^^^^^^^^^^ @@ -161,7 +163,7 @@ A flow model is a special case of a compartmental model, where each compartment .. math:: - Z_i(t) = \sum_{i \ne j} f_{Z_j \rightarrow Z_i}(t) - \sum_{i \ne j} f_{Z_i \rightarrow Z_j}(t), + Z'_i(t) = \sum_{j \ne i} f_{Z_j \rightarrow Z_i}(t) - \sum_{j \ne i} f_{Z_i \rightarrow Z_j}(t), where the flows :math:`f_{Z_i \rightarrow Z_j} \gt 0` are the amount of population changing from compartment :math:`Z_i` to :math:`Z_j` at time :math:`t`. So the first sum accumulates all inflows, the second subtracts all From e59c89f3ea6e8aa1ebc9d122c85e65a362f4af5f Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Wed, 23 Jul 2025 13:15:28 +0200 Subject: [PATCH 026/147] docu review --- docs/source/cpp/lct.rst | 55 ++++++++------------------ docs/source/cpp/models/lsecir.rst | 59 ++++++++++------------------ docs/source/cpp/models/osecirts.rst | 44 ++++++++++----------- docs/source/cpp/models/osecirvvs.rst | 2 +- docs/source/cpp/ode.rst | 6 +-- 5 files changed, 63 insertions(+), 103 deletions(-) diff --git a/docs/source/cpp/lct.rst b/docs/source/cpp/lct.rst index 58741d5173..33a61109cd 100644 --- a/docs/source/cpp/lct.rst +++ b/docs/source/cpp/lct.rst @@ -1,13 +1,9 @@ Linear Chain Trick model ========================= -MEmilio implements a SECIR-type model utilizing the Linear Chain Trick (LCT). This is a generalization of simple ODE -models and allows for Erlang distributed stay times in the compartments by introducing subcompartments. Note that the -resulting system can still be described by ODEs. The LCT-SECIR model can be stratified by age groups or other sociodemographic -factors. +MEmilio implements a SECIR-type model utilizing the Linear Chain Trick (LCT). This is a generalization of simple ODE-based models and allows for Erlang distributed stay times in the compartments by introducing subcompartments. Note that the resulting system is still described by ODEs. The LCT-SECIR model can be stratified by age groups or other sociodemographic factors. -In the following, we present the general structure of the LCT model. The particular model documentation with examples -is linked at the bottom of this page. +In the following, we present the general structure of the LCT model. The particular model documentation with examples is linked at the bottom of this page. Infection states ---------------- @@ -27,15 +23,15 @@ To make use of the LCT, we additionally need to define the numbers of subcompart `Number of subcompartments of State2` `...` -Note that the model is implemented as **CompartmentalModel**. +The model is implemented as **CompartmentalModel**. Sociodemographic stratification ------------------------------- -For this model, the population can also be stratified by one sociodemographic dimension. This dimension can be used -for age groups but can also be used for other interpretations. +For this model, the population can be stratified by one sociodemographic dimension. This dimension can be used +for age groups but for other interpretations. Parameters @@ -44,15 +40,10 @@ Parameters The parameters of the model are defined as structs and are combined in a class ``ParameterSet``. We use different types of parameters to represent epidemiological parameters such as the mean stay times in a compartment or the contact rates between different age groups. Most model parameters are constants that describe -pathogen-specific characteristics (possibly resolved by sociodemographic groups) and are represented by a vector with a -value for each sociodemographic group. To model different contact rates between different sociodemographic groups, we -use a parameter denoted **ContactPatterns** of type **UncertainContactMatrix**. The **UncertainContactMatrix** contains -a set of contact matrices of arbitrary length and which can represent the different contact locations in the model like +pathogen-specific characteristics (possibly resolved by sociodemographic groups) and are represented by a vector with a value for each sociodemographic group. +To model different contact rates between different sociodemographic groups, we use a parameter denoted **ContactPatterns** of type **UncertainContactMatrix**. The **UncertainContactMatrix** contains a set of contact matrices of arbitrary length and which can represent the different contact locations in the model like schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. -In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic -group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced -to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing -nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. +In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initialization and is reduced to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. Parameters can be accessed via ``model.parameters.get>()`` and set via either ``model.parameters.get>() = value`` or ``model.parameters.set>(value)``. @@ -60,20 +51,15 @@ Parameters can be accessed via ``model.parameters.get>()`` and set Initial conditions ------------------ -The initial conditions of the model are represented by a class **LctPopulations** that gives the number of individuals in -each sociodemographic group and each subcompartment of each **InfectionState**. For more details, see -:doc:`Model Creation `. Before the simulation, set the initial conditions for each **InfectionState** and -sociodemographic group. +The initial conditions of the model are represented by a class **LctPopulations** that gives the number of individuals in each sociodemographic group and each subcompartment for each **InfectionState**. For more details, see :doc:`Model Creation `. Before the simulation, the initial conditions for each **InfectionState** and sociodemographic group must be set. .. _Nonpharmaceutical Interventions: Nonpharmaceutical interventions ------------------------------- -Contact rates can be adjusted during the simulation to model nonpharmaceutical interventions (NPIs) such as lockdowns, -school closures, or social distancing. This is done by adding **Damping**\s to the **ContactPatterns** of the model. A -**Damping** is defined by a time point at which the intervention starts and a matrix of the same size as the -**ContactMatrix**. While in many cases, the reduction matrix is given by a constant matrix with factor :math:`r`, also +Contact rates can be adjusted during the simulation to model nonpharmaceutical interventions (NPIs) such as lockdowns, school closures, or social distancing. This is done by adding **Damping**\s to the **ContactPatterns** of the model. A **Damping** is defined by a time point at which the intervention starts and a matrix of the same size as the **ContactMatrix**. +While in many cases, the reduction matrix is given by a constant matrix with factor :math:`r`, also group-specific reductions are possible through setting particular rows or columns differently. With a constant reduction factor :math:`r`, the reduced contact rate is :math:`(1-r) * c_{i,j}`. @@ -94,31 +80,24 @@ reduction factor :math:`r`, the reduced contact rate is :math:`(1-r) * c_{i,j}`. Simulation ---------- -Once the model is setup, run a simple simulation from time ``t0`` to ``tmax`` with an initial step size ``dt`` using the -``mio::simulation()`` function. This will run a simulation of type **Simulation** that saves the sizes of each -subcompartment over time. -You can run a simulation using either fixed or adaptive solution schemes with an absolute or relative tolerance. By -default, the simulation uses an adaptive solution scheme of the boost library and an absolute tolerance of 1e-10 and a -relative tolerance of 1e-5. For more details on the possible integration schemes, see . +Once the model is set up, one can run a simple simulation from time ``t0`` to ``tmax`` with initial step size ``dt`` using the ``mio::simulation()`` function. This will run a simulation of type **Simulation** that saves the sizes of each subcompartment over time. +You can run a simulation using either fixed or adaptive integration schemes with an absolute or relative tolerance. By default, the simulation uses an adaptive solution scheme of the boost library with an absolute tolerance of 1e-10 and a relative tolerance of 1e-5. For more details on the possible integration schemes, see . Output ------ -The output of the **Simulation** is a **TimeSeries** ``result`` containing the sizes of each subcompartment at each time point. +The output of the **Simulation** is a ``mio::TimeSeries`` containing the sizes of each subcompartment at each time point. To obtain a result with respect to the compartments, the subcompartments can be accumulated via the function ``calculate_compartments()``. A simple table can be printed using the ``print_table()`` function of the -``TimeSeries`` class. The compartment sizes can be printed with ``model.calculate_compartments(result).print_table()``. -As adaptive step size methods are used by default, the output will not be available on equidistant time points like `dt` -or days. To obtain outputs on days or user-defined time points, you can interpolate the results to days or -any other series of times points with ``mio::interpolate_simulation_result()``. +``mio::TimeSeries`` class. The compartment sizes can be printed with ``model.calculate_compartments(result).print_table()``. +As adaptive step size methods are used by default, the output will not be available on equidistant time points like `dt` or days. To obtain outputs on days or user-defined time points, you can interpolate the results to days or any other series of times points with ``mio::interpolate_simulation_result()``. Visualization ------------- -To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../python/memilio_plot>` -and its documentation. +To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../python/memilio_plot>` and its documentation. List of models diff --git a/docs/source/cpp/models/lsecir.rst b/docs/source/cpp/models/lsecir.rst index a848e94b6a..fdaec8d6bb 100644 --- a/docs/source/cpp/models/lsecir.rst +++ b/docs/source/cpp/models/lsecir.rst @@ -2,12 +2,10 @@ LCT-based SECIR-type model ========================== The LCT-SECIR module models and simulates an epidemic using an ODE-based approach while making use of the Linear Chain -Trick to provide the option of Erlang distributed stay times in the compartments through the use of subcompartments. -The model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical -states are possible. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. -In the following, we present the model in detail. +This provides the option of Erlang distributed stay times in the compartments through the use of subcompartments. +The model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical states are possible. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. -Below is a visualization of the infection states and transitions. without a stratification according to groups. +Below is a visualization of the infection states and transitions without a stratification according to socisdemographic groups. .. image:: https://github.com/SciCompMod/memilio/assets/70579874/6a5d5a95-20f9-4176-8894-c091bd48bfb7 :alt: tikzLCTSECIR @@ -15,12 +13,9 @@ Below is a visualization of the infection states and transitions. without a stra For a detailed description and application of the model, see: -- Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: -Implications of underlying assumptions for numerical solutions*. Under review. `https://doi.org/10.48550/arXiv.2412.09140 `_ -- Plötzke L. (2023). *Der Linear Chain Trick in der epidemiologischen Modellierung als Kompromiss zwischen gewöhnlichen -und Integro-Differentialgleichungen*. Master's thesis, University of Cologne. `https://elib.dlr.de/200381/ `_ -- Hurtado PJ, Kirosingh AS. (2019). *Generalizations of the ‘Linear Chain Trick’: incorporating more flexible dwell -time distributions into mean field ODE models*. Journal of Mathematical Biology. `https://doi.org/10.1007/s00285-019-01412-w `_ +- Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Under review. `https://doi.org/10.48550/arXiv.2412.09140 `_ +- Plötzke L. (2023). *Der Linear Chain Trick in der epidemiologischen Modellierung als Kompromiss zwischen gewöhnlichen und Integro-Differentialgleichungen*. Master's thesis, University of Cologne. `https://elib.dlr.de/200381/ `_ +- Hurtado PJ, Kirosingh AS. (2019). *Generalizations of the ‘Linear Chain Trick’: incorporating more flexible dwell time distributions into mean field ODE models*. Journal of Mathematical Biology. `https://doi.org/10.1007/s00285-019-01412-w `_ Infection States @@ -45,14 +40,13 @@ It is possible to include subcompartments for the five compartments `Exposed`, ` Sociodemographic Stratification ------------------------------- -In the LDE-SECIR model, the population can be stratified by one sociodemographic dimension. This dimension is denoted -**Group**. It can be used for age groups as well as for other interpretations. +In the LCT-SECIR model, the population can be stratified by one sociodemographic dimension. This dimension is denoted **Group**. It can be used for age groups as well as for other interpretations. Parameters ---------- -The model implements the following parameters. +The model implements the following parameters: .. list-table:: :header-rows: 1 @@ -128,7 +122,7 @@ The notation of the compartments with indices here stands for subcompartments an Initial conditions ------------------ -To initialize the model, we start by defining the number of subcompartments and constructing the model. We can choose the number of subcompartments. +To initialize the model, we start by defining the number of subcompartments and constructing the model with it. .. code-block:: cpp @@ -140,7 +134,7 @@ To initialize the model, we start by defining the number of subcompartments and using Model = mio::lsecir::Model; Model model; -For the simulation, we need initial values for all (sub)compartments. If we do not set the initial values manually, these are internally set to :math:`0`. +For the simulation, we need initial values for all (sub)compartments. If we do not set the initial values manually, these are set to :math:`0` by default. We start with constructing a vector ``initial_populations`` that we will pass on to the model. It contains vectors for each compartment, that contains a vector with initial values for the respective subcompartments. @@ -149,7 +143,7 @@ We start with constructing a vector ``initial_populations`` that we will pass on std::vector> initial_populations = {{750}, {30, 20}, {20, 10, 10}, {50}, {50}, {10, 10, 5, 3, 2}, {20}, {10}}; -We assert that vector has the correct size by checking that the number of `InfectionState`\s and the numbers of subcompartments are correct. +We assert that the vector has the correct size by checking that the number of `InfectionState`\s and the numbers of subcompartments are correct. .. code-block:: cpp @@ -175,7 +169,7 @@ We assert that vector has the correct size by checking that the number of `Infec return 1; } -Now, we transfer the vector ``initial_populations`` to the model. +The initial populations in the model are set via: .. code-block:: cpp @@ -191,21 +185,15 @@ Now, we transfer the vector ``initial_populations`` to the model. In addition to setting the initial populations manually, MEmilio provides two other ways of setting the initial populations: -- The file `parameters_io `_ provides -functionality to compute an initial value vector for the LCT-SECIR model based on reported data. -- The file `initializer_flows `_ -provides functionality to compute an initial value vector for the LCT-SECIR model based on initial data in the form of -a TimeSeries of InfectionTransitions. For the concept of the InfectionTransitions or flows, see also the IDE-SECIR model. -This method can be particularly useful if a comparison is to be made with an IDE model with matching initialization or -if the reported data is in the form of flows. +- The file `parameters_io `_ provides functionality to compute an initial value vector for the LCT-SECIR model based on reported data. +- The file `initializer_flows `_ provides functionality to compute an initial value vector for the LCT-SECIR model based on initial data in the form of a ``mio::TimeSeries`` of InfectionTransitions. For the concept of the InfectionTransitions or flows, see also the IDE-SECIR model. This method can be particularly useful if a comparison is to be made with an IDE model with matching initialization or if the reported data is in the form of flows. .. _Nonpharmaceutical Interventions: Nonpharmaceutical Interventions ------------------------------- -In the SECIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. -These dampings reduce the contact rates between different groups to simulate interventions. +In the LCT-SECIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions. Basic dampings can be added to the contact matrix as follows: @@ -231,8 +219,7 @@ For age-resolved models, you can apply different dampings to different groups: mio::SimulationTime(30.)); -For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific -dampings. The SECIR model supports contact matrices for different locations (e.g., home, school, work, other) and can apply different dampings to each location. +For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific dampings. The LCT-SECIR model supports contact matrices for different locations (e.g., home, school, work, other) and can apply different dampings to each location. Example for defining different contact locations: @@ -284,7 +271,7 @@ You can create intervention types that target specific locations with different Simulation ---------- -We can simulate using the defined model from :math:`t_0` to :math:`t_{\max}` with initial step size :math:`dt` as follows: +We can simulate the model from :math:`t_0` to :math:`t_{\max}` with initial step size :math:`dt` as follows: .. code-block:: cpp @@ -309,14 +296,13 @@ You can also specify a custom integrator: Output ------ -The simulation result is divided by subcompartments. We can call the function ``calculate_compartments()`` to get a -result according to the `InfectionState`\s . +The simulation result is stratefied by subcompartments. The function ``calculate_compartments()`` aggregates the subcompartments by `InfectionState`\s. .. code-block:: cpp mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); -You can access the data in the `TimeSeries` object as follows: +You can access the data in the `mio::TimeSeries` object as follows: .. code-block:: cpp @@ -354,10 +340,7 @@ Additionally, you can export the results to a CSV file: Visualization ------------- -To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` -and its documentation. - -You can export your simulation results to CSV format as described above. +To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` and its documentation. Examples @@ -371,4 +354,4 @@ An example can be found at: Overview of the ``lsecir`` namespace: ----------------------------------------- -.. doxygennamespace:: mio::lsecir \ No newline at end of file +.. doxygennamespace:: mio::lsecir diff --git a/docs/source/cpp/models/osecirts.rst b/docs/source/cpp/models/osecirts.rst index 6f2fbcac40..1624803ee1 100644 --- a/docs/source/cpp/models/osecirts.rst +++ b/docs/source/cpp/models/osecirts.rst @@ -1,10 +1,10 @@ -SECIRTS-type model including multi-layer waning immunity -========================================================= +ODE-based SECIRTS-type model including multi-layer waning immunity +================================================================== -This model extends the SECIRVVS model by adding waning immunity and introducing temporary immunity states that change the meaning of recovery. -We structured the model into three layers of immunity: naive immunity, partial immunity, improved immunity. +This model extends the ODE-SECIRVVS model by adding waning immunity and introducing temporary immunity states that change the meaning of recovery. +Like the ODE-SECIRVVS model, the ODE-SECIRTS model has three layers of immunity: naive immunity, partial immunity, improved immunity. -In the model, waning immunity is defined by the parameters ``TimeWaningPartialImmunity``, ``TimeWaningImprovedImmunity``, ``TimeTemporaryImmunityPI``, and ``TimeTemporaryImmunityII``. The parameters ``TimeWaningPartialImmunity`` and ``TimeWaningImprovedImmunity`` represent the (mean) duration after which an individual transitions from one immunity layer to the next less protected one due to waning immunity, assuming no vaccination or recovery from infection has occurred during this period. Also, the parameters ``TimeTemporaryImmunityPI`` and ``TimeTemporaryImmunityII`` denote the (mean) duration of temporary immunity following exposure to the virus, either through vaccination or recovery. During this state of temporary immunity, individuals are protected from reinfection and are incapable of transmitting the virus to others. Should individuals previously reside in the naive or partial immunity layer, their stay in the temporary immunity state results in a transition to the next more protected immunity layer. +Additionally, waning immunity is defined by the parameters ``TimeWaningPartialImmunity``, ``TimeWaningImprovedImmunity``, ``TimeTemporaryImmunityPI``, and ``TimeTemporaryImmunityII``. The parameters ``TimeWaningPartialImmunity`` and ``TimeWaningImprovedImmunity`` represent the (mean) duration after which an individual transitions from one immunity layer to the next weaker one due to waning immunity, assuming no vaccination or recovery from infection has occurred during this period. Similarly, the parameters ``TimeTemporaryImmunityPI`` and ``TimeTemporaryImmunityII`` denote the (mean) duration of temporary immunity following exposure to the virus, either through vaccination or recovery. During this state of temporary immunity, individuals are protected from reinfection and are incapable of transmitting the virus to others. Should individuals previously reside in the naive or partial immunity layer, their stay in the temporary immunity state results in a transition to the next stronger immunity layer. For more details about the model, we refer to `1 `_. @@ -16,7 +16,7 @@ Below is an overview of the model architecture and its compartments. Infection States ---------------- -The model extends the SECIRVVS model by adding temporary immunity states and flow paths for waning immunity. It contains the following list of **InfectionState**\s: +The model extends the ODE-SECIRVVS model by adding temporary immunity states and flow paths for waning immunity. It contains the following list of **InfectionState**\s: .. code-block:: RST @@ -62,9 +62,9 @@ The model extends the SECIRVVS model by adding temporary immunity states and flo Infection State Transitions --------------------------- -The ODE-SECIRTS model is implemented as a **FlowModel**, which computes the flows between compartments explicitly. A key difference from the SECIRVVS model is that vaccinations in the SECIRTS model are implemented as flows within the ODE system rather than discrete events. +The ODE-SECIRTS model is implemented as a **FlowModel**, which computes the flows between compartments explicitly. A key difference from the ODE-SECIRVVS model is that vaccinations in the ODE-SECIRTS model are implemented as flows within the ODE system rather than discrete events. -The complete list of flows defined in the model is extensive and includes: +The model has the following state trnsitions: .. code-block:: RST @@ -110,14 +110,14 @@ The complete list of flows defined in the model is extensive and includes: Sociodemographic Stratification ------------------------------- -Like the previous SECIR models, the SECIRTS model can be stratified by one sociodemographic dimension, typically age groups. This stratification is important for modeling different vaccination rates, symptom severities, mortality risks, and immunity waning rates across age groups. - +Like the other ODE-SECIR models, the ODE-SECIRTS model can be stratified by one sociodemographic dimension, typically age groups. This stratification is important for modeling different vaccination rates, symptom severities, mortality risks, and immunity waning rates across age groups. The dimension is denoted +**AgeGroup** but can also be used for other interpretations. For stratifications with two or more dimensions, see :doc:`Model Creation <../ode_creation>`. Parameters ---------- -The model includes all parameters from the SECIRVVS model plus additional parameters specific to waning immunity and temporary immunity states: +The model includes all parameters from the ODE-SECIRVVS model as well as additional parameters specific to waning and temporary immunity states: .. list-table:: :header-rows: 1 @@ -226,7 +226,7 @@ The model includes all parameters from the SECIRVVS model plus additional parame Initial conditions ------------------ -The initial conditions of the model are represented by the class **Populations** which defines the number of individuals in each sociodemographic group and **InfectionState**. Before running a simulation, you should set the initial values for each compartment across all immunity levels. The following code is from the ode_secirts.cpp example: +The initial conditions of the model are represented by the class **Populations** which defines the number of individuals in each sociodemographic group and **InfectionState**. Before running a simulation, the initial values for each compartment across all immunity levels have to be set. This can be done via: .. code-block:: cpp @@ -263,7 +263,7 @@ The initial conditions of the model are represented by the class **Populations** model.populations[{i, mio::osecirts::InfectionState::DeadImprovedImmunity}] = 0; } -After setting the initial populations, you also need to configure the daily vaccination parameters, which are directly integrated into the ODE system in this model: +After setting the initial populations, the daily vaccination parameters, which are directly integrated into the ODE system in this model, also need to be configured: .. code-block:: cpp @@ -287,7 +287,7 @@ After setting the initial populations, you also need to configure the daily vacc Nonpharmaceutical Interventions ------------------------------- -Like other SECIR models, the SECIRTS model supports nonpharmaceutical interventions (NPIs) through dampings in the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions like lockdowns. +Like the other ODE-SECIR models, the ODE-SECIRTS model supports nonpharmaceutical interventions (NPIs) through dampings in the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions like lockdowns. Basic dampings can be added to the contact matrix as follows: @@ -313,18 +313,18 @@ The model also supports dynamic NPIs based on epidemic thresholds: dynamic_npis.set_base_value(100'000); // Per 100,000 population dynamic_npis.set_threshold(200.0, dampings); // Trigger at 200 cases per 100,000 -For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific dampings, we refer to the documentation of the ODE SECIR model. +For more complex scenarios, such as real-world lockdown modeling, detailed NPIs with location-specific dampings can be implemented. For further details, see the documentation of the ODE-SECIR model. Simulation ---------- -The SECIRTS model offers the same simulation functions as the other SECIR models: +The SECIRTS model offers the same simulation functions as the other ODE-SECIR models: 1. **simulate**: Standard simulation that tracks the compartment sizes over time 2. **simulate_flows**: Extended simulation that additionally tracks the flows between compartments -Basic simulation: +Standard simulation: .. code-block:: cpp @@ -337,9 +337,9 @@ Basic simulation: During simulation, the model handles several special processes: -1. **Vaccinations**: Unlike the SECIRVVS model, vaccinations are integrated directly into the ODE system through flows from susceptible compartments to temporary immunity compartments. +1. **Vaccinations**: Unlike the ODE-SECIRVVS model, vaccinations are integrated directly into the ODE system through flows from susceptible compartments to temporary immunity compartments. -2. **Variant Evolution**: The `apply_variant` function updates the transmission probability based on the existance of a new variant over time, similar to other SECIR models. +2. **Variant Evolution**: The `apply_variant` function updates the transmission probability based on the existance of a new variant over time, similar to other ODE-SECIR models. For both simulation types, you can also specify a custom integrator: @@ -356,7 +356,7 @@ For both simulation types, you can also specify a custom integrator: Output ------ -The output of the simulation is a `TimeSeries` object containing the sizes of each compartment at each time point. For a basic simulation, you can access the results as follows: +The output of the simulation is a `mio::TimeSeries` object containing the sizes of each compartment at each time point. For a standard simulation, you can access the results as follows: .. code-block:: cpp @@ -390,12 +390,12 @@ Visualization ------------- To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` -and its documentation. You can export your simulation results to CSV format as described above. +and its documentation. Examples -------- -To get started with the SECIRTS model, check out the code example in the memilio repository: +To get started with the ODE-SECIRTS model, check out the code example in the memilio repository: `examples/ode_secirts.cpp `_. Overview of the ``osecirts`` namespace: diff --git a/docs/source/cpp/models/osecirvvs.rst b/docs/source/cpp/models/osecirvvs.rst index f5db982b0d..b86179a4ca 100644 --- a/docs/source/cpp/models/osecirvvs.rst +++ b/docs/source/cpp/models/osecirvvs.rst @@ -89,7 +89,7 @@ Sociodemographic Stratification ------------------------------- Like the basic ODE-SECIR model, the ODE-SECIRVVS model can be stratified by one sociodemographic dimension, typically age groups. This stratification is important for modeling different vaccination rates, symptom severities, and mortality risks across age groups. The dimension is denoted -**AgeGroup** but can also be used for other interpretations +**AgeGroup** but can also be used for other interpretations. For stratifications with two or more dimensions, see :doc:`Model Creation <../ode_creation>`. Parameters diff --git a/docs/source/cpp/ode.rst b/docs/source/cpp/ode.rst index 446568eb05..4ccf0ffa3f 100644 --- a/docs/source/cpp/ode.rst +++ b/docs/source/cpp/ode.rst @@ -35,9 +35,7 @@ The simpler class **CompartmentalModel** only considers the states of the system Sociodemographic stratification ------------------------------- -For most ODE-based models, the population can be stratified by one sociodemographic dimension. This dimension is denoted -**AgeGroup** but can also be used for other interpretations. For stratifications with two or more dimensions, -see :doc:`Model Creation `. +For most ODE-based models, the population can be stratified by one sociodemographic dimension. This dimension is denoted **AgeGroup** but can also be used for other interpretations. For stratifications with two or more dimensions, see :doc:`Model Creation `. Parameters @@ -113,7 +111,7 @@ relative tolerance of 1e-5. Output ------ -The output of the **Simulation** is a ``TimeSeries`` containing the sizes of each compartment at each time point. A +The output of the **Simulation** is a ``mio::TimeSeries`` containing the sizes of each compartment at each time point. A simple table can be printed using the ``print_table()`` function of the ``TimeSeries`` class. The output of the **FlowSimulation** additionally contains the flows between compartments at each time point. The compartment sizes can be printed with ``result[0].print_table()`` and the flows with ``result[1].print_table()``. From 7786b0812df76025d9b223adb87e4a45c0d2b7fc Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:50:02 +0200 Subject: [PATCH 027/147] GLCT Review --- docs/source/cpp/glct.rst | 56 +++++++------------------- docs/source/cpp/models/glsecir.rst | 63 ++++++++++++------------------ 2 files changed, 39 insertions(+), 80 deletions(-) diff --git a/docs/source/cpp/glct.rst b/docs/source/cpp/glct.rst index 7735c457b7..dc8ca98d7e 100644 --- a/docs/source/cpp/glct.rst +++ b/docs/source/cpp/glct.rst @@ -1,13 +1,10 @@ Generalized Linear Chain Trick model ==================================== -MEmilio implements a SECIR-type model utilizing the Generalized Linear Chain Trick (GLCT). This is a generalization of -the LCT model. In contrast to simpler ODE models that assume (possibly unrealistic) exponentially distributed stay times, the GLCT allows for more realistic, phase-type distributed stay times in the compartments through the use of subcompartments. Phase-type distributions are dense -in the field of all positive-valued distributions. Therefore, for any positive-valued distribution, a phase-type -distribution of arbitrary precision can be identified. Note that the resulting system can still be described by an ordinary differential equation system. +MEmilio implements a SECIR-type model utilizing the Generalized Linear Chain Trick (GLCT). This is a generalization of the LCT model. In contrast to simpler ODE models that assume (possibly unrealistic) exponentially distributed stay times, the GLCT allows for more realistic, phase-type distributed stay times in the compartments through the use of subcompartments. Phase-type distributions are dense in the field of all positive-valued distributions. Therefore, for any positive-valued distribution, a phase-type +distribution of arbitrary precision can be identified. Note that the resulting system can still be described by an ODE-system. -In the following, we present the general structure of the GLCT model. The particular model documentation with examples -is linked at the bottom of this page. +In the following, we present the general structure of the GLCT model. The particular model documentation with examples is linked at the bottom of this page. Infection states ---------------- @@ -27,7 +24,7 @@ To make use of the GLCT, we additionally need to define the numbers of subcompar `Number of subcompartments of State2` `...` -Note that the model is implemented as **CompartmentalModel**. +The model is implemented as **CompartmentalModel**. Parameters @@ -36,15 +33,8 @@ Parameters The parameters of the model are defined as structs and are combined in a class ``ParameterSet``. We use different types of parameters to represent epidemiological parameters such as the mean stay times in a compartment or the contact rates between different age groups. Most model parameters are constants that describe -pathogen-specific characteristics (possibly resolved by sociodemographic groups) and are represented by a vector with a -value for each sociodemographic group. To model different contact rates between different sociodemographic groups, we -use a parameter denoted **ContactPatterns** of type **UncertainContactMatrix**. The **UncertainContactMatrix** contains -a set of contact matrices of arbitrary length and which can represent the different contact locations in the model like -schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. -In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic -group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced -to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing -nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. +pathogen-specific characteristics (possibly resolved by sociodemographic groups) and are represented by a vector with a value for each sociodemographic group. To model different contact rates between different sociodemographic groups, we use a parameter denoted **ContactPatterns** of type **UncertainContactMatrix**. The **UncertainContactMatrix** contains a set of contact matrices of arbitrary length which can e.g. represent the different contact locations in the model like schools, workplaces, or homes. The matrices can be loaded or stored in the particular example. +In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic group :math:`i` to group :math:`j`. The dimension of the matrix is automatically defined by the model initialization and it is reduced to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. Parameters can be accessed via ``model.parameters.get>()`` and set via either ``model.parameters.set>(value)`` or ``model.parameters.get>() = value``. @@ -52,22 +42,14 @@ Parameters can be accessed via ``model.parameters.get>()`` and set Initial conditions ------------------ -The initial conditions of the model are represented by a class **LctPopulations** that gives the number of individuals in -each sociodemographic group and each subcompartment of each **InfectionState**. For more details, see -:doc:`Model Creation `. Before the simulation, set the initial conditions for each **InfectionState** and -sociodemographic group. +The initial conditions of the model are represented by a class **LctPopulations** that gives the number of individuals in each sociodemographic group and each subcompartment of each **InfectionState**. For more details, see :doc:`Model Creation `. Before the simulation, the initial conditions for each **InfectionState** and sociodemographic group must be set. .. _Nonpharmaceutical Interventions: Nonpharmaceutical interventions ------------------------------- -Contact rates can be adjusted during the simulation to model nonpharmaceutical interventions (NPIs) such as lockdowns, -school closures, or social distancing. This is done by adding **Damping**\s to the **ContactPatterns** of the model. A -**Damping** is defined by a time point at which the intervention starts and a matrix of the same size as the -**ContactMatrix**. While in many cases, the reduction matrix is given by a constant matrix with factor :math:`r`, also -group-specific reductions are possible through setting particular rows or columns differently. With a constant -reduction factor :math:`r`, the reduced contact rate is :math:`(1-r) * c_{i,j}`. +Contact rates can be adjusted during the simulation to model nonpharmaceutical interventions (NPIs) such as lockdowns, school closures, or social distancing. This is done by adding **Damping**\s to the **ContactPatterns** of the model. A **Damping** is defined by a time point at which the intervention starts and a matrix of the same size as the **ContactMatrix**. While in many cases, the reduction matrix is given by a constant matrix with factor :math:`r`, also group-specific reductions are possible through setting particular rows or columns differently. With a constant reduction factor :math:`r`, the reduced contact rate is :math:`(1-r) * c_{i,j}`. .. dropdown:: :fa:`gears` Expert's settings @@ -86,36 +68,28 @@ reduction factor :math:`r`, the reduced contact rate is :math:`(1-r) * c_{i,j}`. Simulation ---------- -Once the model is setup, run a simple simulation from time ``t0`` to ``tmax`` with an initial step size ``dt`` using the -``mio::simulation()`` function. This will run a simulation of type **Simulation** that saves the sizes of each -subcompartment over time. -You can run a simulation using either fixed or adaptive solution schemes with an absolute or relative tolerance. By -default, the simulation uses an adaptive solution scheme of the boost library and an absolute tolerance of 1e-10 and a -relative tolerance of 1e-5. For more details on the possible integration schemes, see . +Once the model is setup, one can run a simple simulation from time ``t0`` to ``tmax`` with initial step size ``dt`` using the ``mio::simulation()`` function. This will run a simulation of type **Simulation** that saves the sizes of each subcompartment over time. +You can run a simulation using either fixed or adaptive integration schemes with an absolute or relative tolerance. By default, the simulation uses an adaptive solution scheme of the boost library with an absolute tolerance of 1e-10 and a relative tolerance of 1e-5. For more details on the possible integration schemes, see . Output ------ -The output of the **Simulation** is a **TimeSeries** ``result`` containing the sizes of each subcompartment at each time point. +The output of the **Simulation** is a ``mio::TimeSeries`` containing the sizes of each subcompartment at each time point. To obtain a result with respect to the compartments, the subcompartments can be accumulated via the function ``calculate_compartments()``. A simple table can be printed using the ``print_table()`` function of the -``TimeSeries`` class. The compartment sizes can be printed with ``model.calculate_compartments(result).print_table()``. -As adaptive step size methods are used by default, the output will not be available on equidistant time points like `dt` -or days. To obtain outputs on days or user-defined time points, you can interpolate the results to days or -any other series of times points with ``mio::interpolate_simulation_result()``. +``mio::TimeSeries`` class. The compartment sizes can be printed with ``model.calculate_compartments(result).print_table()``. +As adaptive step size methods are used by default, the output will not be available on equidistant time points like `dt` or days. To obtain outputs on days or user-defined time points, you can interpolate the results to days or any other series of times points with ``mio::interpolate_simulation_result()``. Visualization ------------- -To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../python/memilio_plot>` -and its documentation. +To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../python/memilio_plot>` and its documentation. -In the following, we give detailed explanations of the GLCT-SECIR model. List of models ------------------------------ +-------------- .. toctree:: :titlesonly: diff --git a/docs/source/cpp/models/glsecir.rst b/docs/source/cpp/models/glsecir.rst index c3a50f48bf..c0454f0a59 100644 --- a/docs/source/cpp/models/glsecir.rst +++ b/docs/source/cpp/models/glsecir.rst @@ -1,16 +1,10 @@ GLCT SECIR model ================ -The GLCT-SECIR module models and simulates an epidemic using an ODE-based approach while making use of the Generalized -Linear Chain Trick to provide the option of phase-type distributed stay times in the compartments through the use of -subcompartments. The model is particularly suited for pathogens with pre- or asymptomatic infection states and when -severe or critical states are possible. The model assumes perfect immunity after recovery and is thus only suited for -epidemic use cases. In the following, we present the model in detail. +The GLCT-SECIR module models and simulates an epidemic using an ODE-based approach while making use of the Generalized Linear Chain Trick to provide the option of phase-type distributed stay times in the compartments through the use of subcompartments. The model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical states are possible. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. In the following, we present the model in detail. The compartment structure with subcompartments is the same as in the LCT-SECIR model. An overview of the model -architecture can be found in the `LCT model `_. For the GLCT model, some additional transitions are possible and -we have more arrows in the model architecture. Below is an example for the Exposed compartment. Note that some indices -are omitted (e.g., :math:`n` instead of :math:`n_E`) to keep the picture simple. +architecture can be found in the `LCT model `_. For the GLCT model, some additional transitions are possible and we have more arrows in the model architecture. Below is an example for the Exposed compartment. Note that some indices are omitted (e.g., :math:`n` instead of :math:`n_E`) to keep the picture simple. .. image:: https://github.com/user-attachments/assets/fc075b7a-6cd2-4e70-bdd0-a2f4b9f2cf53 :alt: tikzGLCTSECIR @@ -41,8 +35,7 @@ It is possible to include subcompartments for the five compartments `Exposed`, ` Parameters --------------- -Below is an overview of the model variables and the model equations are stated. For a simpler description let -:math:`\mathcal{Z}=\{E,I_{NS},I_{Sy},I_{Sev},I_{Cr}\}` be the set of the compartments that can be divided into subcompartments. +Below is an overview of the model variables: .. list-table:: :header-rows: 1 @@ -73,10 +66,12 @@ Below is an overview of the model variables and the model equations are stated. - ``TransitionMatrix(...z)To(...*)`` - Matrix describing the transitions in between the subcompartments of :math:`z \in \mathcal{Z}` that describes the transition to the compartment *. +The model equations are given below. For a simpler description let :math:`\mathcal{Z}=\{E,I_{NS},I_{Sy},I_{Sev},I_{Cr}\}` be the set of the compartments that can be divided into subcompartments. + .. image:: https://github.com/SciCompMod/memilio/assets/70579874/e1da5e1d-e719-4c16-9f14-45374be7c353 :alt: equations -Note that the bold notation :math:`\mathbf{z}(t)` for :math:`z \in \mathcal{Z}` stands for a vector. If several transitions are possible from a compartment, the vector is split in order to be able to select the stay times until the transitions in each case phase-type distributed. For example, the order +Note that the bold notation :math:`\mathbf{z}(t)` for :math:`z \in \mathcal{Z}` stands for a vector. If several transitions are possible from a compartment, the vector is split in order to be able to select the stay times until the transitions individually. For example, the order .. math:: @@ -124,7 +119,7 @@ It is important that the sizes of the vectors and matrices match each other and Initial conditions ------------------ -We start by defining the number of subcompartments and constructing the model. We can choose the number of subcompartments individually for the compartments Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and InfectedCritical. +We start by defining the number of subcompartments and constructing the model with it. We can choose the number of subcompartments individually for the compartments Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and InfectedCritical. Note that in the GLCT model, we define two strains for the compartments `InfectedNoSymptoms`, `InfectedSymptoms`, `InfectedSevere` and `InfectedCritical` as individuals in these compartments can either transition to an infection state corresponding to a more severe disease state or recover. This is why we define the model with twice the number of subcompartments compared to the LCT-SECIR model for these infection states. .. code-block:: cpp @@ -152,19 +147,14 @@ We continue by defining some epidemiological parameters needed throughout the mo const ScalarType criticalPerSevere = 0.25; const ScalarType deathsPerCritical = 0.3; -Now, we define the initial values with the distribution of the population into subcompartments. Note that this method -of defining the initial values using a vector of vectors is not necessary, but should remind you how the entries of the -initial value vector relate to the defined template parameters of the model or the number of subcompartments. It is -also possible to define the initial values directly. +Now, we define the initial values with the distribution of the population into subcompartments. Note that this method of defining the initial values using a vector of vectors is not necessary, but should show how the entries of the initial value vector relate to the defined template parameters of the model or the number of subcompartments. It is also possible to define the initial values directly. -In this example, we want to initialize the GLCT model so that it corresponds to the example given for the LCT model. -This is why we take the initial population from the LCT example and split it into two strains according to the -respective transition probabilities for the the compartments InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and +In this example, we initialize the GLCT model so that it corresponds to the example given for the LCT model. +For that, we take the initial population from the LCT example and split it into two strains according to the +respective transition probabilities for the compartments InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and InfectedCritical. -Note that in the case of InfectedNoSymptoms, the first three subcompartments correspond to the first strain, i.e. the -individuals that will transition to InfectedSymptoms afterwards, and the other three subcompartments correspond to the -second strain, i.e. the individuals that willl recover. For the other compartments for which we defined two strains in -the model, this is done analogously. +In the case of InfectedNoSymptoms, the first three subcompartments correspond to the first strain, i.e. the +individuals that will transition to InfectedSymptoms afterwards, and the other three subcompartments correspond to the second strain, i.e. the individuals that willl recover. For the other compartments for which we defined two strains in the model, this is done analogously. We continue by defining some epidemiological parameters needed throughout the model definition and initialization. @@ -184,7 +174,7 @@ We continue by defining some epidemiological parameters needed throughout the mo {20}, // Recovered {10}}; // Dead -Here, we assert that ``initial_populations`` has the right shape. +Below, we assert that ``initial_populations`` has the right shape. .. code-block:: cpp @@ -221,9 +211,9 @@ Finally, we transfer the initial values in ``initial_populations`` to the model. } -Since we want to recreate the LCT model as defined in the corresponding example, we want to set the parameters determining the transition distributions such that we obtain Erlang distributions. +Since we want to recreate the LCT model as defined in the corresponding example, we set the parameters determining the transition distributions such that we obtain Erlang distributions. -We will explain how to do this for the different compartments below. In general, we need to define a vector ``StartingProbabilities(...)`` and a matrix ``TransitionMatrix(...z)To(...*)`` for all compartments with subcompartments, i.e. `Exposed`, `InfectedNoSymptoms`, `InfectedSymptoms`, `InfectedSevere` and `InfectedCritical`. +We will explain how to do this for the different compartments in the following. In general, we need to define a vector ``StartingProbabilities(...)`` and a matrix ``TransitionMatrix(...z)To(...*)`` for all compartments with subcompartments, i.e. `Exposed`, `InfectedNoSymptoms`, `InfectedSymptoms`, `InfectedSevere` and `InfectedCritical`. The size of the vector ``StartingProbabilities`` is the number of subcompartments and contains the initial probability of starting in any of the subcompartments of the respective compartment. The entries should sum up to 1. @@ -262,7 +252,7 @@ The strains have a length of ``NumInfectedNoSymptoms/2`` each as we choose the s model.parameters.get() = StartingProbabilitiesInfectedNoSymptoms; -Define equal transition matrices for the strains. They follow the same Erlang distribution such that we get the same result as with LCT model that can only consider one strain. +Equal transition matrices for the strains have to be defined. They follow the same Erlang distribution such that we get the same result as with the LCT model that can only consider one strain. .. code-block:: cpp @@ -326,7 +316,7 @@ We proceed analogously for the remaining compartments `InfectedSymptoms`, `Infec Nonpharmaceutical Interventions ------------------------------- -In the SECIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. +In the GLCT-SECIR model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. These dampings reduce the contact rates between different groups to simulate interventions. Basic dampings can be added to the contact matrix as follows: @@ -354,8 +344,7 @@ For age-resolved models, you can apply different dampings to different groups: -For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific -dampings. The SECIR model supports contact matrices for different locations (e.g., home, school, work, other) and can apply different dampings to each location. +For more complex scenarios, such as real-world lockdown modeling, you can implement detailed NPIs with location-specific dampings. The GLCT-SECIR model supports contact matrices for different locations (e.g., home, school, work, other) and can apply different dampings to each location. Example for defining different contact locations: @@ -407,7 +396,7 @@ You can create intervention types that target specific locations with different Simulation ---------- -We can simulate using the defined model from :math:`t_0` to :math:`t_{\max}` with initial step size :math:`dt_init` as follows: +Simulating the model from :math:`t_0` to :math:`t_{\max}` with initial step size :math:`dt_init` id done as follows: .. code-block:: cpp @@ -431,14 +420,13 @@ You can also specify a custom integrator: Output ------ -The simulation result is divided by subcompartments. We can call the function ``calculate_compartments()`` to get a -result according to the `InfectionState`\s . +The simulation result is divided by subcompartments. The function ``calculate_compartments()`` aggregates the subcompartments by `InfectionState`\s . .. code-block:: cpp mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); -You can access the data in the `TimeSeries` object as follows: +You can access the data in the `mio::TimeSeries` object as follows: .. code-block:: cpp @@ -476,10 +464,7 @@ Additionally, you can export the results to a CSV file: Visualization ------------- -To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` -and its documentation. - -You can export your simulation results to CSV format as described above. +To visualize the results of a simulation, you can use the Python package :doc:`memilio_plot <../../python/memilio_plot>` and its documentation. Examples @@ -493,4 +478,4 @@ An example can be found at: Overview of the ``glsecir`` namespace: -------------------------------------- -.. doxygennamespace:: mio::glsecir \ No newline at end of file +.. doxygennamespace:: mio::glsecir From 2f28dcf68feebc8777d3bdb3c87fbf8dfbad1001 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Wed, 23 Jul 2025 17:42:00 +0200 Subject: [PATCH 028/147] CHG: add internal links to io section --- docs/source/cpp/io.rst | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/source/cpp/io.rst b/docs/source/cpp/io.rst index a48b5500fb..17dfe467f1 100644 --- a/docs/source/cpp/io.rst +++ b/docs/source/cpp/io.rst @@ -4,15 +4,17 @@ Input / Output This document describes utilities for reading and writing data from and to files in different formats, in cases where ``TimeSeries::print_table()`` is not enough. The main sections are: -- The serialization framework, that can be used to define the structure of data without using a specific file format. +- The :ref:`serialization framework`, that can be used to define the structure of data without using a specific file format. There are implementations of the framework for different formats. The framework is described in detail below, also see the `serialization example `__. -- The command line interface, that can be used to set (and get) values of a ``ParameterSet``. +- The :ref:`command line interface`, that can be used to set (and get) values of a ``ParameterSet``. -- The History class for logging almost arbitrary information. It can be thought of as a generalization of a results +- The :ref:`History class` for logging almost arbitrary information. It can be thought of as a generalization of a results ``TimeSeries``, and is mainly used for the ABM. - + + +.. _serialization: The Serialization framework --------------------------- @@ -54,13 +56,13 @@ There is also support for a binary format. If you want to use a format directly Main functions and types ~~~~~~~~~~~~~~~~~~~~~~~~ -- **functions serialize and deserialize**: - Main entry points to the framework to write and read values, respectively. The functions expect an IOContext +- **Functions serialize and deserialize**: + Main entry points to the framework to write and read values, respectively. The functions expect an `IOContext` (see Concepts below) that stores the serialized data. (De-)serialization can be customized by providing a (de-)serialize_internal overload or a (de-)serialize member function for the type. See the section "Implementing serialization for a new type" or the documentation for ``serialize`` and ``deserialize``. - **IOStatus and IOResult**: - Used for error handling, see section "Error Handling" below. + Used for error handling, see section :ref:`Error Handling` below. Default serialization ~~~~~~~~~~~~~~~~~~~~~ @@ -108,10 +110,12 @@ default serialization cannot be used if your class has optional members or value as a pointer. As to the feature set, default-serialization only supports the ``add_element`` and ``expect_element`` operations defined -in the Concepts section below, where each operation's arguments are provided through the ``add`` function. Note that the +in the :ref:`Concepts` section, where each operation's arguments are provided through the ``add`` function. Note that the value provided to ``add`` is also used to assign a value during deserialization, hence the class members must be used directly in the function (i.e. as a non-const lvalue reference). +.. _concepts: + Concepts ~~~~~~~~ @@ -169,6 +173,8 @@ Concepts contain a value or it may be empty. Otherwise returns an error. Note that for some formats a wrong key is indistinguishable from an empty optional, so make sure to provide the correct key. +.. _error handling: + Error handling ~~~~~~~~~~~~~~ @@ -229,6 +235,8 @@ Other IO modules - HDF5 support classes for C++ - Reading of mobility matrix files +.. _command line: + The command line interface -------------------------- @@ -266,6 +274,8 @@ followed by the value that should be assigned (reference ``--print_option``). Va corresponding to the Type of the parameter. Note that some characters may need to be escaped or quoted. For example, the JSON string ``"some string"`` must be entered as ``\\"some string\\"`` or ``'"some string"'``. +.. _history: + Working with the History object ------------------------------- From 9931705a4e97909460cbfaa11ab9fb454ded67bc Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 25 Jul 2025 08:50:15 +0200 Subject: [PATCH 029/147] FIX: wrong formatting in diffusive_abm --- docs/source/cpp/diffusive_abm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/diffusive_abm.rst b/docs/source/cpp/diffusive_abm.rst index 6d7c9c285d..084be6f8cb 100644 --- a/docs/source/cpp/diffusive_abm.rst +++ b/docs/source/cpp/diffusive_abm.rst @@ -35,7 +35,7 @@ Using the infection states Susceptible (S), Exposed (E), Carrier (C), Infected ( Infection state transitions --------------------------- -The infection state transitions are explicitly given by the adoption rates and are therefore subject to user input. Adoption rates always depend on their source infection state. If an adoption event requires interaction of agents (e.g. disease transmission), the corresponding rate depends not only on the source infection state, but also on other infection states, the **Influence**s. An adoption rate that only depends on the source infection state, e.g. recovery or worsening of disease symptoms, is called `first-order` adoption rate and an adoption rate that has influences is called `second-order` adoption rate. Adoption rates are region-dependent; therefore it is possible to have different rates in two regions for the same state transition which can be useful when having e.g. region-dependent interventions or contact behavior. +The infection state transitions are explicitly given by the adoption rates and are therefore subject to user input. Adoption rates always depend on their source infection state. If an adoption event requires interaction of agents (e.g. disease transmission), the corresponding rate depends not only on the source infection state, but also on other infection states, the **Influence**\s. An adoption rate that only depends on the source infection state, e.g. recovery or worsening of disease symptoms, is called `first-order` adoption rate and an adoption rate that has influences is called `second-order` adoption rate. Adoption rates are region-dependent; therefore it is possible to have different rates in two regions for the same state transition which can be useful when having e.g. region-dependent interventions or contact behavior. Using the infection states from above and only one region, there are five first-order and one second-order adoption rate that can be set via: From e3961f86b5be434ef05f6e63d31a0d9ba682daa7 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 25 Jul 2025 11:15:08 +0200 Subject: [PATCH 030/147] CHG: Formatting in io updated --- docs/source/cpp/io.rst | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/source/cpp/io.rst b/docs/source/cpp/io.rst index 17dfe467f1..925e4cfc40 100644 --- a/docs/source/cpp/io.rst +++ b/docs/source/cpp/io.rst @@ -137,10 +137,10 @@ Concepts - ``io.flags()``: Returns the flags that determine the behavior of serialization; see IOFlags. - ``io.error()``: - Returns an IOStatus object to check if there were any errors during serialization. Usually it is not necessary to + Returns an ``IOStatus`` object to check if there were any errors during serialization. Usually it is not necessary to check this manually but can be used to report the error faster and avoid expensive operations that would be wasted anyway. - - ``io.set_error(s)`` with some IOStatus object: + - ``io.set_error(s)`` with some ``IOStatus`` object: Stores an error that was generated outside of the IOContext, e.g., if a value that was deserialized is outside an allowed range. @@ -148,12 +148,12 @@ Concepts Gives structured access to serialized data. During serialization, data can be added with ``add_...`` operations. During deserialization, data can be retrieved with ``expect_...`` operations. Data must be retrieved in the same - orderas it was added since, e.g., binary format does not allow lookup by key. The following operations are supported + order as it was added since, e.g., binary format does not allow lookup by key. The following operations are supported for an IOObject ``obj``: - ``obj.add_element("Name", t)``: Stores an object ``t`` in the IOObject under the key "Name". If ``t`` is of basic type (i.e., int, string), - IOObjectis expected to handle it directly. Otherwise, the object uses ``mio::serialize`` to get the data for ``t``. + IOObject is expected to handle it directly. Otherwise, the object uses ``mio::serialize`` to get the data for ``t``. - ``obj.add_list("Name", b, e)``: Stores the elements in the range represented by iterators ``b`` and ``e`` under the key "Name". The individual elements are not named. The elements are either handled directly by the IOObject or using ``mio::serialize`` just @@ -178,10 +178,10 @@ Concepts Error handling ~~~~~~~~~~~~~~ -Errors are handled by returning error codes. The type IOStatus contains an error code and an optional string with -additional information. The type IOResult contains either a value or an IOStatus that describes an error. Operations +Errors are handled by returning error codes. The type ``IOStatus`` contains an error code and an optional string with +additional information. The type ``IOResult`` contains either a value or an ``IOStatus`` that describes an error. Operations that can fail return an ``IOResult`` where T is the type of the value that is produced by the operation if it is -successful. Except where necessary because of dependencies, the framework does not throw nor catch any exceptions. +successful. Except where necessary because of dependencies, the MEmilio framework does neither throw nor catch any exceptions. IOContext and IOObject implementations are expected to store errors. During serialization, ``add_...`` operations fail without returning errors, but the error is stored in the IOObject and subsequent calls are usually no-ops. During deserialization, the values produced must usually be used or inspected, so ``expect_...`` operations return an IOResult. @@ -195,8 +195,8 @@ Adding a new data type to be serialized Serialization of a new type T can be customized by providing *either* member functions ``serialize`` and ``deserialize`` *or* free functions ``serialize_internal`` and ``deserialize_internal``. -The ``void serialize(IOContext& io)`` member function takes an IO context and uses ``create_object`` and ``add_...`` -operations to add data. The static ``IOResult deserialize(IOContext& io)`` member function takes an IO context and +The ``void serialize(IOContext& io)`` member function takes an IOContext and uses ``create_object`` and ``add_...`` +operations to add data. The static ``IOResult deserialize(IOContext& io)`` member function takes an IOContext and uses ``expect_...`` operations to retrieve the data. The ``apply`` utility function can be used to inspect the result of the ``expect_...`` operations and construct the object of type T. E.g.: @@ -219,7 +219,7 @@ E.g.: }; The free functions ``serialize_internal`` and ``deserialize_internal`` must be found with argument-dependent lookup -(ADL). They can be used if no member function should or can be added to the type. See the code in ``memilio/io/io.h`` +(ADL). They can be used if no member function should or can be added to the type. See the code in `memilio/io/io.h `_ for examples where this was done for, e.g., Eigen3 matrices and STL containers. Adding a new format @@ -243,7 +243,7 @@ The command line interface We provide a function ``mio::command_line_interface`` in the header ``memilio/io/cli.h``, that can be used to write to or read from a parameter set. It can take parameters from command line arguments (i.e. the content of ``argv`` in the main function), and assign them to or get them from a ``mio::ParameterSet``. A small example can be seen in -``cpp/examples/cli.cpp``. +`cpp/examples/cli.cpp `_. The command line interface (CLI) provides some non-parameter options listed below. @@ -280,15 +280,15 @@ Working with the History object ------------------------------- The History object provides a way to save data throughout the simulation process. It offers an interface where users can -define the data to be saved from a given object using Loggers and the method of saving it using Writers. Afterward, the -user can access this data from the History object and manipulate it. For a basic Logger use case, refer to -`this example `__. For an example demonstrating using a Logger in the ABM, refer to +define the data to be saved from a given object using Loggers and the method of saving it using ``Writer``\s. Afterward, the +user can access this data from the History object and manipulate it. For a basic ``Logger`` use case, refer to +`this example `__. For an example demonstrating using a ``Logger`` in the ABM, refer to `this example `__. Loggers ~~~~~~~ -The ``Logger`` struct is a tool for logging data from a given object. Each user-implemented Logger must have a ``Type`` +The ``Logger`` struct is a tool for logging data from a given object. Each user-implemented ``Logger`` must have a ``Type`` and implement two functions: ``Type log(const T&)`` and ``bool should_log(const T&)``. The input ``T`` for these functions is the same as the one given to the ``History`` member-function ``History::log``, e.g. ``Model&`` in the ABM. @@ -335,9 +335,9 @@ user-implemented ``Writer`` must have a ``Data`` Type and implement the the Loggers. It is used whenever ``History::log`` is called and ``Logger::should_log`` is true. A predefined universal ``Writer`` called ``DataWriterToMemory`` is already implemented in `history.h `__. -This stores the data from the loggers in a tuple of vectors every time the Logger is called. Another ``Writer`` named +This stores the data from the loggers in a tuple of vectors every time the ``Logger`` is called. Another ``Writer`` named ``TimeSeriesWriter`` can be found in `this file `__, which saves data in a -Timeseries. The according Logger has to have a suitable return type. +Timeseries. The according ``Logger`` has to have a suitable return type. .. code-block:: cpp @@ -354,19 +354,19 @@ Timeseries. The according Logger has to have a suitable return type. History ~~~~~~~ -The ``History`` class manages the Writers and Loggers and provides an interface to log data. It is templated on one -``Writer`` and several suitable and unique ``Loggers``. To use the Writer to log something, the ``History`` provides the -function ``void log(const T& t)`` to call the ``add_record`` function of the ``Writer`` if the Logger function +The ``History`` class manages the ``Writer``\s and Loggers and provides an interface to log data. It is templated on one +``Writer`` and several suitable and unique ``Logger``\s. To use the Writer to log something, the ``History`` provides the +function ``void log(const T& t)`` to call the ``add_record`` function of the ``Writer`` if the ``Logger`` function ``should_log`` returns true. To access the data from the ``History`` class after logging, we provide the function ``get_log`` to access all records. For this, the lifetime of the ``History`` has to be as long as one wants to have access to the data, e.g., a history should not be constructed in the function it is called in when data is needed later. -To access data from a specific Logger, one can use ``std::get`` where x is the position of the Logger in the template +To access data from a specific ``Logger``, one can use ``std::get`` where x is the position of the ``Logger`` in the template argument list of the ``History`` object. Refer to `this example `__ for a simple implementation of a history object and `this full ABM example `__ for a more advanced use case of the History object with several History objects in use. -As mentioned, if multiple Writers have to be used simultaneously, a separate History object is needed for each Writer. +As mentioned, if multiple ``Writer``\s have to be used simultaneously, a separate History object is needed for each Writer. For a use case of this, refer to `the ABM Simulation advance function `__. From 72c6e6a132dd6b533a1abea5c0351c59a5dc0f77 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 25 Jul 2025 11:25:10 +0200 Subject: [PATCH 031/147] CHG: Remove documentation work in progress warning --- docs/source/index.rst | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 1831069186..cf647fdd80 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,11 +12,6 @@ Welcome =============== -.. attention:: - - This documentation is a work in progress. Some areas are already quite detailed, others are still missing completely. - - MEmilio implements various models for infectious disease dynamics, ranging from simple compartmental models to complex Integro-Differential and agent-based models. Its modular design enables the combination of different models with distinct mobility patterns. Through efficient implementation and parallelization, MEmilio delivers cutting-edge and compute-intensive epidemiological models at a large scale, providing precise and high-resolution spatiotemporal infectious disease dynamics. MEmilio is continuously extended and is available open-source for community use. .. image:: https://github.com/user-attachments/assets/65af6012-106e-43c6-9e0e-c96a73aa7b1e @@ -25,9 +20,9 @@ MEmilio implements various models for infectious disease dynamics, ranging from If you use MEmilio, please :doc:`cite our work`. -.. note:: +.. attention:: - This project is under active development. + This work is under active development as is this documentation. Contents ========= From a6d79c5f27521b4cee413b9a21e3c34f3f303bb9 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 25 Jul 2025 11:26:52 +0200 Subject: [PATCH 032/147] CHG: Specify attention note in index.rst --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index cf647fdd80..19935a7b1a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -22,7 +22,7 @@ If you use MEmilio, please :doc:`cite our work`. .. attention:: - This work is under active development as is this documentation. + This framework is under active development, as is this documentation. Contents ========= From 60445f0eb4d06e72870111d37861d84201f1d5f0 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:10:56 +0200 Subject: [PATCH 033/147] Typo --- docs/source/cpp/smm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/smm.rst b/docs/source/cpp/smm.rst index f1892f530f..fe8eb98990 100644 --- a/docs/source/cpp/smm.rst +++ b/docs/source/cpp/smm.rst @@ -36,7 +36,7 @@ Using the infection states Susceptible (S), Exposed (E), Carrier (C), Infected ( Infection state transitions --------------------------- -The infection state transitions are explicitly given by the adoption rates and are therefore subject to user input. Adoption rates always depend on their source infection state. If an adoption event requires interaction of agents (e.g. disease transmission), the corresponding rate depends not only on the source infection state, but also on other infection states, the **Influence**s. An adoption rate that only depends on the source infection state, e.g. recovery or worsening of disease symptoms, is called `first-order` adoption rate and an adoption rate that has influences is called `second-order` adoption rate. Adoption rates are region-dependent; therefore it is possible to have different rates in two regions for the same infection state transition which can be useful when having e.g. region-dependent interventions or contact behavior. +The infection state transitions are explicitly given by the adoption rates and are therefore subject to user input. Adoption rates always depend on their source infection state. If an adoption event requires interaction of agents (e.g. disease transmission), the corresponding rate depends not only on the source infection state, but also on other infection states, the **Influence**\s. An adoption rate that only depends on the source infection state, e.g. recovery or worsening of disease symptoms, is called `first-order` adoption rate and an adoption rate that has influences is called `second-order` adoption rate. Adoption rates are region-dependent; therefore it is possible to have different rates in two regions for the same infection state transition which can be useful when having e.g. region-dependent interventions or contact behavior. Using the infection states from above and two regions, there are five first-order adoption rates per region and one second-order adoption rate per region. In the example below, the second-order adoption rate (transition from S to E) differs between the regions: From 72f7dd8b66e2e2a9917b306c1c41c68430015c24 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:16:29 +0200 Subject: [PATCH 034/147] review comments smm --- cpp/examples/smm.cpp | 1 + docs/source/cpp/smm.rst | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/cpp/examples/smm.cpp b/cpp/examples/smm.cpp index cb63adf39b..1e7f5d285e 100644 --- a/cpp/examples/smm.cpp +++ b/cpp/examples/smm.cpp @@ -19,6 +19,7 @@ */ #include "smm/simulation.h" +#include "smm/model.h" #include "smm/parameters.h" #include "memilio/data/analyze_result.h" #include "memilio/epidemiology/adoption_rate.h" diff --git a/docs/source/cpp/smm.rst b/docs/source/cpp/smm.rst index f1892f530f..b8c707067b 100644 --- a/docs/source/cpp/smm.rst +++ b/docs/source/cpp/smm.rst @@ -87,6 +87,17 @@ The model has the following parameters: - ``TransitionRate`` - Spatial transition rate for infection state i from region k to region l. +The adoption rate :math:`\gamma^{(k)}_{i,j}` at time :math:`t` is given by + +:math:`\gamma^{(k)}_{i,j}(t) = c_{i,j}\frac{i^{(k)}{N}\cdot\sum_{\tau \in \Psi}\tau.factor \cdot \tau.state(t)` + + and the spatial transiton rate at time :math:`t` by + + :math:`\lambda^{(k,l)}_{i} = \lambda^{(k,l)}_{i}.factor\cdot i^{(k)}(t)` + + with :math:`i^{(k)}` the population in region :math:`k` having infection state :math:`i`. + + Initial conditions ------------------ From fa887a0fca4b3210ad4c99cd41a8f99fcf7dd166 Mon Sep 17 00:00:00 2001 From: Anna Wendler <106674756+annawendler@users.noreply.github.com> Date: Mon, 28 Jul 2025 07:46:23 +0200 Subject: [PATCH 035/147] review epidata and surrogate model packages --- docs/source/python/memilio_epidata.rst | 82 +++++++++++++----------- docs/source/python/memilio_surrogate.rst | 16 ++--- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/docs/source/python/memilio_epidata.rst b/docs/source/python/memilio_epidata.rst index ca8874f22d..fc38c5f3f1 100644 --- a/docs/source/python/memilio_epidata.rst +++ b/docs/source/python/memilio_epidata.rst @@ -1,8 +1,7 @@ MEmilio Epidata Package ======================= - -MEmilio Epidata package provides modules and scripts to download epidemiological data from various official and inofficial sources. +MEmilio Epidata provides modules and scripts to download epidemiological data from various official and inofficial sources. The package as well as links to the sources can be found in the `pycode/memilio-epidata `_. Installation @@ -47,20 +46,25 @@ Usage After installation the following functions are available: -* ``getcasedata``: Downloads SARS-CoV-2 case data from Robert Koch-Institut (RKI-C). -* ``getpopuldata``: Downloads population data for German federal states and counties from various public sources (P). -* ``getjhdata``: Downloads COVID-19 case data from John Hopkins University (JH). -* ``getdividata``: Downloads ICU data from German DIVI Intensivregister (DIVI). -* ``getsimdata``: Downloads all data required for a simulation with the graph-metapopulation model which are SARS-CoV-2 case data(RKI-C), population data (P), ICU data (DIVI) and COVID-19 vaccination data from Robert Koch-Institut (RKI-V). -* ``getcommutermobility``: Downloads data about commuter mobility from German Federal Employment Agency (BAA). -* ``gettestingdata``: Downloads data about SARS-CoV-2 PCR tests from Robert Koch-Institut (RKI-T). -* ``gethospitalizationdata``: Downloads data about COVID-19 hospitalizations data from Robert Koch-Institut (RKI-H) -* ``cleandata``: Deletes all data files generated by the MEmilio Epidata package. +* ``clean_data``: Deletes all data files generated by the MEmilio Epidata package. +* ``get_case_data``: Downloads SARS-CoV-2 case data from Robert Koch-Institut (RKI-C). +* ``get_commuter_data``: Computes DataFrame of commuter mobility patterns based on the Federal Agency of Work data. +* ``get_divi_data``: Downloads ICU data from German DIVI Intensivregister (DIVI). +* ``get_hospitalization_data``: Downloads data about COVID-19 hospitalizations data from Robert Koch-Institut (RKI-H). +* ``get_jh_data``: Downloads COVID-19 case data from John Hopkins University (JH). +* ``get_npi_data``: Loads a certain resolution of recorded NPI data from the Corona Datenplattform and extracts the counties asked for and activates the NPIs if they are incidence dependent. +* ``get_population_data``: Downloads population data for German federal states and counties from various public sources (P). +* ``get_simulation_data``: Downloads all data required for a simulation with the graph-metapopulation model which are SARS-CoV-2 case data(RKI-C), population data (P), ICU data (DIVI) and COVID-19 vaccination data from Robert Koch-Institut (RKI-V). +* ``get_testing_data``: Downloads data about SARS-CoV-2 PCR tests from Robert Koch-Institut (RKI-T). +* ``get_vaccination_data``: Downloads the RKI vaccination data and provides different kind of structured data. +* ``updateMobility2022``: Merges rows and columns of Eisenach to Wartburgkreis which has become one single county by July 2021. +* ``createFederalStatesMobility``: Creates mobility matrices for German federal states based on county mobility. +* ``transformWeatherData``: Transforms weather data. For a detailed description of the run options and the resulting data files written see the `epidata subfolder `_. -The downloaded data is written either to hdf5 or json files. +The downloaded data is written either to HDF5 or json files. Testing and Coverage -------------------- @@ -125,26 +129,26 @@ If a new functionality shall be added please stick to the following instructions When you start creating a new script: -- have a look into getDataIntoPandasDataFrame.py. There the main functionality which should be used is implemented. +- Have a look into getDataIntoPandasDataFrame.py. There the main functionality which should be used is implemented. - get_file is used to read in data. - - the Conf class sets relevant download options. - - use write_dataframe to write the pandas dataframe to file. - - use check_dir if you want to create a new folder to write data to -- use the dictionaries in defaultDict.py to rename the existing columns of your data - - add new column names to one of the existing language dictionaries; english, german and spanish translation exists at the moment. - - for non-english languages always use the EngEng dictionary as the key, thus we can easily change names with just changing one line. - - in defaultDict.py a dictionary with id, state and county name, respectively exists. Please use it. + - The Conf class sets relevant download options. + - Use write_dataframe to write the pandas dataframe to file. + - Use check_dir if you want to create a new folder to write data to +- Use the dictionaries in defaultDict.py to rename the existing columns of your data. + - Add new column names to one of the existing language dictionaries; English, German and Spanish translations exists at the moment. + - For non-english languages always use the EngEng dictionary as the key, thus we can easily change names with just changing one line. + - In defaultDict.py a dictionary with id, state and county name, respectively exists. Please use it. - After renaming columns, you should not use pandas dataframe.column but instead use dataframe[column] where column is given by the dictionaries in defaultDict.py. Example: ID_County = dd.GerEng['IdLandkreis'] or dd.EngEng['idCounty']. -- For extensive operations use the progress indicator to give feedback for the user +- For extensive operations use the progress indicator to give feedback for the user. - ALWAYS use Copy-on-Write for pandas DataFrames. -- use doxygen like comments in code as - - add description in the beginning of the file +- Use doxygen like comments in code as folows: + - Add description in the beginning of the file: - ## Header - # @brief name descr - # longer description - - add description in the beginning of every function directly after the definition + - Add description in the beginning of every function directly after the definition: - start and end with """ - add a short description to first line - afterwards add a longer description @@ -153,28 +157,30 @@ When you start creating a new script: When you add a new script -- add a executable to the setup.py in "pycode/memilio-epidata" -- add it to the cli_dict in getDataIntoPandasDataFrame.py - - add a meaningfull key for the new script - - as the value add a list in the form [comment to print when script is started, list of used parser arguments (optional)] - - if more than the default parser should be added, add these parser to the list of used parser -- add tests -- add an entry "executablename -h" to the .github/test-py/action.yml -- add an entry "executablename -o data_dl" to the .github/workflows/main.yml -- add generated data to cleanData +- Add an executable to the setup.py in "pycode/memilio-epidata". +- Add it to the cli_dict in getDataIntoPandasDataFrame.py. + - Add a meaningful key for the new script. + - as the value add a list in the form [comment to print when script is started, list of used parser arguments (optional)]. + - If more than the default parser should be added, add these parser to the list of used parser. +- Add tests. +- Add an entry "executablename -h" to the .github/test-py/action.yml. +- Add an entry "executablename -o data_dl" to the .github/workflows/main.yml. +- Add generated data to cleanData. Adding a new parser: -- add default value to defaultDict in defaultDict.py -- add to cli_dict in getDataIntoPandasDataFrame.py which scripts use this parser -- add an if 'new parser' in what_list and add parser.add_argument() +- Add default value to defaultDict in defaultDict.py. +- Add to cli_dict in getDataIntoPandasDataFrame.py which scripts use this parser. +- Add an if 'new parser' in what_list and add parser.add_argument(). General -- Always add unittests +- Always add unittests. - Check test coverage report, if every new feature is covered. - Check the pylint report just comments with "refactor" are allowed. Troubleshooting --------------- -- HDF5 errors during installation (mostly on Windows): one of the dependencies of the epidata package requires HDF5 to be installed on the system. If HDF5 is not discovered properly, this `stack overflow thread `_ may help resolve the issue. +- HDF5 errors during installation (mostly on Windows): one of the dependencies of the epidata package requires HDF5 to +be installed on the system. If HDF5 is not discovered properly, this `stack overflow thread `_ +may help resolve the issue. diff --git a/docs/source/python/memilio_surrogate.rst b/docs/source/python/memilio_surrogate.rst index 354f230dc2..832635cc1f 100644 --- a/docs/source/python/memilio_surrogate.rst +++ b/docs/source/python/memilio_surrogate.rst @@ -31,8 +31,8 @@ The package currently provides the following modules: - `models`: models for different tasks Currently we have the following models: - - `ode_secir_simple`: A simple model allowing for asymptomatic as well as symptomatic infection not stratified by age groups. - - `ode_secir_groups`: A model allowing for asymptomatic as well as symptomatic infection stratified by age groups and including one damping. + - `ode_secir_simple`: A simple model allowing for asymptomatic as well as symptomatic infection states not stratified by age groups. + - `ode_secir_groups`: A model allowing for asymptomatic as well as symptomatic infection states stratified by age groups and including one damping. Each model folder contains the following files: - `data_generation`: Data generation from expert model simulation outputs. @@ -42,7 +42,7 @@ The package currently provides the following modules: - `hyperparameter_tuning`: Scripts for tuning model hyperparameters. -- `tests`: this file contains all tests +- `tests`: This file contains all tests. ODE-SECIR Simple Model ----------------- @@ -80,11 +80,11 @@ The `data_generation.py` module provides functionality to generate training data The data generation process can be summarized as follows: -1. Randomly initializes the model parameters and initial compartment populations -2. Runs the ODE-SECIR simulation using the C++ backend via Python bindings -3. Applies logarithmic normalization to improve training stability -4. Splits each time series into input and label segments -5. Saves the dataset as a pickle file for later use +1. Randomly initializes the model parameters and initial compartment populations. +2. Runs the ODE-SECIR simulation using the C++ backend via Python bindings. +3. Applies logarithmic normalization to improve training stability. +4. Splits each time series into input and label segments. +5. Saves the dataset as a pickle file for later use. Network Architectures ~~~~~~~~~~~~~~~~~~~~ From 3f5b14aecab59b6fcc9d5d46392f4506115a93b4 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:01:35 +0200 Subject: [PATCH 036/147] epidata docu review --- docs/source/python/memilio_epidata.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/python/memilio_epidata.rst b/docs/source/python/memilio_epidata.rst index ca8874f22d..d4b35edb09 100644 --- a/docs/source/python/memilio_epidata.rst +++ b/docs/source/python/memilio_epidata.rst @@ -2,7 +2,7 @@ MEmilio Epidata Package ======================= -MEmilio Epidata package provides modules and scripts to download epidemiological data from various official and inofficial sources. +MEmilio Epidata package provides modules and scripts to download epidemiological data from various different sources. The package as well as links to the sources can be found in the `pycode/memilio-epidata `_. Installation @@ -156,7 +156,7 @@ When you add a new script - add a executable to the setup.py in "pycode/memilio-epidata" - add it to the cli_dict in getDataIntoPandasDataFrame.py - add a meaningfull key for the new script - - as the value add a list in the form [comment to print when script is started, list of used parser arguments (optional)] + - for the dict value add a list in the form [comment to print when script is started, list of used parser arguments (optional)] - if more than the default parser should be added, add these parser to the list of used parser - add tests - add an entry "executablename -h" to the .github/test-py/action.yml From 43194acb49375e157a1297eb21dc4dfcf8054fd6 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:18:35 +0200 Subject: [PATCH 037/147] personal page henrik --- docs/source/team.rst | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/source/team.rst b/docs/source/team.rst index 367d9aef3c..3613dbd468 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -10,28 +10,29 @@ Core Developers .. _developer_1: -Max Mustermann +Henrik Zunker ^^^^^^^^^^^^^^^^^^ .. image:: _static/team/max_mustermann.jpg - :alt: Max Mustermann + :alt: Henrik Zunker :width: 150px :align: left :class: developer-photo -**Research Focus:** mathematical modeling of infectious diseases, high-performance computing +**Research Focus:** High-Performance Computing, Mathematical Modeling, Scientific Computing, Ordinary Differential Equations (ODEs) -Max Mustermann is a Phd student ... +Henrik Zunker is a PhD student at the Institute for Software Technology at the German Aerospace Center (DLR) since 2022. He is working on the development of MEmilio, focusing on equation-based models. In addition, he is involved in the development of machine learning models acting as surrogate models using various techniques (such as graph neural networks). **Selected Publications:** -* 1 -* 2 +* **Zunker H**, Schmieding R, Kerkmann D, Schengen A, Diexer S, et al. (2024). *Novel travel time aware metapopulation models and multi-layer waning immunity for late-phase epidemic and endemic scenarios*. *PLOS Computational Biology* 20(12): e1012630. https://doi.org/10.1371/journal.pcbi.1012630 +* **Zunker H**, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models. Chaos, Solitons & Fractals. https://doi.org/10.1016/j.chaos.2025.116782 +* Schmidt A, **Zunker H**, Heinlein A, Kühn MJ. (2024). Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response. arXiv. https://arxiv.org/abs/2411.06500 **Links:** -* `Google Scholar Profile `_ -* `ORCID: 0000-0000-0000-0000 `_ +* `Google Scholar Profile `_ +* `ORCID: 0000-0002-9825-365X `_ ---- @@ -51,7 +52,9 @@ Participating research groups MEmilio is developed in collaboration with various research institutions and partners: -* **123** - Institue 1 +* **German Aerospace Center (DLR)** - Institute for Software Technology +* **University of Bonn** - Life and Medical Sciences Institute and Bonn Center for Mathematical Life Sciences +* TBC .. _acknowledgments: @@ -59,4 +62,10 @@ MEmilio is developed in collaboration with various research institutions and par Acknowledgments --------------- -TBD +MEmilio is supported by the Initiative and Networking Fund of +the Helmholtz Association, Germany (grant agreement number KA1-Co- +08, Project LOKI-Pandemics). + +Memilio was supported by the German Federal Ministry for +Digital and Transport, Germany under grant agreement FKZ19F2211A +(Project PANDEMOS). From 33d7864cce4ac5a5f57344c063ff4d79850b68a5 Mon Sep 17 00:00:00 2001 From: Anna Wendler <106674756+annawendler@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:21:49 +0200 Subject: [PATCH 038/147] review smm --- docs/source/cpp/smm.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/cpp/smm.rst b/docs/source/cpp/smm.rst index bf396f152d..58e1cc9895 100644 --- a/docs/source/cpp/smm.rst +++ b/docs/source/cpp/smm.rst @@ -5,7 +5,7 @@ The stochastic metapopulation model uses a Markov process to simulate disease dy :math:`\partial_t p(X,Z;t) = G p(X,Z;t) + L p(X,Z;t)`. -The operator :math:`G` defines the infection state adoptions and only acts on :math:`Z` the vector containing all subpopulations stratified by infection state. :math:`L` defines location changes, only acting on :math:`X` the vector containing all subpopulations stratified by region. Infection state adoptions are modeled as stochastic jumps with independent Poisson processes given by adoption rate functions. Similar to the infection state dynamics, spatial transitions between regions are also modeled as stochastic jumps with independent Poisson processes given by transition rate functions. Gillespie's direct methode (stochastic simulation algorithm) is used for simulation. +The operator :math:`G` defines the infection state adoptions and only acts on :math:`Z`, the vector containing all subpopulations stratified by infection state. :math:`L` defines location changes, only acting on :math:`X`, the vector containing all subpopulations stratified by region. Infection state adoptions are modeled as stochastic jumps with independent Poisson processes given by adoption rate functions. Similar to the infection state dynamics, spatial transitions between regions are also modeled as stochastic jumps with independent Poisson processes given by transition rate functions. Gillespie's direct method (stochastic simulation algorithm) is used for simulation. Infection states @@ -91,11 +91,11 @@ The adoption rate :math:`\gamma^{(k)}_{i,j}` at time :math:`t` is given by :math:`\gamma^{(k)}_{i,j}(t) = c_{i,j}\frac{i^{(k)}{N}\cdot\sum_{\tau \in \Psi}\tau.factor \cdot \tau.state(t)` - and the spatial transiton rate at time :math:`t` by +and the spatial transition rate at time :math:`t` by :math:`\lambda^{(k,l)}_{i} = \lambda^{(k,l)}_{i}.factor\cdot i^{(k)}(t)` - with :math:`i^{(k)}` the population in region :math:`k` having infection state :math:`i`. +with :math:`i^{(k)}` the population in region :math:`k` having infection state :math:`i`. Initial conditions @@ -140,15 +140,15 @@ As the spatial transition rates are dependent on infection state, region changes //Initialize model parameter model.parameters.get>() = transition_rates; -Non-pharmaceutical Interventions +Nonpharmaceutical interventions -------------------------------- -There are no non-pharmaceutical interventions (NPIs) explicitly implemented in the model. However, NPIs influencing the adoption or spatial transition rates can be realized by resetting the corresponding model parameters. +There are no nonpharmaceutical interventions (NPIs) explicitly implemented in the model. However, NPIs influencing the adoption or spatial transition rates can be realized by resetting the corresponding model parameters. Simulation ---------- -At the beginning of the simulation, the waiting times for all events (infection state adoptions and spatial transitions) are drawn. Then the time is advanced until the time point of the next event - which can be a spatial transition or an infection state adoption - and the event takes places. The waiting times of the other events are updated and a new waiting time for the event that just happend is drawn. The simulation saves the system state in discrete time steps. +At the beginning of the simulation, the waiting times for all events (infection state adoptions and spatial transitions) are drawn. Then the time is advanced until the time point of the next event - which can be a spatial transition or an infection state adoption - and the event takes places. The waiting times of the other events are updated and a new waiting time for the event that just happened is drawn. The simulation saves the system state in discrete time steps. To simulate the model from `t0` to `tmax` with given step size `dt`, a **Simulation** has to be created and advanced until `tmax`. The step size is only used to regularly save the system state during the simulation. From dd9dc95dd777f431346f48ccb07163ddb843bb08 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:36:26 +0200 Subject: [PATCH 039/147] memilio simulations repo --- README.md | 6 ++++++ docs/source/getting_started.rst | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index ad4d164be7..582ec42595 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ and, in particular, for The documentation for MEmilio can be found at https://memilio.readthedocs.io/en/latest/index.html +**Publication simulations** + +Simulations used for publications, along with their specific plotting and processing scripts, +are available in a separate repository: +https://github.com/SciCompMod/memilio-simulations + **Development** The coding guidelines and git workflow description can be found in the documentation at diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index cce7cf2fa5..ad49e4e799 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -183,6 +183,16 @@ Out of the box this works for all examples in the ``cpp/examples`` folder of our that do not depend on user-provided external libraries. Additional explanations for our models are linked at the corresponding sites of this documentation. +Simulations used in publications +******************************** +For simulations used in publications, we maintain a separate repository: +`memilio-simulations `_. +This repository contains simulations organized in separate folders, each with the specific version of MEmilio +used for the published results. This ensures that simulation results can be easily reproduced. + +The repository also includes additional scripts for plotting, data gathering, and pre-/post-processing +that were used in publications. + Loading data ~~~~~~~~~~~~~~~~~~~~~ From 395994a60a6504aea8f17673400c734ede2a6125 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:37:27 +0200 Subject: [PATCH 040/147] CHG: update reference to publication (ABM) --- docs/source/cpp/mobility_based_abm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/mobility_based_abm.rst b/docs/source/cpp/mobility_based_abm.rst index 23e21e734f..d669a1d994 100644 --- a/docs/source/cpp/mobility_based_abm.rst +++ b/docs/source/cpp/mobility_based_abm.rst @@ -5,7 +5,7 @@ This module models and simulates the epidemic using an agent-based model (*ABM*) the spread of COVID-19 in a population with discrete persons (the agents) moving throughout locations in the model and interacting with (infecting) each other. For a detailed overview of the ABM, see: -- Kerkmann D, Korf S, et al. Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread. https://doi.org/10.48550/arXiv.2410.08050 +- Kerkmann D, Korf S, et al. Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread. `DOI:10.1016/j.compbiomed.2025.110269 `_ Introduction ----------- From 6adc12d79ce5d5bb04c5e177ab35117c2d4b17be Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:38:52 +0200 Subject: [PATCH 041/147] CHG: update ABM citation --- docs/source/cpp/mobility_based_abm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/mobility_based_abm.rst b/docs/source/cpp/mobility_based_abm.rst index d669a1d994..9c7a16a315 100644 --- a/docs/source/cpp/mobility_based_abm.rst +++ b/docs/source/cpp/mobility_based_abm.rst @@ -5,7 +5,7 @@ This module models and simulates the epidemic using an agent-based model (*ABM*) the spread of COVID-19 in a population with discrete persons (the agents) moving throughout locations in the model and interacting with (infecting) each other. For a detailed overview of the ABM, see: -- Kerkmann D, Korf S, et al. Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread. `DOI:10.1016/j.compbiomed.2025.110269 `_ +- Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, et al. (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193: 110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ Introduction ----------- From c103f6d88e00460e78282ab7cadd079d28a5a4b4 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:45:22 +0200 Subject: [PATCH 042/147] data types docu --- docs/source/cpp/data_types.rst | 35 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index 6f381b7114..316b1c582c 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -1,22 +1,23 @@ Common data types ----------------- -Here we want to explain the intention behind the non-standard data types that are used throughout MEmilio. +The follwing list expalins the non-standard data types that are used throughout MEmilio. -FP -~~ +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 -The template :code:`FP` and the derived types like :code:`UncertainValue` in these examples are commonly used throughout MEmilio. -:code:`FP` is a floating point type, usually :code:`double`. An :code:`UncertainValue` holds a value of type -:code:`FP` as well as (optionally) a distribution to sample new values from, e.g. for a parameter study. - -.. dropdown:: :fa:`gears` Expert's settings - - The type ``mio::AgeGroup`` is a typesafe ``size_t``, meaning an integer that cannot be confused with other integer - types. So assignment, addition, etc. only works with another ``mio::AgeGroup``, not ``size_t`` or another integer - type. This is useful for function interfaces or indexing, as it makes it (nearly) impossible to mix up, e.g., age - groups with infection states. Check out ``mio::Index`` if you want to learn more. - - The type ``mio::Populations`` is an extension of a ``mio::CustomIndexArray``, which is a template type that manages - a flat array. Its main purpose is to allow multidimensional indexing into this array, using typesafe indices like - a ``mio::Index`` or a ``enum class``. \ No newline at end of file + * - Data type name + - Description + * - :code:`FP` + - A floating point type. Usually :code:`double` is used, but for instane in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see models/oseair and `examples/ode_seair_optimization.cpp `_. + * - :code:`UncertainValue` + - This data type describes a value sampled from a given distribution. The value is intialized with a given :code:`FP` and can be (re)sampled with the :code:`draw_sample()` function. + * - :code:`AgeGroup` + - A typesafe ``size_t``, i.e. an integer that cannot be confused with other integer types so operations like assignment, addition etc. only work with other :code:`AgeGroup`s. + * - :code:`Region` + - A typesafe ``size_t``. + * - :code:`CustomIndexArray` + - An array whose values can be accesses by a multi-index. This datatype is for example used in the parameter :code:`mio::abm::TimeExposedToNoSymptoms` making it dependent on :code:`mio::abm::VirusVariant` and :code:`mio::AgeGroup`. Its values can then be set for a specific :code:`virus_variant` and :code:`age_group` using :code`model.parameters.template get()[{virus_variant, age_group}]`. + * - :code:`Populations` + - Is a :code:`mio::CustomIndexArray` with :code:`mio::UncertainValue` as values. From be1350eaf3d1a1aaa0ec2eebd1524649dd579ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Mon, 28 Jul 2025 10:54:09 +0200 Subject: [PATCH 043/147] model creation --- docs/source/cpp/model_creation.rst | 2 +- docs/source/cpp/structure.rst | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/source/cpp/model_creation.rst b/docs/source/cpp/model_creation.rst index 5d4f6e1242..72eb38dca6 100644 --- a/docs/source/cpp/model_creation.rst +++ b/docs/source/cpp/model_creation.rst @@ -1,7 +1,7 @@ Model Creation ============== -Here we collect information on how to create models. +While MEmilio already preimplements many different models, it is possible to create new models. This section describes how to create a new model in MEmilio. All of MEmilio's models have been designed to share a maximum of structure and functionality, however, the creation of a new aggregated model differs fundamentally from the creation of a new inidividual-based model. New metapopulation models are generally created by implementing an aggregated model in a graph structure. .. toctree:: :maxdepth: 1 diff --git a/docs/source/cpp/structure.rst b/docs/source/cpp/structure.rst index a6105df67f..6f00c802a2 100644 --- a/docs/source/cpp/structure.rst +++ b/docs/source/cpp/structure.rst @@ -1,19 +1,17 @@ Structure ========= -All aggregated models follow the same overall structure. To create a new model, please proceed as follows. - In ``cpp/models``, create a new folder for your model. This folder will contain the following model specific files that are described below. - ``CMakeLists.txt``: Defines your model specific library, links libraries, specifies include directories and adds compile options for your library. Note that you have to add your model specific library to the file ``cpp/CMakeLists.txt``. - ``README.md``: Provides an overview of your model. - ``infection_state.h``: Defines an ``enum`` that specifies the infection states of your model. -- ``model.h``/ ``model.cpp``: Depending on the model, the model equations are specified or a model specific solver is implemented. +- ``model.h``/ ``model.cpp``: Depending on the model, these files define the overall functionality of your model and, in case, of an aggregated model, the equations defining the flows between different infection states. - ``parameters.h``: Defines all parameters used in the model. -Depending on your model, it may make sense to create additional files for functionality that is specific to your model. The following list gives examples from already existing models but is not exhaustive. +Depending on your model, it may make sense to create additional files for functionality that is specific to your model. For aggregated models, the following list gives examples from already existing models but is not exhaustive. - ``parameters_io.h``/ ``parameters_io.cpp``: Implements a scheme to initialize the model based on reported data. - ``simulation.h``/ ``simulation.cpp``: If your model does not derive from ``CompartmentalModel`` or ``FlowModel``, you need to provide a ``Simulation`` class that determines how your model equations are solved. -For further information on how to create a new model, see the respective sections for the different types of equation-based models. \ No newline at end of file +For further information on how to create a new aggregated model, see the respective sections :doc:`above this structure <../model_creation>`. The creation of an individual-based model is much more complex and we suggest to build upon ``cpp/models/abm``. \ No newline at end of file From 7cd18984f4327c00d68389310e9d6ffbb92da67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Mon, 28 Jul 2025 11:14:56 +0200 Subject: [PATCH 044/147] ref --- docs/source/references.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/references.rst b/docs/source/references.rst index eb19c550a0..2f9172e6d3 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -6,7 +6,7 @@ The following gives an overview on (peer-reviewed) publications extending or usi Recently Submitted Publications -------------------------------------- -- Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024). *Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ +- Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. arXiv. `arXiv:2411.06500 `_ - Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `arXiv:2408.12228 `_ - Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ From cee65f228ed414fa5bc7b4b967f73bf2095b8891 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:23:09 +0200 Subject: [PATCH 045/147] CHG: grammar, links and typos in ABM corrected --- docs/source/cpp/mobility_based_abm.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/source/cpp/mobility_based_abm.rst b/docs/source/cpp/mobility_based_abm.rst index 9c7a16a315..e1770038cc 100644 --- a/docs/source/cpp/mobility_based_abm.rst +++ b/docs/source/cpp/mobility_based_abm.rst @@ -10,7 +10,7 @@ model and interacting with (infecting) each other. For a detailed overview of th Introduction ----------- -The model is implemented in multiple classes and headers located in the ``/cpp/models/abm/`` directory. The core classes and their locations are: +The model is implemented in multiple classes and header files located in the ``/cpp/models/abm/`` directory. The core classes and their locations are: - ``Person`` (person.h): Represents individual agents in the simulation - ``Infection`` (infection.h): Manages infection dynamics and disease progression @@ -122,7 +122,7 @@ Simulation ---------- The simulation runs in discrete time steps. Each step has two phases, an **interaction phase** and a **mobility phase**. -After these two phases the disease can progress and the simulation time is increased by one step. +After these two phases, the disease can progress and the simulation time is increased by one step. Interaction phase ~~~~~~~~~~~~~~~~~~~ @@ -135,7 +135,7 @@ level of the susceptible person. Mobility phase ~~~~~~~~~~~~~~~~~~ -During the mobility phase, each person may change their location. Mobility follow +During the mobility phase, each person may change their location. Mobility follows `rules `_, considering the current location, time of day, and properties of the person (e.g. age). Some location changes are deterministic and regular (e.g. going to work), while others are random (e.g. going shopping or to a social event in the evening/on the weekend). When agents are infected, they are quarantined and cannot change their location. @@ -150,9 +150,8 @@ How to ----------- This section gives an introduction to how to use the ABM and set up your own simulation. For a quick overview, you can find a full -example in the `ABM minimal example `_ and a more detailed Doxygen documentation -`here `_. For a guide on installation and running the simulations and -examples, see this `README `_. +example in the `ABM minimal example `_. For a guide on installation and running the simulations and +examples, see :doc:`installation`. Every person in the ABM belongs to an AgeGroup, which we can define as follows: @@ -257,7 +256,7 @@ During the simulation, people can get tested, and we have to specify the scheme Initializing infections ~~~~~~~~~~~~~~~~~~~~~~ -For some infections to happen during the simulation, we have to initialize people with infections: +For infections to happen during the simulation, we have to initialize people with infections: .. code-block:: cpp @@ -289,7 +288,7 @@ Finally, we run the simulation: sim.advance(tmax); Alternatively, if we want to track things in the simulation, we need to set up a -`history `_, for example, to track all the Infection states of each simulation step. +:doc:`history`, for example, to track all the Infection states of each simulation step. .. code-block:: cpp From c0c949f2a9a4a10f4c8d99a8606dbbc70a5c523c Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:25:18 +0200 Subject: [PATCH 046/147] Review performance monitoring --- docs/source/cpp/performance_monitoring.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/cpp/performance_monitoring.rst b/docs/source/cpp/performance_monitoring.rst index 7959a02b56..27508f639d 100644 --- a/docs/source/cpp/performance_monitoring.rst +++ b/docs/source/cpp/performance_monitoring.rst @@ -38,20 +38,20 @@ Timer usage In this section we present how to use the AutoTimer class. This is the preferred way of using the timing framework, as the class takes care of running the timer, managing its lifetime for later evaluation, and ensuring thread safety with -OpenMP. +OpenMP. The AutoTimer class uses some other classes that are listed and explained in :ref:`Classes and their responsibilities`. An AutoTimer starts when it is created, and stops when it is destroyed - which usually happens at the next closing bracket :code:`}` or the next :code:`return`. This design, automating the starting and stopping of a timer, is intentionally limiting, because it helps to avoid several issues or mistakes that can arise when manually running timers. -You can also try out the `code example `__. +An example on how to use the timers can be found at `code example `__. Timing in executables ^^^^^^^^^^^^^^^^^^^^^ -Let's say you have set up a Simulation, and want to measure how long it takes, without the setup. Then you can write +To measure how long advancing a simulation without the setup takes, you can write .. code-block:: cpp @@ -68,8 +68,7 @@ Let's say you have set up a Simulation, and want to measure how long it takes, w ... // evaluate results } -and will see a table printed at the end of your program, that lists the time it took to :code:`advance` next to the -timer named "my simulation". That's it! +and will see a table printed at the end of your program next to the timer named "my simulation", that lists the time it took to :code:`advance`. You can add more timers like this, but make sure you use unique names, otherwise the same timer will be reused, and the measured times will be added together. The name of the timer object itself (here :code:`my_timer`) is not important, as @@ -158,6 +157,7 @@ example, NamedTimer (the class used by AutoTimer) cannot be instantiated dynamic be known at compile time. This also means that adding a lot of timers will impact the time it takes to compile the code, though a couple hundred timers should only take around an additional second. +.. _Classes and their responsibilities: Classes and their responsibilities ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -166,14 +166,14 @@ specific component, view its API documentation. - **BasicTimer**: The foundation of the timing framework. BasicTimer is a very simple class, that defines the methods start, stop, - reset, and get_elapsed_time. These are used by all other classes in this framework. Uses a wall clock, so if compute + reset, and get_elapsed_time. These are used by all other classes in this framework. It uses a wall clock, so if compute resources are shared with other tasks, the timing results may be higher than expected. In debug builds, it will log errors whenever a member function was used incorrectly, e.g. when start was called twice. - **TimerRegistration**: This simple struct is used to keep track of timers and some additional information, but does not manage their storage. It consists of two strings for name and scope, a reference to a BasicTimer, and a thread id. The thread id specifies - which thread the timer is used in, which could differ from the thread is is created by. + which thread the timer is used in, which could differ from the thread it is created by. - **Printer**: A pure virtual class defining a print method to evaluate and output timing results via a list of TimerRegistrations. From 661c31d077fe5ed14705cd5c465de803b701b8ac Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:27:11 +0200 Subject: [PATCH 047/147] CHG: Make link in ABM to history specific --- docs/source/cpp/mobility_based_abm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/mobility_based_abm.rst b/docs/source/cpp/mobility_based_abm.rst index e1770038cc..8fd53d15fa 100644 --- a/docs/source/cpp/mobility_based_abm.rst +++ b/docs/source/cpp/mobility_based_abm.rst @@ -288,7 +288,7 @@ Finally, we run the simulation: sim.advance(tmax); Alternatively, if we want to track things in the simulation, we need to set up a -:doc:`history`, for example, to track all the Infection states of each simulation step. +:ref:`history`, for example, to track all the Infection states of each simulation step. .. code-block:: cpp From d89a507340907f24293562c11b3e854102e9b417 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:33:07 +0200 Subject: [PATCH 048/147] CHG; missing word added in graph ABM --- docs/source/cpp/graph_abm.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/source/cpp/graph_abm.rst b/docs/source/cpp/graph_abm.rst index 99edb6d657..9e1973fb45 100644 --- a/docs/source/cpp/graph_abm.rst +++ b/docs/source/cpp/graph_abm.rst @@ -4,7 +4,11 @@ Graph-based agent-based model Introduction ------------- -This model realizes multiple instances of the mobility-based agent-based model ``abm::Model`` (see :doc:`mobility_based_abm` for documentation) as nodes in a directed graph. One local model represents a geographical region. The regions are connected by the graph edges. Mobility within one node and via the graph edges follows the same mobility rules that can be handed as argument to ``mio::GraphABModel``. Therefore this graph-based agent-based model can be reduced to a single mobility-based agent-based if simulation time steps within the whole graph, i.e. the step size of each node and the step size of the edge exchange, are equal. +This model realizes multiple instances of the mobility-based agent-based model ``abm::Model`` (see :doc:`mobility_based_abm` for the documentation) +as nodes in a directed graph. One local model represents a geographical region. The regions are connected by the graph edges. Mobility within one node +and via the graph edges follows the same mobility rules that can be handed as argument to ``mio::GraphABModel``. Therefore this graph-based agent-based +model can be reduced to a single mobility-based agent-based model if simulation time steps within the whole graph, i.e. the step size of each node and the +step size of the edge exchange, are equal. Simulation ----------- From 33c5e67ae895a1cf8c6852bcc372eeb518b3e820 Mon Sep 17 00:00:00 2001 From: Anna Wendler <106674756+annawendler@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:33:19 +0200 Subject: [PATCH 049/147] some improvements for lct model creation --- docs/source/cpp/lct_creation.rst | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/source/cpp/lct_creation.rst b/docs/source/cpp/lct_creation.rst index 4013760d0d..1e17d7fb91 100644 --- a/docs/source/cpp/lct_creation.rst +++ b/docs/source/cpp/lct_creation.rst @@ -44,7 +44,7 @@ compartments starting from 0, with Count being equal to the number of compartmen Parameters ~~~~~~~~~~ -Next, we define the parameters in "parameters.h", which consist of a struct for each constant used in the mathematical +Next, we define the parameters in "parameters.h", which consist of a struct for each parameter used in the mathematical model. This struct must define the data type, name and default value of the constant. For example, for the time a person stays infectious, :math:`T_I`, we define a struct: @@ -87,9 +87,6 @@ We also define a parameter ``ContactPatterns`` determining the contacts of the d Avoid using the mathematical symbol of a constant as name for the struct. Their connection can be noted down in the documentation of these structs. -Additionally, we can determine parameters influencing the infection dynamics. In the case of the LCT-SECIR model we use -the parameters ``TransmissionProbabilityOnContact``, ``RelativeTransmissionNoSymptoms``, ``RiskOfInfectionFromSymptomatic``, ``StartDay`` and ``Seasonality``. -For each parameter, we need to define a default value and a name as for the above parameters. Finally, define a type :code:`Parameters` by listing all parameter structs as template arguments of a :code:`mio::ParameterSet`: @@ -113,9 +110,9 @@ it using the class ``LctPopulations``. .. code-block:: cpp template - using Population = mio::LctPopulations; + using Populations = mio::LctPopulations; -where ``LctStates`` contains the number of subcompartments per infection state. +where ``LctStates`` contains the number of subcompartments per infection state. Importantly, this class allows further stratifying the population vector, with the most common example being adding age groups. @@ -123,7 +120,7 @@ example being adding age groups. Define the model ^^^^^^^^^^^^^^^^ -Now we can define the model as a **CompartmentalModel** in the file model.h: +Now we can define the model as a **CompartmentalModel** in the file "model.h": .. code-block:: cpp @@ -158,12 +155,17 @@ For LCT models, the class ``CompartmentalModel`` requires the following template - type of floating point type, here ``ScalarType``, - a class ``InfectionState`` containing the compartments, see above, -- the class ``LctPopulations`` which is a class template for compartment populations of LCT models depending on the floating point type and the considered ``LctStates``, +- the class ``LctPopulations`` which is a class template for compartment populations of LCT models depending on the +floating point type and the considered ``LctStates`` and determines the type of the public member ``populations`` which contains +the number of individuals per subcompartment and is used to pass initial conditions to the model, - the class ``Parameters`` containing all required parameters, see above. The function ``get_derivatives()`` evaluates the right-hand-side of the ODE :math:`dydt = f(y, t)` that we want to solve, see above. It is also useful to implement the following methods within the model: -- A function ``calculate_compartments()`` that accumulates the TimeSeries containing simulation results that are divided into subcompartments to a TimeSeries that conatins the simulation results according to the infection states without subcompartments. +- A function ``calculate_compartments()`` that accumulates the TimeSeries containing simulation results that are divided +into subcompartments to a TimeSeries that conatins the simulation results according to the infection states without subcompartments. +For an example, see the implementation within the LCT-SECIR model. - A function ``check_constraints()`` that checks that the model satisfies sensible constraints regarding parameters and initial conditions. +For an example, see the implementation within the LCT-SECIR model. From c5f8b387d5373c2418bc9313175faa73731c9d35 Mon Sep 17 00:00:00 2001 From: Daniel Richter Date: Mon, 28 Jul 2025 11:35:53 +0200 Subject: [PATCH 050/147] memilio-generation doc changes --- docs/source/python/memilio_generation.rst | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/source/python/memilio_generation.rst b/docs/source/python/memilio_generation.rst index 09b0177389..f923f838e0 100644 --- a/docs/source/python/memilio_generation.rst +++ b/docs/source/python/memilio_generation.rst @@ -33,8 +33,9 @@ The package provides an example script on how to use it in `memilio/tools`. The Before running the example you have to do these steps of setup: -* Change `config.json.txt `_. -* Check if the parameters set in ``__post_init__()`` of the `ScannerConfig class `_ match with the cpp-class names. +* Set the source file path in `example_oseir.py `_ under ``conf.source_file`` to the path of the C++ model you want to generate bindings for. +* Set the target folder path in `example_oseir.py `_ under ``conf.target_folder`` to the path where you want the generated bindings to be. + Example: After processing as described in the previous paragraph, run the example with the command (adjust the path according to your current folder): @@ -44,7 +45,32 @@ After processing as described in the previous paragraph, run the example with th python memilio/tools/example_oseir.py -When working on a new model, you can copy the example script and add an additional segment to the config.json.txt. The setup works similar to the example. Additionaly, you can print the AST of your model into a file for development or debugging. + +Visualization +------------- + +The package contains a `Visualization class `_ to display the generated AST. +This allows you to visualize the abstract syntax tree (AST) of the C++ model. + +This allows for: + +* Printing the AST in the terminal. +* Saving the AST as a PDF file. +* Formatting the AST in a text file. + +Example: +``aviz.output_ast_formatted(ast, ast.get_node_by_index(1))`` displays the second node of the AST and its children in a file called ast_formatted.txt. +With the root node ``.get_node_by_index(0)`` you can display the whole AST. + +``aviz.output_ast_terminal(ast, ast.get_node_by_index(1))`` displays the second node of the AST and its children in terminal. + +The first argument of the statements specifies the given AST. The second argument specifies the node and its children that you want to display. + +``aviz.output_ast_png(ast.get_node_by_index(2), 2)`` displays the third node of the AST and its children with a depth of 2 as a png file. +The second argument of the statement specifies the depth up to which the function displays child nodes. +This means that any nodes beyond the specified depth (e.g., all nodes at level 3 and beyond if the depth is set to 2) will not be shown. + +Notice that the visualization as a png-file should not print the whole AST, as it is not possible to display the whole AST in a single image. Testing ------- From 538b15c0a378b049afed54f9d6a6943efeebba5d Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:36:05 +0200 Subject: [PATCH 051/147] Update ode creation --- docs/source/cpp/ode_creation.rst | 108 ++++++++++++++++++++++++++----- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/docs/source/cpp/ode_creation.rst b/docs/source/cpp/ode_creation.rst index 3be94de6f5..35437b8ccd 100644 --- a/docs/source/cpp/ode_creation.rst +++ b/docs/source/cpp/ode_creation.rst @@ -4,8 +4,8 @@ ODE model creation The mathematical model ---------------------- -Before implementing a model in MEmilio, we need to do some math, in particular, define an initial value problem -given by a system of ordinary differential equations. For example, we consider a SIRD model given by +Before implementing a model in MEmilio, we need to do a some math, in particular, define an initial value problem +given by a system of ordinary differential equations. For example we consider a SIRD model given by .. math:: @@ -16,7 +16,7 @@ given by a system of ordinary differential equations. For example, we consider a D'(t) & = \frac{\mu_D}{T_I}I(t) \\ \end{aligned} -and some initial values for :math:`t=0`. Here, :math:`N_{\perp D} := S(t) + I(t) + R(t)`. +and some initial values for :math:`t=0`. Here :math:`N_{\perp D} := S(t) + I(t) + R(t)`. This type of model is called compartmental model, because the model population is represented by discrete infection states **S**\usceptible, **I**\nfectious, **R**\ecovered, **D**\eceased, also called compartments. @@ -29,15 +29,15 @@ use different methods to define the right hand side of the mathematical model ab the infection states, population and parameters it uses. The FlowModel additionally requires a list of flows. We start by creating a new directory for our model under "cpp/models", in this case we can call it "ode_sird". The name -must be unique and start with "ode\_", so the type of model is obvious. The rest of the name usually contains the compartments or +must be unique and start with "ode\_", so the type of model is obvious. The rest usually contains the compartments or other noteworthy features of the model in shortened form. All files in the following are put into this directory. Infection states ~~~~~~~~~~~~~~~~ -First, we define an :code:`enum class` called ``InfectionState`` in the file "infection_state.h", which contains an entry +First we define an :code:`enum class` called ``InfectionState`` in the file "infection_state.h", which contains an entry for each infection state of the mathematical model, followed by an entry called :code:`Count`. This enumerates the -compartments starting from 0, with :code:`Count` being equal to the number of compartments. In our example we have: +compartments starting from 0, with Count being equal to the number of compartments. In our example we have: .. code-block:: cpp @@ -50,6 +50,12 @@ compartments starting from 0, with :code:`Count` being equal to the number of co Count }; +.. dropdown:: :fa:`gears` Expert's settings + + We use an ``enum class`` instead of a regular ``enum`` here, since it helps avoid unintended conversions, invalid + array accesses, and "magic numbers" - you cannot simply write "2" and use it as ``InfectionState::Recovered``, since + an ``enum class`` does not allow implicit conversions. + Parameters ~~~~~~~~~~ @@ -96,6 +102,10 @@ and for the contact rate :math:`\phi` a struct Avoid using the mathematical symbols of the constant as names for the struct. Their connection can be noted in the documentation of these structs. +The template :code:`FP` and the type :code:`UncertainValue` in these examples are commonly used throughout MEmilio. +:code:`FP` is a floating point type, usually :code:`double`. An :code:`UncertainValue` holds a value of type +:code:`FP` as well as (optionally) a distribution to sample new values from, e.g. for a parameter study. + Finally, define a type :code:`Parameters` by listing all parameter structs as template arguments of a :code:`mio::ParameterSet`: @@ -113,7 +123,7 @@ Population ~~~~~~~~~~ The population will be stored in a vector, with a component for each infection state. We define it using the class -``mio::Population``. +``mio::Populations``. .. code-block:: cpp @@ -121,7 +131,36 @@ The population will be stored in a vector, with a component for each infection s using Population = mio::Populations; Importantly, this class allows further stratifying the population vector, with the most common -example being adding :code:`mio::AgeGroups`. +example being adding :code:`mio::AgeGroups` to the template. + +.. dropdown:: :fa:`gears` Expert's settings + + The type ``mio::AgeGroup`` is a typesafe ``size_t``, meaning an integer that cannot be confused with other integer + types. So assignment, addition, etc. only works with another ``mio::AgeGroup``, not ``size_t`` or another integer + type. This is useful for function interfaces or indexing, as it makes it (nearly) impossible to mix up, e.g., age + groups with infection states. Check out ``mio::Index`` if you want to learn more. + + The type ``mio::Populations`` is an extension of a ``mio::CustomIndexArray``, which is a template type that manages + a flat array. Its main purpose is to allow multidimensional indexing into this array, using typesafe indices like + a ``mio::Index`` or a ``enum class``. + + The definition of our ``Population`` then changes to + + .. code-block:: cpp + + template + using Population = mio::Populations; + + and the access (compare with the model definition below) changes to, e.g., + + .. code-block:: cpp + + const AgeGroup i = . . .; + const size_t Ri = this->populations.get_flat_index({i, InfectionState::Susceptible}); + dydt[Ri] = . . . * y[Ri]; + + where we use ``populations.get_flat_index`` to get the correct index in the flat state and derivative vectors. + You may also want to change the Parameters to use age groups, check out the available ODE models as reference. Define a compartmental model ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -130,7 +169,7 @@ Now we can define the model: .. code-block:: cpp - template + template class Model : public mio::CompartmentalModel, Parameters> { public: @@ -138,6 +177,11 @@ Now we can define the model: using typename Base::ParameterSet; using typename Base::Populations; + Model() + : Base(Populations({InfectionState::Count}), ParameterSet()) + { + } + void get_derivatives(Eigen::Ref> pop, Eigen::Ref> y, FP t, Eigen::Ref> dydt) const override { @@ -148,22 +192,39 @@ Now we can define the model: dydt[InfectionState::Susceptible] = -params.template get>() * params.template get>() * - y[InfectionState::Susceptible] * y[InfectionState::Infectious] / N; + y[InfectionState::Susceptible] * pop[InfectionState::Infectious] / N; . . . } }; -Here, the :code:`get_derivatives` function implements the right hand side of the differential equations. +Here, create a new class ``Model`` that inherits from ``mio::CompartmentalModel``, which predefines some functions (like +``check_constraints``) and members (like ``populations`` and ``parameters``) for us. In the class body, we first add +a few ``using`` statements, that create shorthands for our base class and its types. Next, we define a constructor, that +creates populations and parameters with their default values, so they can be set later. Finally, we define the the right +hand side of the model equations through ``get_derivatives``. Importantly, note that the value in ``populations`` is +only used as initial value for the IVP, the current state of the model at time ``t`` is given by ``y`` instead. The +derivative of ``y`` at ``t`` is to be stored in ``dydt``. + +Note that the argument ``pop`` is used once instead of ``y``. As a general rule, use ``y`` when the index matches with +the one used for ``dydt``, and ``pop`` otherwise. + +Essentially, ``pop`` is the population that is interacted with, while ``y`` is the acting/changing population. Our graph +models use this to model the exchange between multiple models, e.g. by setting ``pop = y ± commuters``. Outside of graph +models both ``y`` and ``pop`` will have the same value. + +Check out the page on the usage of :doc:`ODE-based models`. + Define a flow model ^^^^^^^^^^^^^^^^^^^ -A flow model is a special case of a compartmental model, where each compartment :math:`Z_i` can be written as +A flow model is a special case of a compartmental model, where the derivative of each compartment over time +:math:`Z_i'(t)` can be written as .. math:: - Z'_i(t) = \sum_{j \ne i} f_{Z_j \rightarrow Z_i}(t) - \sum_{j \ne i} f_{Z_i \rightarrow Z_j}(t), + Z_i'(t) = \sum_{i \ne j} f_{Z_j \rightarrow Z_i}(t) - \sum_{i \ne j} f_{Z_i \rightarrow Z_j}(t), where the flows :math:`f_{Z_i \rightarrow Z_j} \gt 0` are the amount of population changing from compartment :math:`Z_i` to :math:`Z_j` at time :math:`t`. So the first sum accumulates all inflows, the second subtracts all @@ -194,6 +255,14 @@ we get: mio::Flow, mio::Flow>; +The first term in each :code:`mio::Flow` is the source compartment, the second the target. As a convention, we always +compute non-negative outflows. Hence, we only list the flow :math:`S \rightarrow I`, but not :math:`I \rightarrow S`. + +Infection states, Parameters and Population +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since a Flow model is just a special case of a compartmental model, all of these are defined exactly as described above. + Define the model ~~~~~~~~~~~~~~~~ @@ -201,7 +270,7 @@ With the flows and classes also used by the CompartmentalModel, we can define a .. code-block:: cpp - template + template class Model : public mio::FlowModel, Parameters, Flows> { public: @@ -219,8 +288,17 @@ With the flows and classes also used by the CompartmentalModel, we can define a flows[this->template get_flat_flow_index()] = params.template get>() * params.template get>() * - y[InfectionState::Susceptible] * y[InfectionState::Infectious] / N; + y[InfectionState::Susceptible] * pop[InfectionState::Infectious] / N; . . . } }; + +This is mostly analoguous to the definition of a compartmental model, with a few important differences. First, we now +inherit from ``FlowModel``, which gets the ``Flows`` as an additional template argument. The ``Base`` alias changes +accordingly. Secondly, the function we implement is called ``get_flows`` and computes the derivative of y in terms of +its flows. + +To index into the ``flows`` vector we use the function ``get_flat_flow_index``, which takes the source and target +compartments as template arguments, in that order. Indexes from further stratification (like ``mio::AgeGroup``) can be +passed as an optional function argument. From 6f6f88921d6309c54fed31a0c096d9677006dbe3 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:37:27 +0000 Subject: [PATCH 052/147] Extend memilio simulation --- docs/source/python/memilio_simulation.rst | 2 +- docs/source/python/model_creation.rst | 29 ++++++++++++++--------- docs/source/python/model_usage.rst | 6 ++--- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/source/python/memilio_simulation.rst b/docs/source/python/memilio_simulation.rst index f917330d03..2b67410587 100644 --- a/docs/source/python/memilio_simulation.rst +++ b/docs/source/python/memilio_simulation.rst @@ -22,7 +22,7 @@ Usage ----- For understanding the package you can follow the introductions on model usage and creation. -Additionally, multiple `examples ` for the +Additionally, multiple `examples `_ for the different features are provided. Lastly, this documentation provides tutorials on applications for the MEmilio Python interface, coupling it with other libraries, showing the advantages of in language simulation models. diff --git a/docs/source/python/model_creation.rst b/docs/source/python/model_creation.rst index 041a58d755..f54d42bcec 100755 --- a/docs/source/python/model_creation.rst +++ b/docs/source/python/model_creation.rst @@ -1,4 +1,4 @@ -Tutorial: Expanding python bindings +Tutorial: Expanding Python bindings =================================== .. toctree:: @@ -6,8 +6,8 @@ Tutorial: Expanding python bindings This tutorial is aimed at explaining the process of adding new functionalities to the simulation package -through writing new python bindings. It is advantagous to have knowledge on programming in C++ -as the bindings, as well as the functional source code we want to call from python, is written in C++. +through writing new Python bindings. It is advantagous to have knowledge on programming in C++ +as the bindings, as well as the functional source code we want to call from Python, is written in C++. We will go over the common workflow of writing bindings for MEmilio, explaining the structure of the bindings, providing a small step-by-step guide on adding a new model and showcasing common patterns you @@ -18,22 +18,29 @@ Lets start by looking at the Adding a new model ------------------ -If currently a model is not available in the python bindings or you added a new c++ model and want to bind it +If currently a model is not available in the Python bindings or you added a new c++ model and want to bind it then the following steps should give an overview of what needs to be done: -#. Add new bindings including a model file in the [models](memilio/simulation/bindings/models/) folder that defines the new module -#. Add a python module file similar to the other models, e.g. [osir.py](memilio/simulation/osir.py) (needed for the structure of the python package) and modify getter function in [__init__.py](memilio/simulation/__init__.py) -#. Add the new module to the building process by modifying [CMakeLists.txt](CMakeLists.txt) -#. Write new tests and examples -#. (Add the package to the documentation and stubs generation) +* Add new bindings including a model file in the `models `_folder that defines the new module +* Add a Python module file similar to the other models, e.g. `osir.py `_ (needed for the structure of the Python package) and modify getter function in `__init__.py `_ +* Add the new module to the building process by modifying `CMakeLists.txt `_ +* Write new tests and examples +* (Add the package to the documentation and stubs generation) - talk about memilio generation :doc:`memilio-generation ` Expanding the simulation packages --------------------------------- +All bindings are located inside the folder `bindings `_. The simulation package is defined over modules. A main module `memilio.simulation` contains the general functionalities of the MEmilio library like the TimeSeries class, different integrators, dampings or IO functionen. +Additionally each model is located into its own submodule, where model specific bindings are written. -Pure python additions +Python doesn't provide an interface for templates. Therefore, templated functions or classes need to be defined explicitly for each template argument that should be usable. +To reduce overhead, the bindings provide functions to bind templated classes and function. The binding function gets the same template arguments as the class and defines the interface for the class. +As an example, you can look at `bind_CompartmentalModel() `_. + +Pure Python additions --------------------- -The bindings can also be expanded with pure Python code by expanding the existing python files or adding new ones underneath the [simulation](memilio/simulation/) folder. +The bindings can also be expanded with pure Python code by expanding the existing Python files or adding new ones underneath the `simulation `_ folder. +For those extensions follow the standard Python coding guidelines. diff --git a/docs/source/python/model_usage.rst b/docs/source/python/model_usage.rst index 03d9221cc0..1316153bd6 100755 --- a/docs/source/python/model_usage.rst +++ b/docs/source/python/model_usage.rst @@ -1,8 +1,8 @@ -Tutorial: Usage of python bindings +Tutorial: Usage of Python bindings ================================== This tutorial should give an overview of how to use the -currently available functions and models of the python bindings. +currently available functions and models of the Python bindings. For expanding the bindings with new models look into the section :doc:`Model Creation `. @@ -97,7 +97,7 @@ Now we could simulate the infectious diesease dynamic by calling simulate: result = oseir.simulate(0, t_max, dt, model) -Similar to the MEmilio C++ library, the python interface provides the option of adjusting the solver. +Similar to the MEmilio C++ library, the Python interface provides the option of adjusting the solver. Currently available: * Euler From 98846e207658bc3c21abd2a044a066f708547206 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:49:32 +0200 Subject: [PATCH 053/147] CHG: add links to Graph ABM --- docs/source/cpp/graph_abm.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/cpp/graph_abm.rst b/docs/source/cpp/graph_abm.rst index 9e1973fb45..3a9da70a4b 100644 --- a/docs/source/cpp/graph_abm.rst +++ b/docs/source/cpp/graph_abm.rst @@ -126,7 +126,9 @@ Assigning infection states and locations to persons in all models can be done vi adult2.set_assigned_location(mio::abm::LocationType::Work, work, model2.get_id()); adult3.set_assigned_location(mio::abm::LocationType::Work, work, model2.get_id()); -For initializing the graph nodes and edges a ``mio::Graph`` is created which gets ``mio::ABMSimulationNode`` and ``mio::ABMMobilityEdge`` as templates. Additionally, every node needs a ``mio::History`` object to log its results during the simulation. See :doc:`io` for information on how to use ``mio::History``. Below, ``mio::abm::LogInfectionState`` is used as logger. +For initializing the graph nodes and edges, a ``mio::Graph`` is created which gets ``mio::ABMSimulationNode`` and ``mio::ABMMobilityEdge`` as templates. +Additionally, every node needs a ``mio::History`` object to log its results during the simulation. See :ref:`history` for information on how to use ``mio::History``. +Below, ``mio::abm::LogInfectionState`` is used as logger. .. code-block:: cpp @@ -139,7 +141,8 @@ For initializing the graph nodes and edges a ``mio::Graph`` is created which get graph.add_edge(model1.get_id(), model2.get_id()); graph.add_edge(model2.get_id(), model1.get_id()); -To simulate the model from `start_date` to `end_date` with given graph step size `exchange_time_span`, a GraphSimulation has to be created. The step size is used to regularly exchange agents via the graph edges. Advancing the simulation until `end_date` is done as follows: +To simulate the model from `start_date` to `end_date` with given graph step size `exchange_time_span`, a GraphSimulation has to be created. +The step size is used to regularly exchange agents via the graph edges. Advancing the simulation until `end_date` is done as follows: .. code-block:: cpp From 829b4de25f12bbc94b40221dec14d6864a88c046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Mon, 28 Jul 2025 13:00:45 +0200 Subject: [PATCH 054/147] contributor and funder --- docs/source/team.rst | 58 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/docs/source/team.rst b/docs/source/team.rst index 3613dbd468..d96bf519c4 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -10,6 +10,39 @@ Core Developers .. _developer_1: +Martin Kühn +^^^^^^^^^^^^^^^^^^ + +.. image:: https://www.martinkuehn.eu/research/kuehn150x150.jpg + :alt: Martin Kühn + :width: 150px + :align: left + :class: developer-photo + +**Research Focus:** High-Performance Computing, Mathematical Modeling, Numerical Analysis, Scientific Computing + +Martin Kühn studied at University of Cologne, Germany, and Université de Montréal, Canada, and obtained a PhD in Applied Mathematics from University of Cologne in 2018. He is affiliated with the Institute of Software Technology of the German Aerospace Center where he leads the group of Predictive Simulation Software and the University of Bonn where he does research on mathematical-epidemiological modeling. His research focuses on the design, analysis, and implementation of numerical methods in the context of high performance computing. Over the last five years, Martin Kühn has studied various models for the spread of infectious diseases, including SARS-CoV-2. He initiated the development of the MEmilio framework to support fast and reliable response to pandemic threats using mathematical models from ordinary and integro-differential equations, metapopulations, agent-based and hybrid metapopulation-agent-based models as well as machine learning surrogate models through, e.g., graph neural networks. + +**Selected Publications:** + +* Schmidt A, Zunker H, Heinlein A, **Kühn MJ**. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. Submitted for publication. `arXiv:2411.06500 `_ +* Wendler AC, Plötzke L, Tritzschak H, **Kühn MJ**. (2025). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Accepted for publication. `arXiv:2408.12228 `_ +* Plötzke L, Wendler A, Schmieding R, **Kühn MJ**. (2025). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Accepted for publication. `DOI:10.48550/arXiv.2412.09140 `_ +* Zunker H, Dönges P, Lenz P, Contreras S, **Kühn MJ**. (2025). Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models. Chaos, Solitons & Fractals. `DOI:10.1371/journal.pcbi.1012630 `_ +* Schmid N, Bicker J , Hofmann AF, Wallrafen-Sam K, Kerkmann D, Wieser A, **Kühn MJ**, Hasenauer J (2025). *Integrative Modeling of the Spread of Serious Infectious Diseases and Corresponding Wastewater Dynamics*. *Epidemics* 51:100836. `DOI:10.1016/j.epidem.2025.100836 `_ +* Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, Gerstein C, Göbbert JH, Basermann A, **Kühn MJ**, Meyer-Hermann M (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193:110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ +* Bicker J, Schmieding R, Meyer-Hermann M, **Kühn MJ**. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `DOI:10.1016/j.idm.2024.12.015 `_ +* Zunker H, Schmieding R, Kerkmann D, Schengen A, Diexer S, ..., **Kühn MJ** (2024). *Novel travel time aware metapopulation models and multi-layer waning immunity for late-phase epidemic and endemic scenarios*. *PLOS Computational Biology* 20(12): e1012630. `DOI:10.1371/journal.pcbi.1012630 `_ + + +**Links:** + +* `Google Scholar Profile `_ +* `ORCID Profile: 0000-0002-0906-6984 `_ +* `SCOPUS Author Profile: `_ + +.. _developer_2: + Henrik Zunker ^^^^^^^^^^^^^^^^^^ @@ -41,20 +74,19 @@ Henrik Zunker is a PhD student at the Institute for Software Technology at the G Contributors ------------ -We thank all contributors, who have contributed to MEmilio. TBD - -For a complete list of contributors, please see our `GitHub Contributors page `_. +We thank all contributors, who have contributed to MEmilio. For a complete list of contributors, please see our `GitHub Contributors page `_. .. _collaboration: Participating research groups ------------- -MEmilio is developed in collaboration with various research institutions and partners: +MEmilio has been developed by and in collaboration with various research institutions: * **German Aerospace Center (DLR)** - Institute for Software Technology -* **University of Bonn** - Life and Medical Sciences Institute and Bonn Center for Mathematical Life Sciences -* TBC +* **University of Bonn** - Life and Medical Sciences Institute and Bonn Center for Mathematical Life Sciences +* **Helmholtz Centre for Infection Research (HZI)** - Department of Systems Immunology +* **Forschungszentrum Jülich (FZJ)** - Institute of Climate and Energy Systems (ICE-1) and Institute of Bio- und Geosciences: Biotechnology (IBG-1) .. _acknowledgments: @@ -62,10 +94,10 @@ MEmilio is developed in collaboration with various research institutions and par Acknowledgments --------------- -MEmilio is supported by the Initiative and Networking Fund of -the Helmholtz Association, Germany (grant agreement number KA1-Co- -08, Project LOKI-Pandemics). - -Memilio was supported by the German Federal Ministry for -Digital and Transport, Germany under grant agreement FKZ19F2211A -(Project PANDEMOS). +MEmilio has been supported by various project grants. Since 2020, MEmilio has been funded +* by the Initiative and Networking Fund of the Helmholtz Association of German Research Institutions under grant agreement number KA1-Co-08 (Project LOKI-Pandemics), +* by the German Federal Ministry for Digital and Transport under grant agreement FKZ19F2211A and FKZ19F2211B (Project PANDEMOS), +* by the Helmholtz School for Data Science in Life, Earth and Energy (HDS-LEE), +* by German Federal Ministry of Education and Research under grant agreement 031L0297B (Project INSIDe), +* by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under grant agreement 528702961, +* by German Federal Ministry of Education and Research under grant agreement 031L0319A and 031L0319A (Project AIMS). From f107b1ce7b5eccbf1555d99cc39cccd65e7ec23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Mon, 28 Jul 2025 13:02:15 +0200 Subject: [PATCH 055/147] logos --- docs/source/_static/funder/bmbf.jpg | Bin 0 -> 110461 bytes docs/source/_static/funder/bmdv.png | Bin 0 -> 59723 bytes docs/source/_static/funder/hdslee.png | Bin 0 -> 62013 bytes docs/source/_static/funder/helmholtz.jpg | Bin 0 -> 115334 bytes docs/source/_static/funder/mfund.png | Bin 0 -> 21312 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/_static/funder/bmbf.jpg create mode 100644 docs/source/_static/funder/bmdv.png create mode 100644 docs/source/_static/funder/hdslee.png create mode 100644 docs/source/_static/funder/helmholtz.jpg create mode 100644 docs/source/_static/funder/mfund.png diff --git a/docs/source/_static/funder/bmbf.jpg b/docs/source/_static/funder/bmbf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e91c985687969f31f5fe478a43e266565e6b34e8 GIT binary patch literal 110461 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/funder/hdslee.png b/docs/source/_static/funder/hdslee.png new file mode 100644 index 0000000000000000000000000000000000000000..08b012c3c34e628ecfa498928770cc48d2da47d9 GIT binary patch literal 62013 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/funder/helmholtz.jpg b/docs/source/_static/funder/helmholtz.jpg new file mode 100644 index 0000000000000000000000000000000000000000..60bd2e120b54e784996897b975e66ad2d2d8ca50 GIT binary patch literal 115334 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 XLV$$;3jr1aECg5xd_>^%hk^eE^*@sc literal 0 HcmV?d00001 diff --git a/docs/source/_static/funder/mfund.png b/docs/source/_static/funder/mfund.png new file mode 100644 index 0000000000000000000000000000000000000000..e0913145a3b39b16345ae99c0abb98b506ce63d4 GIT binary patch literal 21312 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 z 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 O0000 Date: Mon, 28 Jul 2025 13:03:43 +0200 Subject: [PATCH 056/147] Review comments graph ABM --- docs/source/cpp/graph_abm.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/source/cpp/graph_abm.rst b/docs/source/cpp/graph_abm.rst index 3a9da70a4b..12fc7cc68c 100644 --- a/docs/source/cpp/graph_abm.rst +++ b/docs/source/cpp/graph_abm.rst @@ -4,11 +4,9 @@ Graph-based agent-based model Introduction ------------- -This model realizes multiple instances of the mobility-based agent-based model ``abm::Model`` (see :doc:`mobility_based_abm` for the documentation) +This model realizes multiple instances of the mobility-based agent-based model (ABM) ``abm::Model`` (see :doc:`mobility_based_abm` for the documentation) as nodes in a directed graph. One local model represents a geographical region. The regions are connected by the graph edges. Mobility within one node -and via the graph edges follows the same mobility rules that can be handed as argument to ``mio::GraphABModel``. Therefore this graph-based agent-based -model can be reduced to a single mobility-based agent-based model if simulation time steps within the whole graph, i.e. the step size of each node and the -step size of the edge exchange, are equal. +and via the graph edges follows the same mobility rules that can be handed as argument to ``mio::GraphABModel``. Therefore this graph-based agent-based (graph-ABM) model can be reduced to a single mobility-based agent-based model if simulation time steps within the whole graph, i.e. the step size of each node and the step size of the edge exchange, are equal. Preimplemented mobility rules can be found in ``_. The motivation behind the graph-ABM is to have multiple ABMs run independently from each other in parallel for different regions and only synchronize, i.e. exchange agents via edges, in fixed time intervals. The synchronization time steps should be bigger than the internal ABM time steps to reduce communication between nodes as much as possible. Simulation ----------- From 519f9d8b677a2c25d7e287538fbc19a69777e811 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:32:46 +0200 Subject: [PATCH 057/147] model structure with figure --- docs/source/cpp/overview.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/source/cpp/overview.rst b/docs/source/cpp/overview.rst index dd9c190d4e..245b21f28f 100644 --- a/docs/source/cpp/overview.rst +++ b/docs/source/cpp/overview.rst @@ -33,6 +33,30 @@ The main directory structure in the ``cpp`` directory includes: - **benchmarks/**: Analyzing runtime performance +Model Structure +----------------- + +The MEmilio library uses a modular organization of models, where generic implementations are inherited by specific implementations: + +.. image:: https://github.com/user-attachments/assets/280fa4f8-9caf-4e70-8e87-79754b4cc2dc + :alt: Model Hierarchy + :width: 100% + +**CompartmentalModel**: The base class for all compartment-based models in MEmilio. It defines the fundamental structure for epidemiological models with compartments (e.g., SEIR, SECIR) and provides methods like ``eval_right_hand_side`` and ``get_initial_values`` required for ODE solvers. + +**FlowModel**: Inherits from CompartmentalModel and extends it with the concept of flows between compartments. Instead of directly defining derivatives, it specifies the flows between compartments. + +**Specific Model Implementations**: + +- **ODE Model** (Ordinary Differential Equations): Deterministic models for continuous populations described by ordinary differential equations. + +- **IDE Model** (Integro-Differential Equations): Extends the ODE model integration terms. + +- **SDE Model** (Stochastic Differential Equations): Adds stochastic components to model uncertainties and random effects. + +**Individual-based Model**: Stands separate from the compartmental hierarchy and models each individual explicitly with its own properties and interactions. This enables more detailed simulations. + + Build System ------------- From 4172c0a1bd2463ea90c015a5d2721cf9cb2e2e45 Mon Sep 17 00:00:00 2001 From: Carlotta Gerstein Date: Mon, 28 Jul 2025 13:37:17 +0200 Subject: [PATCH 058/147] start sde creation --- docs/source/cpp/sde_creation.rst | 40 +++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/source/cpp/sde_creation.rst b/docs/source/cpp/sde_creation.rst index 53a0f1ea3c..4dd030e47a 100644 --- a/docs/source/cpp/sde_creation.rst +++ b/docs/source/cpp/sde_creation.rst @@ -1,2 +1,40 @@ SDE Model Creation -================== \ No newline at end of file +================== + +The mathematical model +---------------------- + +We assume the following form for our stochastic models: + +.. math:: + + \begin{aligned} + \mathrm{d}X_t = a(X_t, t) \mathrm{d}t + b(X_t, t)\mathrm{d}W_t + \end{aligned} + +with initial values :math:`X_0 = x_0` where :math:`W_t` denotes the Wiener process. + +Analogously to the ODE models, :math:`X_t` describes the sizes of compartments at time point :math:`t`. +The first summand of the equation describes the deterministic part of the model and the second summand represents a +noise term. + +How to define an SDE model +------------------------- + +To define an SDE model in MEmilio, you define a **StochasticModel**. It is derived from either a **CompartmentalModel** +or **FlowModel** which differ in the methods to define the right hand side of the deterministic part of the mathematical +model. For both choices you need to define a list of **InfectionState**\s, **Parameter**\s and initial conditions via a +**Population**. We refer to the corresponding :doc:`ODE model ` documentation for more details. +The stochastic part of the model is determined in a :code:`get_noise()` function of the **StochasticModel**, which +represents the result of the deterministic noise matrix multiplied by a white noise vector. + +Noise contributions +~~~~~~~~~~~~~~~~~~~ + +In infectious disease models, the noise matrix typically assigns noise contributions from transitions to the respective +compartments. In that case, the white noise vector usually has the same size as the number of transitions. The resulting +noise vector must have the same dimension as the state vector, since the integration of stochastic models is performed +on the compartments. This is necessary, because the applied noise can occasionally push compartments into negative +values. While this can be mitigated by removing negative values and rescaling the population (see `map_to_nonnegative`), +this mitigation can not (in general) be applied to flows. However, the noise is applied per transition, with inflows and +outflows of a compartment using the same random value to conserve the total population. From af01864ea44260344398e994e1eb0d0f40851f03 Mon Sep 17 00:00:00 2001 From: Henrik Zunker Date: Mon, 28 Jul 2025 13:50:36 +0200 Subject: [PATCH 059/147] some more data types --- docs/source/cpp/data_types.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index 316b1c582c..c952857ed3 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -21,3 +21,23 @@ The follwing list expalins the non-standard data types that are used throughout - An array whose values can be accesses by a multi-index. This datatype is for example used in the parameter :code:`mio::abm::TimeExposedToNoSymptoms` making it dependent on :code:`mio::abm::VirusVariant` and :code:`mio::AgeGroup`. Its values can then be set for a specific :code:`virus_variant` and :code:`age_group` using :code`model.parameters.template get()[{virus_variant, age_group}]`. * - :code:`Populations` - Is a :code:`mio::CustomIndexArray` with :code:`mio::UncertainValue` as values. + * - :code:`TimeSeries` + - Stores vectors of values at time points. Each time point has a vector of values of the same size with operations like adding time points, retrieving values, exporting to CSV, etc. It's also used for storing and analyzing simulation results over time. + * - :code:`Graph` + - A generic graph structure that represents a network of nodes connected by edges. Each node and edge can have associated properties. The Graph is used to model geographical regions connected by mobility patterns (e.g., commuting), where each node is represented by its own epidemiological model. + * - :code:`Node` + - Represents a node in a Graph with a unique ID and associated properties. + * - :code:`Edge` + - Represents a directed connection between two nodes in a Graph with associated properties. + * - :code:`EdgeBase`, :code:`InEdgeBase`, :code:`OutEdgeBase` + - Base classes for Edge that define start and end node indices for connections in the Graph. + * - :code:`SimulationNode` + - Represents a simulation in one node of a mobility graph. Contains a simulation model of any type and keeps track of the last state and time point. + * - :code:`MobilityCoefficients` + - Time-dependent mobility coefficients used to model how populations move between nodes in a graph. + * - :code:`MobilityCoefficientGroup` + - A collection of time-dependent mobility coefficients that differentiate between various sources of mobility. + * - :code:`MobilityParameters` + - Parameters that influence mobility between nodes, including coefficients and dynamic non-pharmaceutical interventions (NPIs). + * - :code:`MobilityEdge` + - Represents mobility between two nodes in a graph. Handles the movement of populations between nodes, tracks mobile populations, and applies mobility returns according to epidemiological models. From 7e9eae9e68c48bf37b10a9ffe7f87dcfa2155d7d Mon Sep 17 00:00:00 2001 From: Henrik Zunker Date: Mon, 28 Jul 2025 13:56:23 +0200 Subject: [PATCH 060/147] contact matrix / damping types --- docs/source/cpp/data_types.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index c952857ed3..c75378e2c1 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -41,3 +41,11 @@ The follwing list expalins the non-standard data types that are used throughout - Parameters that influence mobility between nodes, including coefficients and dynamic non-pharmaceutical interventions (NPIs). * - :code:`MobilityEdge` - Represents mobility between two nodes in a graph. Handles the movement of populations between nodes, tracks mobile populations, and applies mobility returns according to epidemiological models. + * - :code:`DampingMatrixExpression` + - Represents a coefficient-wise matrix expression B - D * (B - M), where B is a baseline matrix, M is a minimum matrix, and D is a time-dependent complex damping factor. Used as the base for time-dependent contact matrices. + * - :code:`DampingMatrixExpressionGroup` + - Represents a collection of DampingMatrixExpressions that are summed up. Used for representing multiple sources of contacts or mobility. + * - :code:`ContactMatrix` + - Time-dependent contact frequencies between groups, derived from DampingMatrixExpression. Models how the contact rates between different age groups change over time due to interventions. + * - :code:`ContactMatrixGroup` + - A collection of contact matrices that represent different contexts (e.g., home, school, work) whose sum is the total number of contacts, derived from DampingMatrixExpressionGroup. From 2cb053e82f95b85538e35838b0c4af3e0576ff56 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:18:22 +0200 Subject: [PATCH 061/147] add funder logos --- docs/source/team.rst | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/source/team.rst b/docs/source/team.rst index d96bf519c4..59ab1f9498 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -58,14 +58,14 @@ Henrik Zunker is a PhD student at the Institute for Software Technology at the G **Selected Publications:** -* **Zunker H**, Schmieding R, Kerkmann D, Schengen A, Diexer S, et al. (2024). *Novel travel time aware metapopulation models and multi-layer waning immunity for late-phase epidemic and endemic scenarios*. *PLOS Computational Biology* 20(12): e1012630. https://doi.org/10.1371/journal.pcbi.1012630 * **Zunker H**, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models. Chaos, Solitons & Fractals. https://doi.org/10.1016/j.chaos.2025.116782 -* Schmidt A, **Zunker H**, Heinlein A, Kühn MJ. (2024). Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response. arXiv. https://arxiv.org/abs/2411.06500 +* Schmidt A, **Zunker H**, Heinlein A, Kühn MJ. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. Submitted for publication. `arXiv:2411.06500 `_ +* **Zunker H**, Schmieding R, Kerkmann D, Schengen A, Diexer S, et al. (2024). *Novel travel time aware metapopulation models and multi-layer waning immunity for late-phase epidemic and endemic scenarios*. *PLOS Computational Biology* 20(12): e1012630. https://doi.org/10.1371/journal.pcbi.1012630 **Links:** * `Google Scholar Profile `_ -* `ORCID: 0000-0002-9825-365X `_ +* `ORCID Profile: 0000-0002-9825-365X `_ ---- @@ -101,3 +101,25 @@ MEmilio has been supported by various project grants. Since 2020, MEmilio has be * by German Federal Ministry of Education and Research under grant agreement 031L0297B (Project INSIDe), * by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under grant agreement 528702961, * by German Federal Ministry of Education and Research under grant agreement 031L0319A and 031L0319A (Project AIMS). + +.. |helmholtz| image:: _static/funder/helmholtz.jpg + :width: 200px + :alt: Helmholtz Association + +.. |bmbf| image:: _static/funder/bmbf.jpg + :width: 200px + :alt: Bundesministerium für Bildung und Forschung + +.. |bmdv| image:: _static/funder/bmdv.png + :width: 200px + :alt: Bundesministerium für Digitales und Verkehr + +.. |hdslee| image:: _static/funder/hdslee.png + :width: 200px + :alt: Helmholtz School for Data Science in Life, Earth and Energy + +.. |mfund| image:: _static/funder/mfund.png + :width: 200px + :alt: mFUND + +|helmholtz| |bmbf| |bmdv| |hdslee| |mfund| From 20bea400c1fe658ad55de061aaa6b8d8f8bc247b Mon Sep 17 00:00:00 2001 From: Daniel Richter Date: Mon, 28 Jul 2025 14:34:44 +0200 Subject: [PATCH 062/147] memilio generation docs changes --- docs/source/python/memilio_generation.rst | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/source/python/memilio_generation.rst b/docs/source/python/memilio_generation.rst index f923f838e0..6747d15795 100644 --- a/docs/source/python/memilio_generation.rst +++ b/docs/source/python/memilio_generation.rst @@ -8,11 +8,12 @@ For a particular example, see the SEIR model with its files `oseir.cpp` and `ose This generating software was developed as a part of the Bachelor thesis `Automatische Codegenerierung für nutzerfreundliche mathematisch-epidemiologische Modelle `_. The following figure from Chapter 5 outlines the workflow of the generator. Blue boxes represent parts of the code generator and orange ones the input and output. Rectangular boxes contain classes with logic, the rest represent data. -.. image:: ../../../pycode/memilio-generation/generator_workflow.png - :alt: tikzGeneratorWorkflow +.. image:: https://github.com/SciCompMod/memilio/raw/main/pycode/memilio-generation/generator_workflow.png + :alt: Workflow of the generator + Dependencies ------------- +---------- The package uses the `Clang C++ library `_ and the `LibClang Python library `_ to analyze the C++ code of a model. Both need to be installed and share the same version. @@ -24,6 +25,8 @@ Required python packages: * graphviz * importlib-resources + + Usage ----- @@ -38,13 +41,25 @@ Before running the example you have to do these steps of setup: Example: + After processing as described in the previous paragraph, run the example with the command (adjust the path according to your current folder): .. code-block:: console python memilio/tools/example_oseir.py +To use the visualization run the command: + +.. code-block:: console + + python memilio/tools/example_oseir.py -p + + +Common mistakes: +* Please ensure that your python bindings are compatible with your libclang.so version. + +Set your libclang version under ``install_requires`` according to your python bindings version in the `setup.py `_. Visualization ------------- @@ -59,7 +74,9 @@ This allows for: * Formatting the AST in a text file. Example: + ``aviz.output_ast_formatted(ast, ast.get_node_by_index(1))`` displays the second node of the AST and its children in a file called ast_formatted.txt. + With the root node ``.get_node_by_index(0)`` you can display the whole AST. ``aviz.output_ast_terminal(ast, ast.get_node_by_index(1))`` displays the second node of the AST and its children in terminal. From f1ca4f15fe258ee6aaac6837e74cc126d7a82ebe Mon Sep 17 00:00:00 2001 From: Henrik Zunker Date: Mon, 28 Jul 2025 14:57:25 +0200 Subject: [PATCH 063/147] faq contact mail + ref to discussion, update gnn citation --- README.md | 2 +- docs/source/citation.rst | 2 +- docs/source/faq.rst | 4 +++- docs/source/python/memilio_surrogate.rst | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 582ec42595..e3279f2df5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ and, in particular, for - Integro-differential equation-based (IDE) models: Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `https://arxiv.org/abs/2408.12228` - Agent-based models (ABMs): Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, et al. (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193: 110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ - Hybrid agent-metapopulation-based models: Bicker J, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `https://doi.org/10.1016/j.idm.2024.12.015` -- Graph Neural Networks: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024). *Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `https://arxiv.org/abs/2411.06500` +- Graph Neural Networks: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. Submitted for publication. `arXiv:2411.06500 ` - ODE-based models with Linear Chain Trick: Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `https://doi.org/10.48550/arXiv.2412.09140` - Behavior-based ODE models: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. *Chaos, Solitons & Fractals* 199:116782. `https://doi.org/10.1016/j.chaos.2025.116782` diff --git a/docs/source/citation.rst b/docs/source/citation.rst index bacc2c2a5d..121fbb285c 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -11,7 +11,7 @@ and, in particular, for - **Integro-differential equation-based (IDE) models**: Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `arXiv:2408.12228 `_ - **Agent-based models (ABMs)**: Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, et al. (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193: 110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ - **Hybrid agent-metapopulation-based models**: Bicker J, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `DOI:10.1016/j.idm.2024.12.015 `_ -- **Graph Neural Networks**: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024).*Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ +- **Graph Neural Networks**: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. Submitted for publication. `arXiv:2411.06500 ` _ - **ODE-based models with Linear Chain Trick**: Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ - **Behavior-based ODE models**: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. *Chaos, Solitons & Fractals* 199:116782. `DOI:10.1016/j.chaos.2025.116782 `_ diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 23a520fd38..49ecdef2f8 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -3,6 +3,8 @@ FAQ .. _model-faq: +If your question is not answered in this documentation, feel free to contact martin.kuehn@dlr.de. Alternatively, you can also ask your question in the GitHub repository under the Discussions section. + What do you mean when talking about a *model* (implementation)? -------------------------------------------------------------------- @@ -32,4 +34,4 @@ Please open an issue on our `GitHub `_ page. If there is not yet a bug report, please open an issue first and reference it in your pull request. For more information, please refer to :doc:`development`. \ No newline at end of file +Please open a pull request on our `GitHub `_ page. If there is not yet a bug report, please open an issue first and reference it in your pull request. For more information, please refer to :doc:`development`. diff --git a/docs/source/python/memilio_surrogate.rst b/docs/source/python/memilio_surrogate.rst index 832635cc1f..2a94fa46b7 100644 --- a/docs/source/python/memilio_surrogate.rst +++ b/docs/source/python/memilio_surrogate.rst @@ -8,7 +8,7 @@ e.g., a metapopulation or agent-based model while still having acceptable errors The package can be found in `pycode/memilio-surrogatemodel `_. -For more details, we refer to: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024).*Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ +For more details, we refer to: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. Submitted for publication. `arXiv:2411.06500 `_ Dependencies ------------ From e071510fcf328d1dcf2e0214137223fc5fdf24ca Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:57:59 +0200 Subject: [PATCH 064/147] fix data types table bug --- docs/source/cpp/data_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index c75378e2c1..391e4ef8b0 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -5,7 +5,7 @@ The follwing list expalins the non-standard data types that are used throughout .. list-table:: :header-rows: 1 - :widths: 20 20 60 + :widths: 20 60 * - Data type name - Description From 0c1899b7756590f868cc7fffb4746a711d08dd15 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:03:01 +0200 Subject: [PATCH 065/147] CHG: rename interfaces to extensions --- docs/source/cpp/interfaces.rst | 4 ++-- docs/source/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/cpp/interfaces.rst b/docs/source/cpp/interfaces.rst index dddadc87ff..e1d4d8243c 100644 --- a/docs/source/cpp/interfaces.rst +++ b/docs/source/cpp/interfaces.rst @@ -1,7 +1,7 @@ -Interfaces +Extensions ============== -MEmilio provides several interfaces to interact with external data structures or software. +MEmilio provides several extensions to interact with external data structures or software. - :doc:`SBML ` can be used to semi-automatically creat models in MEmilio given a model description in the SBML format. - :doc:`Epidata <../python/memilio_epidata>` provides a Python interface to access epidimiological data. diff --git a/docs/source/index.rst b/docs/source/index.rst index 19935a7b1a..0a2aadb270 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -20,7 +20,7 @@ MEmilio implements various models for infectious disease dynamics, ranging from If you use MEmilio, please :doc:`cite our work`. -.. attention:: +.. note:: This framework is under active development, as is this documentation. @@ -42,11 +42,11 @@ Contents :caption: C++ Interface cpp/overview - cpp/interfaces cpp/installation cpp/model_usage cpp/model_creation cpp/development + cpp/interfaces .. toctree:: :maxdepth: 2 From 8eecd59d21b970f0bb7146be6c0bbd476a8b4b97 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:07:05 +0200 Subject: [PATCH 066/147] bug fix data types --- docs/source/cpp/data_types.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index 391e4ef8b0..5bcbe4c636 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -10,7 +10,7 @@ The follwing list expalins the non-standard data types that are used throughout * - Data type name - Description * - :code:`FP` - - A floating point type. Usually :code:`double` is used, but for instane in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see models/oseair and `examples/ode_seair_optimization.cpp `_. + - A floating point type. Usually :code:`double` is used, but for instane in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see :doc:` ` and `examples/ode_seair_optimization.cpp `_. * - :code:`UncertainValue` - This data type describes a value sampled from a given distribution. The value is intialized with a given :code:`FP` and can be (re)sampled with the :code:`draw_sample()` function. * - :code:`AgeGroup` @@ -18,7 +18,7 @@ The follwing list expalins the non-standard data types that are used throughout * - :code:`Region` - A typesafe ``size_t``. * - :code:`CustomIndexArray` - - An array whose values can be accesses by a multi-index. This datatype is for example used in the parameter :code:`mio::abm::TimeExposedToNoSymptoms` making it dependent on :code:`mio::abm::VirusVariant` and :code:`mio::AgeGroup`. Its values can then be set for a specific :code:`virus_variant` and :code:`age_group` using :code`model.parameters.template get()[{virus_variant, age_group}]`. + - An array whose values can be accesses by a multi-index. This datatype is for example used in the parameter :code:`mio::abm::TimeExposedToNoSymptoms` making it dependent on :code:`mio::abm::VirusVariant` and :code:`mio::AgeGroup`. Its values can then be set for a specific :code:`virus_variant` and :code:`age_group` using :code:`model.parameters.template get()[{virus_variant, age_group}]`. * - :code:`Populations` - Is a :code:`mio::CustomIndexArray` with :code:`mio::UncertainValue` as values. * - :code:`TimeSeries` From 2288296280dd5bafd5d19482df06ee0dbc408b00 Mon Sep 17 00:00:00 2001 From: Anna Wendler <106674756+annawendler@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:10:41 +0200 Subject: [PATCH 067/147] improve ide and lct usage and some capitalizing --- docs/source/citation.rst | 4 +- docs/source/cpp/ide.rst | 83 ++++++++------------------------ docs/source/cpp/ide_creation.rst | 2 +- docs/source/cpp/lct_creation.rst | 29 +++++++++-- docs/source/cpp/models/iseir.rst | 2 +- docs/source/cpp/sde.rst | 5 +- docs/source/cpp/sde_creation.rst | 2 +- docs/source/references.rst | 4 +- 8 files changed, 53 insertions(+), 78 deletions(-) diff --git a/docs/source/citation.rst b/docs/source/citation.rst index bacc2c2a5d..7a2bcea863 100644 --- a/docs/source/citation.rst +++ b/docs/source/citation.rst @@ -8,10 +8,10 @@ If you use MEmilio, please cite our work and, in particular, for - **Ordinary differential equation-based (ODE) and Graph-ODE models**: Zunker H, Schmieding R, Kerkmann D, Schengen A, Diexer S, et al. (2024). *Novel travel time aware metapopulation models and multi-layer waning immunity for late-phase epidemic and endemic scenarios*. *PLOS Computational Biology* 20(12): e1012630. `DOI:10.1371/journal.pcbi.1012630 `_ -- **Integro-differential equation-based (IDE) models**: Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `arXiv:2408.12228 `_ +- **Integro-differential equation-based (IDE) models**: Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Accepted for publication. `arXiv:2408.12228 `_ - **Agent-based models (ABMs)**: Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, et al. (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193: 110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ - **Hybrid agent-metapopulation-based models**: Bicker J, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `DOI:10.1016/j.idm.2024.12.015 `_ - **Graph Neural Networks**: Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2024).*Towards Graph Neural Network Surrogates Leveraging Mechanistic Expert Knowledge for Pandemic Response*. arXiv. `arXiv:2411.06500 `_ -- **ODE-based models with Linear Chain Trick**: Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ +- **ODE-based models with Linear Chain Trick**: Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Accepted for publication. `DOI:10.48550/arXiv.2412.09140 `_ - **Behavior-based ODE models**: Zunker H, Dönges P, Lenz P, Contreras S, Kühn MJ. (2025). *Risk-mediated dynamic regulation of effective contacts de-synchronizes outbreaks in metapopulation epidemic models*. *Chaos, Solitons & Fractals* 199:116782. `DOI:10.1016/j.chaos.2025.116782 `_ diff --git a/docs/source/cpp/ide.rst b/docs/source/cpp/ide.rst index 84a7d0f922..ddaba12703 100644 --- a/docs/source/cpp/ide.rst +++ b/docs/source/cpp/ide.rst @@ -9,7 +9,7 @@ In the following, we present the general structure of the IDE-SECIR model. The IDE-SEIR model follows a different structure, an introduction and a detailed example are provided in a separate section below. -Infection States +Infection states ---------------- Every model contains a list of **InfectionState**\s that define particular features of the subpopulations in the particular state. @@ -21,7 +21,7 @@ Every model contains a list of **InfectionState**\s that define particular featu `...` -Infection State Transitions +Infection state transitions --------------------------- Additionally, we define **InfectionTransition**\s that define the possible transitions between the **InfectionState**\s. @@ -34,7 +34,7 @@ When solving the model, we obtain results for the **InfectionTransition**\s as w `...` -Sociodemographic Stratification +Sociodemographic stratification ------------------------------- For the IDE-SECIR model, the population can also be stratified by one sociodemographic dimension. This dimension is denoted @@ -55,7 +55,7 @@ schools, workplaces, or homes. The matrices can be loaded or stored in the parti In the **ContactPatterns**, each matrix element stores baseline contact rates :math:`c_{i,j}` between sociodemographic group :math:`i` and group :math:`j`. The dimension of the matrix is automatically defined by the model initiation and it is reduced to one value if no stratification is used. The values can be adjusted during the simulation, e.g., through implementing -nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions`. +nonpharmaceutical interventions, see the section on :ref:`Nonpharmaceutical Interventions IDE`. An important feature of our IDE-based model is that we can choose the transition distributions in a flexible way. The default distribution is a smoother cosine function as it provides good testing qualities. For more realistic simulations, @@ -68,8 +68,7 @@ Parameters can get accessed via ``model.parameters.get>()`` an ``model.parameters.get>() = value``. - -Initial Conditions +Initial conditions ------------------ The initial conditions consist of a **TimeSeries** that contains the flows per time step for some time interval before @@ -79,8 +78,8 @@ the documentation of **StateAgeFunction**. Note that the last time point of the start time of the simulation. -.. _Nonpharmaceutical Interventions: -Nonpharmaceutical Interventions +.. _Nonpharmaceutical Interventions IDE: +Nonpharmaceutical interventions ------------------------------- Contact rates can be adjusted during the simulation to model nonpharmaceutical interventions (NPIs) such as lockdowns, @@ -130,76 +129,34 @@ To visualize the results of a simulation, you can use the Python package :doc:`m and its documentation. List of models ------------------------ +-------------- .. toctree:: :titlesonly: - models/iseir models/isecir IDE-SEIR model ---------------- - -Introduction -~~~~~~~~~~~~~ -The four compartments - -- `Susceptible` (:math:`S`), may become exposed at any time -- `Exposed` (:math:`E`), becomes infected after some time -- `Infected` (:math:`I`), will recover after some time -- `Recovered` (:math:`R`) - -are used to simulate the spread of the disease. - -Simulation -~~~~~~~~~~~ - -The simulation runs in discrete time steps using a trapezoidal rule. The model and the numerical scheme is based on the paper `"Modeling infectious diseases using integro-differential equations: Optimal -control strategies for policy decisions and Applications in COVID-19" by Keimer and Pflug, 2020 `_. - -For a detailed description and application of the model, see: - -Plötzke L (2021) Modellierung epidemischer Infektionskrankheiten auf der Basis von gewöhnlichen und Integro-Differentialgleichungen. Bachelor thesis, University of Cologne. https://elib.dlr.de/143504/ - -How to: Set up and run a simulation of the IDE-SEIR model -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -Here, we set the contact matrix used in the simulation. One can define multiple matrices for different locations. The size of each of these matrices is defined by the number of age groups. -Below, we use only one contact matrix for one location. As we only consider one age group in our example, we set the corresponding contact rate to :math:`10`. +-------------- -.. code-block:: cpp +The IDE-SEIR roughly follows the same structure but differs in a few points: - mio::ContactMatrixGroup contact_matrix = mio::ContactMatrixGroup(1, 1); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10.)); +- We do not consider infection state transitions but only infection states. +- The initial conditions are defined by the compartment sizes at the simulation start :math:`t_0` -To simulate the implementation of nonpharmaceutical interventions, we add dampings to the contact rate. Here, we apply a damping of :math:`0.7` after :math:`10` days, meaning that the contact rate is reduced to :math:`30%` of the initial value. +Note that the IDE-SEIR model is solved with a different numerical scheme than the IDE-SECIR model. For further information, +see :doc:`IDE-SEIR <./models/iseir>`. -.. code-block:: cpp - - contact_matrix[0].add_damping(0.7, mio::SimulationTime(10.)); - model.parameters.get>() = mio::UncertainContactMatrix(contact_matrix); - -After defining :math:`t_{\max}`, we can simulate, which means that we calculate the value for the compartment :math:`S`. - -.. code-block:: cpp - - int tmax = 15; - model.simulate(tmax); - -The values of the remaining compartments :math:`E`, :math:`I` and :math:`R` are calculated using the parameters ``LatencyTime`` and ``InfectiousTime`` and obtain a time series containing the values of all compartments. - -.. code-block:: cpp - - auto result = model.calculate_EIR(); +List of models +-------------- -Finally, we can print our results. +.. toctree:: + :titlesonly: + + models/iseir -.. code-block:: cpp - result.print_table({"S", "E", "I", "R"}); diff --git a/docs/source/cpp/ide_creation.rst b/docs/source/cpp/ide_creation.rst index ae4166f024..53314a59b6 100644 --- a/docs/source/cpp/ide_creation.rst +++ b/docs/source/cpp/ide_creation.rst @@ -1,4 +1,4 @@ -IDE Models +IDE models ========== Currently, the implemented solver for integro-differential equations is tailored to the SECIR-type model. From a theoretical point of view, this type of solver can also be applied to other epidemic IDE-based models. Implementing this into our software is work in progress. \ No newline at end of file diff --git a/docs/source/cpp/lct_creation.rst b/docs/source/cpp/lct_creation.rst index 1e17d7fb91..5a9a0f0f17 100644 --- a/docs/source/cpp/lct_creation.rst +++ b/docs/source/cpp/lct_creation.rst @@ -50,9 +50,8 @@ person stays infectious, :math:`T_I`, we define a struct: .. code-block:: cpp - template struct TimeInfectious { - using Type = UncertainValue; + using Type = UncertainValue; static Type get_default() { @@ -94,8 +93,8 @@ Finally, define a type :code:`Parameters` by listing all parameter structs as te .. code-block:: cpp template - using Parameters = mio::ParameterSet, RecoveryRate, LethalityRate, ContactRate, - TransmissionRisk>; + using Parameters = mio::ParameterSet; For more complex models, :code:`Parameters` allows passing arguments from its constructor to the :code:`get_default` functions. Make sure that all of these functions take the exact types as function arguments that you want to pass to @@ -142,11 +141,25 @@ Now we can define the model as a **CompartmentalModel** in the file "model.h": const auto N = y[InfectionState::Susceptible] + y[InfectionState::Infectious] + y[InfectionState::Recovered]; + ScalarType flow = 0; + + // Derivative for Susceptible compartment. dydt[InfectionState::Susceptible] = -params.template get>() * params.template get>() * y[InfectionState::Susceptible] * y[InfectionState::Infectious] / N; - + + // Derivative for subcompartments of Infectious compartment. + for (size_t i = 0; i < LctState::template get_num_subcompartments(); i++) { + flow = (ScalarType)LctState::template get_num_subcompartments() * + (1 / params.template get()) * + y[LctState::template get_first_index() + i]; + dydt[LctState::template get_first_index() + i] = + dydt[LctState::template get_first_index() + i] - flow; + dydt[LctState::template get_first_index() + i + 1] = flow; + } + . . . + } }; @@ -169,3 +182,9 @@ into subcompartments to a TimeSeries that conatins the simulation results accord For an example, see the implementation within the LCT-SECIR model. - A function ``check_constraints()`` that checks that the model satisfies sensible constraints regarding parameters and initial conditions. For an example, see the implementation within the LCT-SECIR model. + +.. dropdown:: :fa:`gears` Expert's settings + + To make the model more realistic, it is possible to stratify the population by sociodemographic dimensions auch as age groups. + For this, we can add a template parameter ``Group`` to the model and adapt the code accordingly. For an example, + see the implementation of the LCT-SECIR model. diff --git a/docs/source/cpp/models/iseir.rst b/docs/source/cpp/models/iseir.rst index 001b5d2d76..96c4e98a1d 100644 --- a/docs/source/cpp/models/iseir.rst +++ b/docs/source/cpp/models/iseir.rst @@ -2,7 +2,7 @@ IDE-based SEIR-type model ========================= The IDE-based SEIR type module models and simulates an epidemic using integro-differential equations. The model is -particularly suited for pathogens a latent infection state. The model assumes perfect immunity after recovery and is +particularly suited for pathogens with a latent infection state. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. In the following, we present the model in detail. Note that this model is solved differently tha the IDE-SECIR model and the structure presented in :doc:`IDE models <../ide>` applies only partially. diff --git a/docs/source/cpp/sde.rst b/docs/source/cpp/sde.rst index b638356d7c..72b3cf267e 100644 --- a/docs/source/cpp/sde.rst +++ b/docs/source/cpp/sde.rst @@ -1,4 +1,4 @@ -SDE Models +SDE models ========== @@ -8,8 +8,7 @@ SDE Models List of models ------------------------------ - +-------------- .. toctree:: :titlesonly: diff --git a/docs/source/cpp/sde_creation.rst b/docs/source/cpp/sde_creation.rst index 53a0f1ea3c..d9dfca6d4a 100644 --- a/docs/source/cpp/sde_creation.rst +++ b/docs/source/cpp/sde_creation.rst @@ -1,2 +1,2 @@ -SDE Model Creation +SDE model creation ================== \ No newline at end of file diff --git a/docs/source/references.rst b/docs/source/references.rst index 2f9172e6d3..24ad0b600b 100644 --- a/docs/source/references.rst +++ b/docs/source/references.rst @@ -7,8 +7,8 @@ Recently Submitted Publications -------------------------------------- - Schmidt A, Zunker H, Heinlein A, Kühn MJ. (2025). *Graph Neural Network Surrogates to leverage Mechanistic Expert Knowledge towards Reliable and Immediate Pandemic Response*. arXiv. `arXiv:2411.06500 `_ -- Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Submitted for publication. `arXiv:2408.12228 `_ -- Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Submitted for publication. `DOI:10.48550/arXiv.2412.09140 `_ +- Wendler AC, Plötzke L, Tritzschak H, Kühn MJ. (2024). *A nonstandard numerical scheme for a novel SECIR integro differential equation-based model with nonexponentially distributed stay times*. Accepted for publication. `arXiv:2408.12228 `_ +- Plötzke L, Wendler A, Schmieding R, Kühn MJ. (2024). *Revisiting the Linear Chain Trick in epidemiological models: Implications of underlying assumptions for numerical solutions*. Accepted for publication. `DOI:10.48550/arXiv.2412.09140 `_ Peer-Reviewed Publications From 2fbe04371e6d7745778816876477b23469e6411f Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:21:12 +0200 Subject: [PATCH 068/147] CHG: rename development guidelines to developer workflow --- docs/source/development.rst | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/source/development.rst b/docs/source/development.rst index 0fb2dd7e2d..5e9e984539 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -1,4 +1,4 @@ -Development guidelines +Developer workflow ======================== We are always happy about contributions to the project! Here you can find more information on our coding guidelines, our git workflow, benchmarking our models and writing documentation. @@ -345,13 +345,6 @@ To honor original authors as well as reviewers and their suggestions, reviewers The full list of labels that should be used to identify issues can be found at: https://github.com/DLR-SC/memilio/labels -Agent-based model development ------------------------------------------------- - -If you add new features to the agent-based model, please make sure to run the benchmarks and check if the performance is -acceptable. See the section on :ref:`performance-monitoring-cpp` for more information. - - Documentation -------------------- From 64a1d3b6929c894be5a83cb5573c4f57f7264f55 Mon Sep 17 00:00:00 2001 From: HenrZu <69154294+HenrZu@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:36:36 +0200 Subject: [PATCH 069/147] logos --- docs/source/team.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/team.rst b/docs/source/team.rst index 59ab1f9498..b8fc298997 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -95,6 +95,7 @@ Acknowledgments --------------- MEmilio has been supported by various project grants. Since 2020, MEmilio has been funded + * by the Initiative and Networking Fund of the Helmholtz Association of German Research Institutions under grant agreement number KA1-Co-08 (Project LOKI-Pandemics), * by the German Federal Ministry for Digital and Transport under grant agreement FKZ19F2211A and FKZ19F2211B (Project PANDEMOS), * by the Helmholtz School for Data Science in Life, Earth and Energy (HDS-LEE), @@ -115,11 +116,11 @@ MEmilio has been supported by various project grants. Since 2020, MEmilio has be :alt: Bundesministerium für Digitales und Verkehr .. |hdslee| image:: _static/funder/hdslee.png - :width: 200px + :width: 250px :alt: Helmholtz School for Data Science in Life, Earth and Energy .. |mfund| image:: _static/funder/mfund.png - :width: 200px + :width: 150px :alt: mFUND -|helmholtz| |bmbf| |bmdv| |hdslee| |mfund| +|helmholtz| |bmdv| |mfund| |bmbf| |hdslee| From 9e3b7e372bcdbdd4d67800ae90123c441fa8e655 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:45:13 +0200 Subject: [PATCH 070/147] CHG: Fix data types formatting --- docs/source/cpp/data_types.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index 5bcbe4c636..2e1b5b6130 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -10,11 +10,11 @@ The follwing list expalins the non-standard data types that are used throughout * - Data type name - Description * - :code:`FP` - - A floating point type. Usually :code:`double` is used, but for instane in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see :doc:` ` and `examples/ode_seair_optimization.cpp `_. + - A floating point type. Usually :code:`double` is used, but for instane in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see :doc:`models/oseair` and `examples/ode_seair_optimization.cpp `_. * - :code:`UncertainValue` - This data type describes a value sampled from a given distribution. The value is intialized with a given :code:`FP` and can be (re)sampled with the :code:`draw_sample()` function. * - :code:`AgeGroup` - - A typesafe ``size_t``, i.e. an integer that cannot be confused with other integer types so operations like assignment, addition etc. only work with other :code:`AgeGroup`s. + - A typesafe ``size_t``, i.e. an integer that cannot be confused with other integer types so operations like assignment, addition etc. only work with other :code:`AgeGroup`\s. * - :code:`Region` - A typesafe ``size_t``. * - :code:`CustomIndexArray` @@ -42,10 +42,10 @@ The follwing list expalins the non-standard data types that are used throughout * - :code:`MobilityEdge` - Represents mobility between two nodes in a graph. Handles the movement of populations between nodes, tracks mobile populations, and applies mobility returns according to epidemiological models. * - :code:`DampingMatrixExpression` - - Represents a coefficient-wise matrix expression B - D * (B - M), where B is a baseline matrix, M is a minimum matrix, and D is a time-dependent complex damping factor. Used as the base for time-dependent contact matrices. + - Represents a coefficient-wise matrix expression :math:`B - D * (B - M)`, where :math:`B` is a baseline matrix, :math:`M` is a minimum matrix, and :math:`D` is a time-dependent complex damping factor. Used as the base for time-dependent contact matrices. * - :code:`DampingMatrixExpressionGroup` - - Represents a collection of DampingMatrixExpressions that are summed up. Used for representing multiple sources of contacts or mobility. + - Represents a collection of ``DampingMatrixExpression``\s that are summed up. Used for representing multiple sources of contacts or mobility. * - :code:`ContactMatrix` - - Time-dependent contact frequencies between groups, derived from DampingMatrixExpression. Models how the contact rates between different age groups change over time due to interventions. + - Time-dependent contact frequencies between groups, derived from ``DampingMatrixExpression``. Models how the contact rates between different age groups change over time due to interventions. * - :code:`ContactMatrixGroup` - - A collection of contact matrices that represent different contexts (e.g., home, school, work) whose sum is the total number of contacts, derived from DampingMatrixExpressionGroup. + - A collection of contact matrices that represent different contexts (e.g., home, school, work) whose sum is the total number of contacts, derived from ``DampingMatrixExpressionGroup``. From 23a8bca12c0980819dbee3000b29a67de8d8f320 Mon Sep 17 00:00:00 2001 From: Anna Wendler <106674756+annawendler@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:51:16 +0200 Subject: [PATCH 071/147] minor changes in data_types --- docs/source/cpp/data_types.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/cpp/data_types.rst b/docs/source/cpp/data_types.rst index 5bcbe4c636..c31a16ea03 100644 --- a/docs/source/cpp/data_types.rst +++ b/docs/source/cpp/data_types.rst @@ -1,7 +1,7 @@ Common data types ----------------- -The follwing list expalins the non-standard data types that are used throughout MEmilio. +The following list explains the nonstandard data types that are used throughout MEmilio. .. list-table:: :header-rows: 1 @@ -10,7 +10,7 @@ The follwing list expalins the non-standard data types that are used throughout * - Data type name - Description * - :code:`FP` - - A floating point type. Usually :code:`double` is used, but for instane in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see :doc:` ` and `examples/ode_seair_optimization.cpp `_. + - A floating point type. Usually :code:`double` is used, but for instance in the optimization using optimal control :code:`FP` is equal to :code:`Ipopt::Number`, see :doc:` ` and `examples/ode_seair_optimization.cpp `_. * - :code:`UncertainValue` - This data type describes a value sampled from a given distribution. The value is intialized with a given :code:`FP` and can be (re)sampled with the :code:`draw_sample()` function. * - :code:`AgeGroup` @@ -18,19 +18,19 @@ The follwing list expalins the non-standard data types that are used throughout * - :code:`Region` - A typesafe ``size_t``. * - :code:`CustomIndexArray` - - An array whose values can be accesses by a multi-index. This datatype is for example used in the parameter :code:`mio::abm::TimeExposedToNoSymptoms` making it dependent on :code:`mio::abm::VirusVariant` and :code:`mio::AgeGroup`. Its values can then be set for a specific :code:`virus_variant` and :code:`age_group` using :code:`model.parameters.template get()[{virus_variant, age_group}]`. + - An array whose values can be accessed by a multi-index. This data type is for example used in the parameter :code:`mio::abm::TimeExposedToNoSymptoms` making it dependent on :code:`mio::abm::VirusVariant` and :code:`mio::AgeGroup`. Its values can then be set for a specific :code:`virus_variant` and :code:`age_group` using :code:`model.parameters.template get()[{virus_variant, age_group}]`. * - :code:`Populations` - Is a :code:`mio::CustomIndexArray` with :code:`mio::UncertainValue` as values. * - :code:`TimeSeries` - Stores vectors of values at time points. Each time point has a vector of values of the same size with operations like adding time points, retrieving values, exporting to CSV, etc. It's also used for storing and analyzing simulation results over time. * - :code:`Graph` - - A generic graph structure that represents a network of nodes connected by edges. Each node and edge can have associated properties. The Graph is used to model geographical regions connected by mobility patterns (e.g., commuting), where each node is represented by its own epidemiological model. + - A generic graph structure that represents a network of nodes connected by edges. Each node and edge can have associated properties. The graph is used to model geographical regions connected by mobility patterns (e.g., commuting), where each node is represented by its own epidemiological model. * - :code:`Node` - - Represents a node in a Graph with a unique ID and associated properties. + - Represents a node in a graph with a unique ID and associated properties. * - :code:`Edge` - - Represents a directed connection between two nodes in a Graph with associated properties. + - Represents a directed connection between two nodes in a graph with associated properties. * - :code:`EdgeBase`, :code:`InEdgeBase`, :code:`OutEdgeBase` - - Base classes for Edge that define start and end node indices for connections in the Graph. + - Base classes for Edge that define start and end node indices for connections in the graph. * - :code:`SimulationNode` - Represents a simulation in one node of a mobility graph. Contains a simulation model of any type and keeps track of the last state and time point. * - :code:`MobilityCoefficients` @@ -38,7 +38,7 @@ The follwing list expalins the non-standard data types that are used throughout * - :code:`MobilityCoefficientGroup` - A collection of time-dependent mobility coefficients that differentiate between various sources of mobility. * - :code:`MobilityParameters` - - Parameters that influence mobility between nodes, including coefficients and dynamic non-pharmaceutical interventions (NPIs). + - Parameters that influence mobility between nodes, including coefficients and dynamic nonpharmaceutical interventions (NPIs). * - :code:`MobilityEdge` - Represents mobility between two nodes in a graph. Handles the movement of populations between nodes, tracks mobile populations, and applies mobility returns according to epidemiological models. * - :code:`DampingMatrixExpression` From 9a1bbc48b9feb49f40b92b1e58e3af921b7ff97d Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:53:26 +0200 Subject: [PATCH 072/147] Personal section Julia --- docs/source/team.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/source/team.rst b/docs/source/team.rst index b8fc298997..68dd8d1db5 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -67,6 +67,31 @@ Henrik Zunker is a PhD student at the Institute for Software Technology at the G * `Google Scholar Profile `_ * `ORCID Profile: 0000-0002-9825-365X `_ +.. _developer_3: + +Julia Bicker +^^^^^^^^^^^^^^^^^^ + +.. image:: _static/team/max_mustermann.jpg + :alt: Julia Bicker + :width: 150px + :align: left + :class: developer-photo + +**Research Focus:** Mathematical Modeling, Hybrid Modeling, High-Performance Computing, Agent-based Modeling, Ordinary Differential Equations (ODEs) + +Julia Bicker is a PhD student at the Institute for Software Technology at the German Aerospace Center (DLR) since 2022. She focuses on the development of hybrid models that combine individual-based and population-based models, namely stochastic agent-based and ODE-based metapopulation models. + +**Selected Publications:** + +* **Bicker J**, Schmieding R, Meyer-Hermann M, Kühn MJ. (2025). *Hybrid metapopulation agent-based epidemiological models for efficient insight on the individual scale: A contribution to green computing*. *Infectious Disease Modelling* 10(2): 571-590. `DOI:10.1016/j.idm.2024.12.015 `_ +* Schmid N, **Bicker J** , Hofmann AF, Wallrafen-Sam K, Kerkmann D, Wieser A, Kühn MJ, Hasenauer J (2025). *Integrative Modeling of the Spread of Serious Infectious Diseases and Corresponding Wastewater Dynamics*. *Epidemics* 51:100836. `DOI:10.1016/j.epidem.2025.100836 `_ + +**Links:** + +* `Google Scholar Profile `_ +* `ORCID Profile: 0000-0001-9382-4209 `_ + ---- .. _contributors: From 0de8fb9ca6fb931149adaf8c75e7d8a6ddc99aa4 Mon Sep 17 00:00:00 2001 From: David Kerkmann <44698825+DavidKerkmann@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:53:54 +0200 Subject: [PATCH 073/147] ABM doc changes --- docs/source/cpp/mobility_based_abm.rst | 99 ++++++++++++-------------- 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/docs/source/cpp/mobility_based_abm.rst b/docs/source/cpp/mobility_based_abm.rst index 8fd53d15fa..969f0de625 100644 --- a/docs/source/cpp/mobility_based_abm.rst +++ b/docs/source/cpp/mobility_based_abm.rst @@ -2,51 +2,33 @@ Agent-based model ================= This module models and simulates the epidemic using an agent-based model (*ABM*) approach. Unlike the compartmental models that use a system of ODEs, this model simulates -the spread of COVID-19 in a population with discrete persons (the agents) moving throughout locations in the +the spread of an epidemic in a population with discrete persons (the agents) moving throughout locations in the model and interacting with (infecting) each other. For a detailed overview of the ABM, see: - Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, et al. (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193: 110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ -Introduction ------------ - -The model is implemented in multiple classes and header files located in the ``/cpp/models/abm/`` directory. The core classes and their locations are: - -- ``Person`` (person.h): Represents individual agents in the simulation -- ``Infection`` (infection.h): Manages infection dynamics and disease progression -- ``Location`` (location.h): Defines places where agents interact -- ``Model`` (model.h): Coordinates all components of the simulation -- ``Simulation`` (simulation.h): Executes the simulation logic - -The following sections outline the major features of the agent-based model. Structure ~~~~~~~~~ -The model consists of the following major classes: +The model is implemented in multiple classes. Source and header fiels are located in the ``/cpp/models/abm/`` directory. While many files contain supporting implementation or additional features, it is important to understand the main workflow and core classes. +The core classes and their locations are: + +- ``Simulation`` (simulation.h): Runs the simulation and stores results. The model is evolved in discrete time steps of the same size which can be chosen by the user. +- ``Model`` (model.h): Collection of all persons, locations and used parameters. Is initialized with the number of age groups that are considered. It also holds information about the testing strategy of the simulation and holds the rules for the mobility phase. +- ``Model Functions`` (model_functins.h): A collection of model functions that mostly cover interaction of agents at locations. These functions are called in the model evolve function. +- ``Parameters`` (parameters.h): Collection of all parameters used in the model. +- ``Location`` (location.h): Represents places in the model where people meet and interact, e.g. home, school, work, social event sites. Along their type, locations contain an ID and a geographical location (longitude and latitude). A location can be split into cells to model parts of a location, like classrooms in a school. Some infection parameters are location-specific and can be set per location. Manditory masks can be activated to simulate a mask obligation intervention. +- ``Person`` (person.h): Represents an agent of the model. A person has an ID, is associated with an age group and has and a list with their assigned locations (i.e. the locations they can visit during the simulation). Every person has lists with past and current infections as well as vaccinations. Further, more information on the personal behavior and test results is available. +- ``Infection`` (infection.h): Collection of all information about a person’s infection, i.e. infectiousness, infection course and symptoms, virus variant. The infection course is drawn stochastically from the infection states that are similar to the compartments of the SECIR model and is explained in detail below. -1. **Person**: Represents an agent of the model. A person has an ID and a list with their assigned locations (i.e. the locations they visit during the simulation). They can perform - tests and wear masks. Every person has lists with past and current infections and vaccinations. - -2. **Infection**: Collection of all information about a person's infection, i.e. infectiousness, infection course, - virus variant. The infection course is drawn stochastically from the infection states that are similar to the - compartments of the SECIR model. - -3. **Location**: Represents places in the model where people meet and interact, e.g. home, school, work, social event - sites. A location can be split into cells to model parts of a location, like classrooms in a school. Some infection - parameters are location-specific and one can activate NPIs like mandatory masks or tests to enter the location. - -4. **Model**: Collection of all persons and locations. It also holds information about the testing strategy of the - simulation and holds the rules for the mobility phase. - -5. **Simulation**: Runs the simulation and stores results. Disease progression ~~~~~~~~~~~~~~~~~~ The ABM implements a detailed disease progression model that captures the full course of an infection from exposure to resolution. The disease progression is modeled through the ``Infection`` class, which contains: -1. **Infection States**: Similar to the SECIR model, an infected person progresses through states defined in ``infection_state.h``: +1. **Infection States**: Similar to the equation based models, an infected person progresses through states defined in ``infection_state.h``: * **Susceptible**: Initial state before infection * **Exposed**: Infected but not yet infectious @@ -57,35 +39,39 @@ The ABM implements a detailed disease progression model that captures the full c * **Recovered**: Recovered from infection with immunity * **Dead**: Deceased due to infection +Stochastic Transitions: + Agents traverse the infection states from Susceptible to Recovered or Dead. Recovery is possible from every Infected State (NoSymptoms, Symptoms, Severe, Critical) and Dead is possible from InfetedSevere and InfectedCritical. + Progression between states is stochastic, with age-dependent probabilities. The duration in each state is drawn from distributions. + 2. **Viral Load Dynamics**: The model implements realistic viral load curves based on scientific data: * **Incline Phase**: Rapid increase in viral concentration * **Peak**: Maximum viral load * **Decline Phase**: Gradual decrease until clearance -3. **Infectiousness**: The probability of transmitting the virus depends on viral load through an invlogit function. + Viral shed: The amount of virus that is shed by an agent depends on the viral load and individual factors through an invlogit function. -4. **Stochastic Transitions**: Progression between states is stochastic, with age-dependent probabilities: +3. **Dependencies**: Both infection state and viral load parameters depend on: - * The duration in each state is drawn from distributions in the model parameters - * Prior immunity (from vaccination or previous infection) affects: + * Age group of the person + * Virus variant + * Protection status (prior immunity) + * Random factors (individual variation) + + In particular, prior immunity (from vaccination or previous infection) affects: * Viral load (reduced peak) * Severity progression (reduced probability of severe outcomes) * Duration of infectious period - -5. **Infection Course**: The infection course is determined by: + * Probability of being infected (again) - * Age group of the person - * Virus variant - * Protection status (prior immunity) - * Random factors (individual variation) +4. **Disease spread**: During interactions, agents can infected each other. The viral shed is used in combination with further personal information and contact details to feed into a stochastic process that determines if the virus is transmitted and a new agent becomes infected. The chosen time step of the model has no impact on the expected amount of transmissions per time. Data collection ~~~~~~~~~~~~~~~~~~ - -The ABM simulation can collect data through the ``History`` object, which allows for flexible data logging. This is particularly -useful for analyzing results after the simulation has completed. There are multiple types of data that can be collected: +The ABM simulation can collect data through the ``History`` object, which allows for flexible data logging and writing. +A collection of often used loggers and writers is available in common_abm_loggers.h, but users can define their own loggers and writers to satisfy their individual needs. +This is particularly useful for analyzing results after the simulation has completed. There are multiple types of data that can be collected: 1. **Time Series Data**: Track how infection states change over time @@ -118,6 +104,8 @@ The ABM supports various interventions that can be applied at specific time poin 3. **Lockdowns**: Restrict movement between locations +Examples for usage can be found below. + Simulation ---------- @@ -137,14 +125,14 @@ Mobility phase During the mobility phase, each person may change their location. Mobility follows `rules `_, considering the current location, time of day, and properties of the person (e.g. age). -Some location changes are deterministic and regular (e.g. going to work), while others are random (e.g. going shopping or to a +The mobility rules use the assigned locations of the persons. Some location changes are deterministic and regular (e.g. going to work), while others are random (e.g. going shopping or to a social event in the evening/on the weekend). When agents are infected, they are quarantined and cannot change their location. You can restrict some mobility rules by allowing only a proportion of people to enter specific locations. Another way of mobility is using trips. A trip consists of the ID of the person that performs this trip, a time point when this trip is performed, and the destination. -At the beginning of the simulation, a list with all trips is initialized and followed during the simulation. There can be different -trips on the weekend than during the week, but other than that, the agents do the same trips every day. As before, agents that are -in quarantine or in the hospital cannot change their location. +At the beginning of the simulation, a list with all trips is initialized and followed during the simulation. The agents do the same trips every day. As before, agents that are +in quarantine or in the hospital cannot change their location. Trips can be used even for locations that are not the assigned locations for the respective person. + How to ----------- @@ -170,11 +158,11 @@ With this number we create an empty model: auto model = mio::abm::Model(num_age_groups); We can set several general parameters, which you can find `here `_. Here is an example where we set the -duration of the incubation period to 4 days: +duration of the time in the InfectedSymptoms state to the InfectedSevere state to 4 days: .. code-block:: cpp - model.parameters.get() = 4.; + model.parameters.get() = 4.; Locations and persons ~~~~~~~~~~~~~~~~~~~~~ @@ -196,14 +184,15 @@ For more complex location configurations, the model allows setting location-spec .. code-block:: cpp - // Add one social event with 5 maximum contacts + // Add one social event with 5 maximum contacts (local) auto event = model.add_location(mio::abm::LocationType::SocialEvent); model.get_location(event).get_infection_parameters().set(5); - // Increase aerosol transmission for all locations + // Increase aerosol transmission for all locations (global) model.parameters.get() = 10.0; - // Increase contact rate for specific age groups at work + // Increase contact rate for specific age groups at a specific work location (local) + auto work = model.add_location(mio::abm::LocationType::Work); model.get_location(work) .get_infection_parameters() .get()[{age_group_15_to_34, age_group_15_to_34}] = 10.0; @@ -256,7 +245,7 @@ During the simulation, people can get tested, and we have to specify the scheme Initializing infections ~~~~~~~~~~~~~~~~~~~~~~ -For infections to happen during the simulation, we have to initialize people with infections: +For infections to happen during the simulation, we have to initialize people with infections. Here, we iterate over all persons of the model and initialize them with random infection states according to a discrete distribution, i.e., 50% of persons are initialized as Susceptible, 30% as Exposed, etc. .. code-block:: cpp @@ -276,7 +265,7 @@ For infections to happen during the simulation, we have to initialize people wit Running the simulation ~~~~~~~~~~~~~~~~~~~~~ -Finally, we run the simulation: +Here, we run the simulation: .. code-block:: cpp @@ -302,7 +291,7 @@ Then we can run the simulation with the history object and access the data throu sim.advance(tmax, history); auto log = history.get_log(); -Finally, we can print the data to a text file: +Finally, for example, we can print the data to a text file: .. code-block:: cpp From e134c0ffa370d13ad2484984fb25331c9e615912 Mon Sep 17 00:00:00 2001 From: Carlotta Gerstein Date: Mon, 28 Jul 2025 16:08:18 +0200 Subject: [PATCH 074/147] Personal Section Carlotta --- docs/source/team.rst | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/source/team.rst b/docs/source/team.rst index 68dd8d1db5..1faa5a8ec0 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -11,7 +11,7 @@ Core Developers .. _developer_1: Martin Kühn -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^ .. image:: https://www.martinkuehn.eu/research/kuehn150x150.jpg :alt: Martin Kühn @@ -44,7 +44,7 @@ Martin Kühn studied at University of Cologne, Germany, and Université de Montr .. _developer_2: Henrik Zunker -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^ .. image:: _static/team/max_mustermann.jpg :alt: Henrik Zunker @@ -70,7 +70,7 @@ Henrik Zunker is a PhD student at the Institute for Software Technology at the G .. _developer_3: Julia Bicker -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^ .. image:: _static/team/max_mustermann.jpg :alt: Julia Bicker @@ -92,6 +92,29 @@ Julia Bicker is a PhD student at the Institute for Software Technology at the Ge * `Google Scholar Profile `_ * `ORCID Profile: 0000-0001-9382-4209 `_ +.. _developer_4: + +Carlotta Gerstein +^^^^^^^^^^^^^^^^^ + +.. image:: _static/team/max_mustermann.jpg + :alt: Carlotta Gerstein + :width: 150px + :align: left + :class: developer-photo + +**Research Focus:** Agent-based modeling, Metapopulation models, Epidemiological modeling + +Carlotta Gerstein completed her Bachelor's degree in Mathematics at the University of Bonn. To explore more applied areas, she continued with a Master's in Mathematics at the University of Cologne, where she focused on High Performance Computing. During her studies, she worked as a student assistant at the German Aerospace Center (DLR) in the Department of High-Performance Computing, where she focused on agent-based and metapopulation models to simulate the spatial spread of infectious diseases. In April 2025, she joined the research group of Prof. Jan Hasenauer as a PhD student at the University of Bonn. + +**Selected Publications:** + +* Kerkmann D, Korf S, Nguyen K, Abele D, Schengen A, **Gerstein C**, Göbbert JH, Basermann A, Kühn MJ, Meyer-Hermann M (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193:110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ + +**Links:** + +* `ORCID Profile: 0009-0004-4410-0502 `_ + ---- .. _contributors: From bf0b4010aa12da4898540493c859bd791e7304ef Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:12:23 +0000 Subject: [PATCH 075/147] Changes to title in python interface --- docs/source/python/coupling_.rst | 0 docs/source/python/memilio_epidata.rst | 4 ++-- docs/source/python/memilio_plot.rst | 4 ++-- docs/source/python/memilio_simulation.rst | 4 ++-- docs/source/python/model_usage.rst | 24 ++++++++++++----------- docs/source/python/python_packages.rst | 17 +++++++++------- 6 files changed, 29 insertions(+), 24 deletions(-) create mode 100644 docs/source/python/coupling_.rst diff --git a/docs/source/python/coupling_.rst b/docs/source/python/coupling_.rst new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/source/python/memilio_epidata.rst b/docs/source/python/memilio_epidata.rst index 33da178850..4d397439a8 100644 --- a/docs/source/python/memilio_epidata.rst +++ b/docs/source/python/memilio_epidata.rst @@ -1,5 +1,5 @@ -MEmilio Epidata Package -======================= +MEmilio Epidata +================ MEmilio Epidata provides modules and scripts to download epidemiological data from various different sources. The package as well as links to the sources can be found in the `pycode/memilio-epidata `_. diff --git a/docs/source/python/memilio_plot.rst b/docs/source/python/memilio_plot.rst index b5a8336464..840e54adc8 100644 --- a/docs/source/python/memilio_plot.rst +++ b/docs/source/python/memilio_plot.rst @@ -1,5 +1,5 @@ -MEmilio Plot Package -======================= +MEmilio Plot +============= MEmilio Plot provides modules and scripts to plot epidemiological or simulation data as returned by other packages of the MEmilio software. diff --git a/docs/source/python/memilio_simulation.rst b/docs/source/python/memilio_simulation.rst index 2b67410587..a5572b95c2 100644 --- a/docs/source/python/memilio_simulation.rst +++ b/docs/source/python/memilio_simulation.rst @@ -1,5 +1,5 @@ -MEmilio Simulation Package -========================== +MEmilio Simulation +=================== MEmilio Simulation contains Python bindings for the MEmilio C++ library. It enables setting up and running simulations of infectious disease dynamics from a Python interface. diff --git a/docs/source/python/model_usage.rst b/docs/source/python/model_usage.rst index 1316153bd6..6457023a70 100755 --- a/docs/source/python/model_usage.rst +++ b/docs/source/python/model_usage.rst @@ -12,7 +12,7 @@ changes to create a more pythonic interface. We will follow the example of an ODE SEIR model starting with a comparison of the model initialization between Python and C++. -Define Model +Define the model ------------ .. grid:: 1 1 2 2 @@ -38,17 +38,16 @@ Define Model #include "ode_seir/model.h" int num_groups = 1; - mio::oseir::Model model(num_groups); + mio::oseir::Model model(num_groups); +Initialize parameters +--------------------- Next, the parameters should be defined of the scenario you want to model. The model has the possibility to incorporate age stratification, which leads to many of the parameters having values for each age group. The AgeGroup is used for indexing. For the example model with a single age group defined by num_groups the parameter definitions follow as -Initialize Parameters ---------------------- - .. code-block:: python import memilio.simulation as mio @@ -65,7 +64,7 @@ Initialize Parameters AgeGroup(0) defines the first age group. For a model with more than one age group, we could index the other groups with AgeGroup(1), AgeGroup(2), .... -Initial Conditions +Initial conditions ------------------- We also need to define the inital states of the population. They are not only divided through an age group, @@ -88,6 +87,12 @@ of the age group. Nonpharmaceutical interventions ------------------------------- + +.. code-block:: python + + integrator = mio.RKIntegratorCore(dt_max=1) + result = oseir.simulate(0, days, dt, model, integrator) + Simulation ---------- @@ -111,7 +116,7 @@ The integrator can be changed as the last parameter of the simulate function. integrator = mio.RKIntegratorCore(dt_max=1) result = oseir.simulate(0, days, dt, model, integrator) -Output and Visualization +Output and visualization ------------------------- The result returned from the simulation is a TimeSeries object containing the number of people per age group in each infection state at each time step. @@ -125,14 +130,11 @@ pythonic interface. Now you can use the usual data handling options and make us of the easy visualization tools that are part of Python. Some plotting functions specific to MEmilio and created as part of the project are combined in the :doc:`MEmilio Plot Package `. -Additional Ressources +Additional ressources --------------------- Further examples are provided at `examples/simulation `_. They include the usage of a FlowModel, introducing a graph model for regional differences or parameter studies for simulating under uncertainty. -Limitations ------------ -Lastly Limitations with introduction of model creation diff --git a/docs/source/python/python_packages.rst b/docs/source/python/python_packages.rst index 205f6ef154..843db1ea5a 100644 --- a/docs/source/python/python_packages.rst +++ b/docs/source/python/python_packages.rst @@ -1,4 +1,4 @@ -Python Packages +Overview =============== MEmilio contains a plethora of python modules containing tools to expand on the main C++. @@ -8,13 +8,13 @@ Most of them serve their own use case, :gutter: 2 3 4 4 .. grid-item-card:: - :img-top: ../../memilio-small.png + :img-top: https://github.com/user-attachments/assets/b95b8803-0fdc-4d80-88cc-fcb2643c3e8f :text-align: center MEmilio Python Bindings ^^^ - This module provides a python interface for parts of the C++ main library, + This package provides a python interface for parts of the C++ main library, with the goal of exposing fast mathematical-epidemiological models to a bigger user base. @@ -28,13 +28,13 @@ Most of them serve their own use case, To the python bindings .. grid-item-card:: - :img-top: ../../memilio-small.png + :img-top: https://github.com/user-attachments/assets/ca098013-ec8a-4fe9-964c-f1881b7382de :text-align: center Epidata Tool ^^^ - The memilio-epidata package provides tools to download and structure important + This package provides tools to download and structure important data such as infection or mobility data. +++ @@ -51,12 +51,13 @@ Most of them serve their own use case, :gutter: 2 3 4 4 .. grid-item-card:: + :img-top: https://github.com/user-attachments/assets/064a55a0-7054-4421-a026-353f8f4cc478 :text-align: center Surrogate Models ^^^ - Expanding on AI + This package contains machine learning based surrogate models that make predictions based on the MEmilio simulation models. +++ @@ -68,12 +69,13 @@ Most of them serve their own use case, To the intro of surrogate models .. grid-item-card:: + :img-top: https://github.com/user-attachments/assets/81659df6-826c-4a34-83c0-a20c32d1d266 :text-align: center Visualization ^^^ - Plot of data + Generalized visualization functions for MEmilio specific plots. +++ @@ -85,6 +87,7 @@ Most of them serve their own use case, To the visualization .. grid-item-card:: + :img-top: https://github.com/user-attachments/assets/0c23a9a1-5b78-477f-981e-6bb7993fc80d :text-align: center Generating Bindings From 435a1022efde032ac3c5f47f6e9a8d1c25b2c4c0 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:12:47 +0000 Subject: [PATCH 076/147] more changes in python interface --- docs/source/python/memilio_generation.rst | 4 ++-- docs/source/python/memilio_surrogate.rst | 4 ++-- docs/source/python/model_usage.rst | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/source/python/memilio_generation.rst b/docs/source/python/memilio_generation.rst index 6747d15795..3f29b70f8b 100644 --- a/docs/source/python/memilio_generation.rst +++ b/docs/source/python/memilio_generation.rst @@ -1,5 +1,5 @@ -MEmilio Generation Package -========================== +MEmilio Generation +=================== This package provides an automatic code generator for python bindings of the MEmilio C++ library. It enables the automatic generation of a part of the :doc:`Python Bindings ` that is common across multiple models. diff --git a/docs/source/python/memilio_surrogate.rst b/docs/source/python/memilio_surrogate.rst index 2a94fa46b7..a3261092f0 100644 --- a/docs/source/python/memilio_surrogate.rst +++ b/docs/source/python/memilio_surrogate.rst @@ -1,5 +1,5 @@ -MEmilio Surrogate Model Package -=============================== +MEmilio Surrogate Model +======================== MEmilio Surrogate Model contains machine learning based surrogate models that make predictions based on the MEmilio simulation models. Currently there are only surrogate models for ODE-type models. The simulations of these models are used for data generation. diff --git a/docs/source/python/model_usage.rst b/docs/source/python/model_usage.rst index 6457023a70..909800fdbe 100755 --- a/docs/source/python/model_usage.rst +++ b/docs/source/python/model_usage.rst @@ -9,11 +9,13 @@ For expanding the bindings with new models look into the section Generally, the package is following the structure of the main C++ library to make it easy to understand and comparable, while introducing changes to create a more pythonic interface. We will follow the example of an -ODE SEIR model starting with a comparison of the model initialization between -Python and C++. +ODE SEIR model. -Define the model ------------- +Define infectious disease model +-------------------------------- + +Following is a comparison of the model initialization between +Python and C++ to better understand the differences between both interfaces. .. grid:: 1 1 2 2 From 6abd506957d0dc28ed7ca4482382445acc6d5ba3 Mon Sep 17 00:00:00 2001 From: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com> Date: Mon, 28 Jul 2025 14:35:47 +0000 Subject: [PATCH 077/147] Personal page Max --- docs/source/team.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/source/team.rst b/docs/source/team.rst index 1faa5a8ec0..bb72b9cb95 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -115,7 +115,18 @@ Carlotta Gerstein completed her Bachelor's degree in Mathematics at the Universi * `ORCID Profile: 0009-0004-4410-0502 `_ ----- +Maximilian Betz +^^^^^^^^^^^^^^^ + +.. image:: _static/team/max_mustermann.jpg + :alt: Maximilian Betz + :width: 150px + :align: left + :class: developer-photo + +**Research Focus:** Epidemiological modeling, Machine Learning, Automatic generation of Python bindings + +Maximilian Betz completed his Bachelor's degree in Computer Science at the DHBW Mannheim as an integrated degree program with the Department of High-Performance Computing at the German Aerospace Center (DLR). Afterwards, he continued with a Master's in Computer Science at the University of Cologne, where he focused on Machine Learning and High Performance Computing. During the Master's, he kept working at the DLR as a student assistant with a focus on automatic generation of Python bindings, metapopulation models to simulate the spatial spread of infectious diseases and Machine Learning based parameter inference. .. _contributors: From b73ba02a51ee11bea46b40c94d788b02e1e9bcc3 Mon Sep 17 00:00:00 2001 From: Sascha <51127093+xsaschako@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:44:08 +0200 Subject: [PATCH 078/147] sascha about --- docs/source/team.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/source/team.rst b/docs/source/team.rst index bb72b9cb95..c3fad140d8 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -69,6 +69,33 @@ Henrik Zunker is a PhD student at the Institute for Software Technology at the G .. _developer_3: + +Sascha Korf +^^^^^^^^^^^^^ + +.. image:: _static/team/max_mustermann.jpg + :alt: Sascha Korf + :width: 150px + :align: left + :class: developer-photo + +**Research Focus:** High-Performance Computing, Mathematical Modeling, Scientific Computing, Agent Based Modelling (ABMs) + +Sascha Korf is a PhD student at the Institute for Software Technology at the German Aerospace Center (DLR) since 2022. +His background is in numerical mathematics, where he studied at the University of Cologne. He is interested in the development of agent-based models for infectious disease dynamics, especially in the context of high-performance computing. + +**Selected Publications:** + +* Kerkmann D, **Korf S**, Nguyen K, Abele D, Schengen A, Gerstein C, Göbbert JH, Basermann A, Kühn MJ, Meyer-Hermann M (2025). *Agent-based modeling for realistic reproduction of human mobility and contact behavior to evaluate test and isolation strategies in epidemic infectious disease spread*. *Computers in Biology and Medicine* 193:110269. `DOI:10.1016/j.compbiomed.2025.110269 `_ +Diallo D, Schoenfeld J, Schmieding R, **Korf S**, Kühn MJ, Hecking T. (2025). *Integrating Human Mobility Models with Epidemic Modeling: A Framework for Generating Synthetic Temporal Contact Networks*. *Entropy (Basel)* 27(5):507. `DOI:10.3390/e27050507 `_ + +**Links:** + +* `Google Scholar Profile `_ +* `ORCID Profile: 0000-0002-9825-365X `_ + +.. _developer_3: + Julia Bicker ^^^^^^^^^^^^ From 1f8c47eeba40dde09b94c9e31326711e848618d6 Mon Sep 17 00:00:00 2001 From: jubicker <113909589+jubicker@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:51:56 +0200 Subject: [PATCH 079/147] IO docu --- docs/source/cpp/io.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/source/cpp/io.rst b/docs/source/cpp/io.rst index 925e4cfc40..ac5b866de1 100644 --- a/docs/source/cpp/io.rst +++ b/docs/source/cpp/io.rst @@ -1,5 +1,10 @@ -Input / Output -============== +IO functionalities +=================== + +Aggregated output +----------------- + +All aggregated models save their results in a :code:`mio::TimeSeries` that contains all subpopulations for every time point, see :doc:` ` for a description of the :code:`mio::TimeSeries` data type. The time series can be printed to the terminal with the :code:`TimeSeries::print_table()` function and exported to a CSV file using :code:`TimeSeries::export_csv`. As e.g. for the ODE-based models adaptive step size methods are used, the time series will not be necessarily available on equidistant time points or days. To obtain a :code:`mio::TimeSeries` interpolated on days or user-defined time points, the :code:`mio::interpolate_simulation_result()` can be used. This document describes utilities for reading and writing data from and to files in different formats, in cases where ``TimeSeries::print_table()`` is not enough. The main sections are: From 48dc07342c14e2dbfe8ad65723d1ab0eb59f7e40 Mon Sep 17 00:00:00 2001 From: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:54:46 +0200 Subject: [PATCH 080/147] CHG: Add Kilian --- docs/source/team.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/source/team.rst b/docs/source/team.rst index c3fad140d8..c0c4946d05 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -142,6 +142,25 @@ Carlotta Gerstein completed her Bachelor's degree in Mathematics at the Universi * `ORCID Profile: 0009-0004-4410-0502 `_ +Kilian Volmer +^^^^^^^^^^^^^^^^^ + +.. image:: https://www.mathematics-and-life-sciences.uni-bonn.de/people/kilian-volmer/@@images/image/preview + :alt: Kilian Volmer + :width: 150px + :align: left + :class: developer-photo + +**Research Focus:** Epidemiological modelling, Metapopulation models + + +Kilian holds a B.SC. and a M.SC. in Mathematics from the University of Bonn. During his Masters he worked as a research +assistant in the group of Prof. Kevin Thurley and wrote his thesis on modelling immune cell interactions. In December +2024 he joined the group of Prof. Jan Hasenauer at the Life and Medical Sciences Institute and the Bonn Center +for Mathematical Life Sciences as a PhD student to work on infectious disease modelling. + + + Maximilian Betz ^^^^^^^^^^^^^^^ From 7039d8990717761d596b2b6785cb0d9bbfbb66ac Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Mon, 28 Jul 2025 17:03:45 +0200 Subject: [PATCH 081/147] add build and install instructions --- docs/source/cpp/installation.rst | 237 ++++++++++++++++++++++++++++++- 1 file changed, 235 insertions(+), 2 deletions(-) diff --git a/docs/source/cpp/installation.rst b/docs/source/cpp/installation.rst index 97a9eb4699..6f02cd74d4 100644 --- a/docs/source/cpp/installation.rst +++ b/docs/source/cpp/installation.rst @@ -1,4 +1,237 @@ +Build instructions +================== + +The MEmilio core library (MEmilio C++) is written C++ and uses `CMake `_ as build system. Before +installing MEmilio C++, make sure a C++20 compiler, CMake and a build tool (like GNU Make or Ninja) is installed on your +device. The following guide will make use of the command line, but you can use graphical build tools from an IDE as +well. + +Quickstart +^^^^^^^^^^ + +These are minimal build instructions. More detailed steps and explanations are given below. + +.. code:: bash + + git clone https://github.com/SciCompMod/memilio # download the project + cd memilio # go into the project directory + cmake -S cpp -B cpp/build # *configure* the project, creating a "build" directory under cpp/ + cmake --build cpp/build -j 2 # *build* all targets from the project with 2 threads + +After the build process is done, you can run files from "cpp/build/bin", for example our test suite: + +.. code:: bash + + ./cpp/build/bin/memilio-test + +This will run several tests and should write out ``[ PASSED ]`` in the end. + +Requirements +^^^^^^^^^^^^ +MEmilio C++ is regularly tested with the following compilers (list will be extended over time): + +- GCC, versions 11 and 13 +- Clang, version 14 and 17 +- MSVC, version 19.43 (Visual Studio 2022) + +MEmilio C++ is regularly tested on GitHub runners using Ubuntu 22.04 and 24.04 and Windows Server 2022 and 2025. It is +expected to run on any comparable Linux or Windows system. It is currently not tested on macOS. + +The following table lists the dependencies that are used. Most of them are required, but some are optional. The library +can be used without them but with slightly reduced features. CMake will warn about them during configuration. Most of +them are bundled with this library and do not need to be installed manually. Bundled libraries are either included with +this project or loaded from the web on demand. For each dependency, there is a CMake option to use an installed version +instead. Version compatibility needs to be ensured by the user, the version we currently use is included in the table. + +.. list-table:: + :header-rows: 1 + + * - Library + - Version + - Required + - Bundled + - Notes + * - spdlog + - 1.15.0 + - Yes + - Yes (git repo) + - https://github.com/gabime/spdlog + * - Eigen + - 3.4.0 + - Yes + - Yes (git repo) + - http://gitlab.com/libeigen/eigen + * - Boost + - 1.84.0 + - Yes + - Yes (git repo) + - https://github.com/boostorg/boost + * - JsonCpp + - 1.9.6 + - No + - Yes (git repo) + - https://github.com/open-source-parsers/jsoncpp + * - HDF5 + - 1.12.0 + - No + - No + - https://www.hdfgroup.org/, package libhdf5-dev on apt (Ubuntu) + * - GoogleTest + - 1.10 + - For Tests only + - Yes (git repo) + - https://github.com/google/googletest + * - LibSBML + - 5.20.2 + - No + - No + - https://sbml.org/software/libsbml/ (For SBML integration only) + +See the `thirdparty directory `_ for more details. + +Step-by-step instructions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Start by download the newest version (or a specific release) of MEmilio from our +`github repository `_ to a directory of your choice, or clone it directly using +git by first opening a terminal in that directory and then running + +.. code:: bash + + git clone https://github.com/SciCompMod/memilio + +.. dropdown:: :fa:`gears` Note for developers + + If you need to push changes to the main repo, register an ssh key with GitHub and clone from + *git@github.com:SciCompMod/memilio.git* instead. You can also change to the ssh address later using + + .. code:: bash + + git remote set-url origin git@github.com:SciCompMod/memilio.git + +This will create a new directory called "memilio". Change into this directory. + +.. code:: bash + + cd memilio + +Before we can *build* anything, we need to *configure* the project first. If you want to use its default options, +simply run + +.. code:: bash + + cmake -S cpp -B cpp/build + +Additional options can be specified by appending one or more ``-D