-
Notifications
You must be signed in to change notification settings - Fork 1
feat(model): SIR model #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
emptymalei
wants to merge
11
commits into
main
Choose a base branch
from
lm/sir
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
6d1215e
feat(model): add SIR model for pandemic
emptymalei 996ffa0
test(refactor): rename test folders to test_x
emptymalei 83926f4
test(sir): add tests for sir model
emptymalei 83e6333
docs(model): add sir tutorials
emptymalei 442d4d9
test(model): adjust tests for sir model
emptymalei f3a1dc5
build(pyproject.toml): exclude two more rules for rull
emptymalei 40c6a67
ci(github-actions): bump versions of actions steps
emptymalei f5f6c0f
docs(sir.py): improve tutorial on sir model
emptymalei 04c4fde
docs(mkdocs.yml): added sir tutorial to docs
emptymalei fdb8207
ci(github-actions): remove pre-commit runs and rely on local setups
emptymalei 9e1ffab
fix(sir-model): fix bugs in time sequence input and iterations
emptymalei File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # SIR | ||
|
|
||
| ::: hamilflow.models.sir |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # --- | ||
| # jupyter: | ||
| # jupytext: | ||
| # text_representation: | ||
| # extension: .py | ||
| # format_name: percent | ||
| # format_version: '1.3' | ||
| # jupytext_version: 1.16.4 | ||
| # kernelspec: | ||
| # display_name: .venv | ||
| # language: python | ||
| # name: python3 | ||
| # --- | ||
|
|
||
| # %% [markdown] | ||
| # # SIR Model | ||
| # | ||
| # In this tutorial, we will learn how to use the SIR model. | ||
|
|
||
| # %% | ||
| import plotly.express as px | ||
|
|
||
| from hamilflow.models.sir import SIR | ||
|
|
||
| # %% [markdown] | ||
| # ## Model | ||
|
|
||
| # %% | ||
| sir_1 = SIR( | ||
| system={ | ||
| "beta": 0.3, | ||
| "alpha": 0.1, | ||
| "delta_t": 0.1, | ||
| }, | ||
| initial_condition={ | ||
| "susceptible_0": 999, | ||
| "infected_0": 1, | ||
| "recovered_0": 0, | ||
| }, | ||
| ) | ||
|
|
||
| # %% | ||
| n_steps = 100 | ||
|
|
||
| sir_1_results = sir_1.generate_from(n_steps=n_steps) | ||
| sir_1_results.head() | ||
|
|
||
| # %% | ||
| px.line( | ||
| sir_1_results, | ||
| x="t", | ||
| y=["S", "I", "R"], | ||
| ) | ||
|
|
||
| # %% | ||
| sir_1._step(999, 1) # noqa: SLF001 | ||
|
|
||
| # %% |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| """Main module for SIR model in epidemiology.""" | ||
|
|
||
| from collections.abc import Mapping | ||
| from functools import cached_property | ||
| from typing import Any | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
| from pydantic import BaseModel, Field, computed_field | ||
|
|
||
| from hamilflow.models.utils.typing import TypeTime | ||
|
|
||
|
|
||
| class SIRSystem(BaseModel): | ||
| """Definition of the SIR system. | ||
|
|
||
| :cvar beta: Transmission rate | ||
| :cvar alpha: Recovery rate | ||
| :cvar delta_t: Time granularity of the simulation | ||
| """ | ||
|
|
||
| beta: float = Field(default=0.3, gt=0, description="Transmission rate", frozen=True) | ||
| alpha: float = Field(default=0.1, gt=0, description="Recovery rate", frozen=True) | ||
| delta_t: float = Field(ge=0.0, default=1.0) | ||
|
|
||
|
|
||
| class SIRIC(BaseModel): | ||
| """The initial condition for an SIR model simulation. | ||
|
|
||
| :cvar susceptible_0: Initial number of susceptible individuals | ||
| :cvar infected_0: Initial number of infectious individuals | ||
| :cvar recovered_0: Initial number of recovered individuals | ||
| """ | ||
|
|
||
| susceptible_0: int = Field( | ||
| default=999, | ||
| ge=0, | ||
| description="Initial susceptible population", | ||
| ) | ||
| infected_0: int = Field( | ||
| default=1, | ||
| ge=0, | ||
| description="Initial infectious population", | ||
| ) | ||
| recovered_0: int = Field( | ||
| default=0, | ||
| ge=0, | ||
| description="Initial recovered population", | ||
| ) | ||
|
|
||
| @computed_field # type: ignore[misc] | ||
| @cached_property | ||
| def n(self) -> int: | ||
| """Total population in the simulation.""" | ||
| return self.susceptible_0 + self.infected_0 + self.recovered_0 | ||
|
|
||
|
|
||
| class SIR: | ||
| r"""SIR model simulation. | ||
|
|
||
| The SIR model divides a population into three compartments: | ||
|
|
||
| - $S$ (Susceptible): Individuals who can be infected. | ||
| - $I$ (Infectious): Individuals who are currently infected and can transmit the disease. | ||
| - $R$ (Recovered): Individuals who have recovered and are assumed to have immunity. | ||
| - $N$(Total population): $N = S + I + R$. | ||
|
|
||
| The dynamics of the compartments are governed by the following system of ordinary differential equations: | ||
|
|
||
| $$ | ||
| \begin{split} | ||
| \frac{dS(t)}{dt} &= -\beta I(t) S(t) \\ | ||
| \frac{dI(t)}{dt} &= \beta S(t) I(t) - \alpha I(t) \\ | ||
| \frac{dR(t)}{dt} &= \alpha I(t), | ||
| \end{split} | ||
| $$ | ||
|
|
||
| with the constraint | ||
|
|
||
| $$ | ||
| N = S(t) + I(t) + R(t). | ||
| $$ | ||
|
|
||
| Where: | ||
| - $\beta$ is the transmission rate (probability of infection per contact per unit time). | ||
| - $\alpha$ is the recovery rate (rate at which infected individuals recover per unit time). | ||
|
|
||
| :param system: The parameters of the SIR system, including `beta` and `alpha`. | ||
| :param initial_condition: The initial state of the population, including `S0`, `I0`, and `R0`. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| system: Mapping[str, float], | ||
| initial_condition: Mapping[str, int], | ||
| ) -> None: | ||
| self.system = SIRSystem(**system) | ||
| self.initial_condition = SIRIC(**initial_condition) | ||
|
|
||
| @cached_property | ||
| def definition(self) -> dict[str, dict[str, Any]]: | ||
| """Model params and initial conditions defined as a dictionary.""" | ||
| return { | ||
| "system": self.system.model_dump(), | ||
| "initial_condition": self.initial_condition.model_dump(), | ||
| } | ||
|
|
||
| def generate_from(self, n_steps: int) -> pd.DataFrame: | ||
| """Simulate the SIR model and return time series data. | ||
|
|
||
| :param n_steps: Number of steps to simulate | ||
| :return: DataFrame with time, S, I, R columns | ||
| """ | ||
| time_steps = np.arange(1, n_steps) * self.system.delta_t | ||
|
|
||
| return self(time_steps) | ||
|
|
||
| def _step( | ||
| self, | ||
| susceptible: float, | ||
| infected: float, | ||
| ) -> tuple[int, int, int]: | ||
| """Calculate changes in S, I, R populations for one time step. | ||
|
|
||
| :param susceptible: Current susceptible population | ||
| :param infected: Current infected population | ||
| :param recovered: Current recovered population | ||
| :return: tuple of (dS, dI, dR) changes | ||
| """ | ||
| delta_s = -self.system.beta * susceptible * infected * self.system.delta_t | ||
| delta_i = ( | ||
| self.system.beta * susceptible * infected - self.system.alpha * infected | ||
| ) * self.system.delta_t | ||
| delta_r = self.system.alpha * infected * self.system.delta_t | ||
|
|
||
| return int(delta_s), int(delta_i), int(delta_r) | ||
|
emptymalei marked this conversation as resolved.
|
||
|
|
||
| def __call__(self, t: TypeTime) -> pd.DataFrame: | ||
| """Generate the SIR model simulation based on the given time array.""" | ||
| susceptible = self.initial_condition.susceptible_0 | ||
| infected = self.initial_condition.infected_0 | ||
| recovered = self.initial_condition.recovered_0 | ||
|
|
||
| results = [ | ||
| { | ||
| "t": 0, | ||
| "S": susceptible, | ||
| "I": infected, | ||
| "R": recovered, | ||
| }, | ||
| ] | ||
|
|
||
| for t_i in np.array(t): | ||
| delta_s, delta_i, delta_r = self._step(susceptible, infected) | ||
|
|
||
| susceptible = max(susceptible + delta_s, 0) | ||
| infected = max(infected + delta_i, 0) | ||
| recovered = max(recovered + delta_r, 0) | ||
|
|
||
| results.append( | ||
| { | ||
| "t": t_i, | ||
| "S": susceptible, | ||
| "I": infected, | ||
| "R": recovered, | ||
| }, | ||
| ) | ||
|
|
||
| return pd.DataFrame(results) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.