You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: modules/networks/sis.qmd
+62-28Lines changed: 62 additions & 28 deletions
Original file line number
Diff line number
Diff line change
@@ -4,15 +4,15 @@ subtitle: "Guided Exercise"
4
4
format: html
5
5
---
6
6
7
-
The SIS (Susceptible–Infected–Susceptible) is a simple epidemic model of disease spread where individuals can be infected multiple times. In this module, we will implement the SIS model on networks using the NetworkX library in Python. Our goal is to explore how **network structure** (random vs small-world vs scale-free) changes spreading.
7
+
The SIS (Susceptible–Infected–Susceptible) model is a simple epidemic model where individuals can be infected multiple times. In this module, we implement SIS on networks using NetworkX and explore how **network structure** (random vs small-world vs scale-free) changes spreading.
8
8
9
9
## What is the SIS Model?
10
10
11
-
-**States:**Each node can be Susceptible (S) or Infected (I).
11
+
-**States:**each node is Susceptible (S) or Infected (I).
12
12
-**Transitions:**
13
-
- $S \to I$: a susceptible node becomes infected through contacts (infection probability $\beta$).
14
-
- $I \to S$: an infected node recovers and becomes susceptible again (recovery probability $\mu$).
15
-
-**No permanent immunity:**Nodes can be reinfected after recovery.
13
+
- $S \to I$: infection through contacts (controlled by $\beta$).
14
+
- $I \to S$: recovery (controlled by $\mu$).
15
+
-**No permanent immunity:**recovered nodes can be reinfected.
16
16
17
17
### Discrete-Time Update Rule
18
18
@@ -72,6 +72,19 @@ initial_infected = 5 # should always be < N
72
72
N = 100 # number of nodes in the graph
73
73
```
74
74
75
+
::: {.callout-note}
76
+
## About `SEED` (why we fix it)
77
+
78
+
There are **two** sources of randomness in this activity:
79
+
80
+
1. the **graph** you generate (e.g., which edges appear in Erdős–Rényi), and
81
+
2. the **stochastic SIS dynamics** (who gets infected / recovers at each step).
82
+
83
+
We fix `SEED` so results are **reproducible**: rerunning the page gives the same plots, which makes debugging and reporting possible.
84
+
85
+
When we do many repetitions, we use `SEED + r` so repetition `r` is a **different random trial** (new graph + new random infection events) but still reproducible. If you reused the same `SEED` in every repetition, you would repeat the *same* realization and your “average over runs” would be meaningless.
86
+
:::
87
+
75
88
## Choose a Network
76
89
77
90
For your project you will compare several models. Start by picking one. You can see a sample of the three models in the page: [Network Models](models.qmd).
Remember that you can loop through all nodes and their neighbors using `G.nodes()` and `G.neighbors(node)`. You can also use `np.random.random()` to generate random numbers for the probabilistic transitions.
237
+
Remember that you can loop through all nodes and their neighbors using `G.nodes()` and `G.neighbors(node)`. Use `np.random.random()` for the probabilistic transitions.
223
238
224
239
::: {.callout-tip collapse="true"}
225
240
## Solved: `step_sis()`
@@ -228,7 +243,10 @@ Remember that you can loop through all nodes and their neighbors using `G.nodes(
228
243
#| echo: true
229
244
#| message: false
230
245
#| warning: false
231
-
def step_sis(G, beta, mu):
246
+
def step_sis(G, beta, mu, rng=None):
247
+
if rng is None:
248
+
rng = np.random.default_rng()
249
+
232
250
# Retrieve current states of all nodes
233
251
current = {node: G.nodes[node]["state"] for node in G.nodes()}
234
252
new_state = current.copy() # start with the same state for synchronous update
@@ -240,15 +258,17 @@ def step_sis(G, beta, mu):
240
258
if m > 0:
241
259
# Compute infection probability
242
260
p_infect = 1 - (1 - beta) ** m
243
-
if np.random.random() < p_infect:
261
+
if rng.random() < p_infect:
244
262
new_state[node] = "I"
245
263
else: # current[node] == "I"
246
-
if np.random.random() < mu:
264
+
if rng.random() < mu:
247
265
new_state[node] = "S"
248
266
nx.set_node_attributes(G, new_state, "state")
249
267
return G
250
268
```
251
269
270
+
:::
271
+
252
272
Now wrap it into a simulator that records the number of infected nodes over time. The function `run_sis()` should take a graph and the parameters, run the simulation for a given number of steps, and return a list of the number of infected nodes at each time step.
# Count initial number of infected nodes and initialize the list
273
297
infected_counts = [sum(H.nodes[node]["state"] == "I" for node in H.nodes())]
274
298
# Apply SIS steps and record infected counts
275
299
for _ in range(steps):
276
-
step_sis(H, beta=beta, mu=mu)
300
+
step_sis(H, beta=beta, mu=mu, rng=rng)
277
301
infected_counts.append(sum(H.nodes[node]["state"] == "I" for node in H.nodes()))
278
302
return infected_counts
279
303
```
@@ -288,10 +312,11 @@ print("Infected counts over time:", counts)
288
312
289
313
## Run and Visualize
290
314
291
-
Use `matplotlib` to plot the fraction of infected nodes over time. Try to get a graph similar to @sis-plot below.
315
+
Use `matplotlib` to plot the fraction of infected nodes over time. Try to get a graph similar to @fig-sis-plot below.
292
316
293
317
```{python}
294
-
#| label: sis-plot
318
+
#| label: fig-sis-plot
319
+
#| fig-cap: "Fraction of infected nodes over time for a single SIS run on a random graph."
295
320
#| fig-alt: 'Fraction of infected nodes over time for a single run of the SIS model on a random graph.'
296
321
#| fig-width: 6
297
322
#| fig-height: 4
@@ -308,10 +333,11 @@ plt.show()
308
333
309
334
## Compare Network Types
310
335
311
-
Finally, run multiple simulations for each graph type and plot the average infected fraction over time for each type on the same graph. @sis-compare is an example of what this could look like.
336
+
Finally, run multiple simulations for each graph type and plot the average infected fraction over time for each type on the same graph. @fig-sis-compare is an example of what this could look like.
312
337
313
338
```{python}
314
-
#| label: sis-compare
339
+
#| label: fig-sis-compare
340
+
#| fig-cap: "Mean infected fraction over time for different network models (random, small-world, scale-free)."
315
341
#| fig-alt: 'Average infected fraction over time for different network models (random, small-world, scale-free).'
0 commit comments