|
| 1 | +SI with Behaviour |
| 2 | +================ |
| 3 | +Steve Walker |
| 4 | + |
| 5 | +- <a href="#states" id="toc-states">States</a> |
| 6 | +- <a href="#parameters" id="toc-parameters">Parameters</a> |
| 7 | +- <a href="#dynamics" id="toc-dynamics">Dynamics</a> |
| 8 | +- <a href="#model-specification" id="toc-model-specification">Model |
| 9 | + Specification</a> |
| 10 | +- <a href="#simulation" id="toc-simulation">Simulation</a> |
| 11 | + |
| 12 | +This is an SI model in which individuals can protect themselves. |
| 13 | + |
| 14 | +<!-- --> |
| 15 | + |
| 16 | +The per-capita protection-rate, `alpha_t`, is time-varying, and |
| 17 | +increases as the number of infectious individuals, `I`, increases. |
| 18 | + |
| 19 | +The code in this article uses the following packages. |
| 20 | + |
| 21 | +``` r |
| 22 | +library(macpan2) |
| 23 | +library(ggplot2) |
| 24 | +library(dplyr) |
| 25 | +library(tidyr) |
| 26 | +``` |
| 27 | + |
| 28 | +This model was inspired by a question from [Irena |
| 29 | +Papst](https://github.com/papsti). The [awareness |
| 30 | +model](https://github.com/canmod/macpan2/tree/main/inst/starter_models/awareness) |
| 31 | +provides another example of a behavioural response. |
| 32 | + |
| 33 | +# States |
| 34 | + |
| 35 | +| Variable | Description | |
| 36 | +|----------|-----------------------------------------------------| |
| 37 | +| $S(t)$ | Number of susceptible individuals | |
| 38 | +| $I(t)$ | Number of infectious individuals | |
| 39 | +| $P(t)$ | Number of individuals who have protected themselves | |
| 40 | + |
| 41 | +# Parameters |
| 42 | + |
| 43 | +| Parameter | Description | |
| 44 | +|-----------------------|---------------------------------------------------------------------| |
| 45 | +| $\beta$ | Transmission rate | |
| 46 | +| $\alpha_{\text{max}}$ | Maximum per-capita protection rate | |
| 47 | +| $I^*$ | Threshold number of infectious individuals that triggers protection | |
| 48 | +| $k$ | Steepness of the sigmoidal switching function | |
| 49 | +| $N$ | Total population size (constant) | |
| 50 | + |
| 51 | +# Dynamics |
| 52 | + |
| 53 | +$$ |
| 54 | +\begin{align*} |
| 55 | +\sigma(I) &= \frac{1}{1 + \exp\left( -k \cdot (I - I^*) \right)} \\ |
| 56 | +\alpha(t) &= \alpha_{\text{max}} \cdot \sigma(I(t)) \\ |
| 57 | +\\ |
| 58 | +\frac{dS}{dt} &= - \beta \cdot \frac{I}{N} \cdot S - \alpha(t) \cdot S \\ |
| 59 | +\frac{dI}{dt} &= \beta \cdot \frac{I}{N} \cdot S \\ |
| 60 | +\frac{dP}{dt} &= \alpha(t) \cdot S |
| 61 | +\end{align*} |
| 62 | +$$ |
| 63 | + |
| 64 | +# Model Specification |
| 65 | + |
| 66 | +This model has been specified in the `si_behaviour` directory |
| 67 | +[here](https://github.com/canmod/macpan2/blob/main/inst/starter_models/si_behaviour/tmb.R) |
| 68 | +and is accessible from the `macpan2` model library (see [Example |
| 69 | +Models](https://canmod.github.io/macpan2/articles/example_models.html) |
| 70 | +for details). |
| 71 | + |
| 72 | +Printing the steps of the simulation loop illustrates how to implement a |
| 73 | +sigmoidal response of the protection rate to the prevalence of |
| 74 | +infectious individuals. |
| 75 | + |
| 76 | +``` r |
| 77 | +mp_print_during(si) |
| 78 | +#> --------------------- |
| 79 | +#> At every iteration of the simulation loop (t = 1 to T): |
| 80 | +#> --------------------- |
| 81 | +#> 1: sigma ~ invlogit(switch_slope * (I - threshold)) |
| 82 | +#> 2: alpha_t ~ alpha_max * sigma |
| 83 | +#> 3: mp_per_capita_flow(from = "S", to = "I", rate = "beta * I / N", |
| 84 | +#> flow_name = "infection") |
| 85 | +#> 4: mp_per_capita_flow(from = "S", to = "P", rate = "alpha_t", flow_name = "protection") |
| 86 | +``` |
| 87 | + |
| 88 | +One could make this reponse an approximate step-function by increasing |
| 89 | +the `switch_slope` parameter, and an exact step-function by modifying |
| 90 | +expression `2` as follows. |
| 91 | + |
| 92 | +``` r |
| 93 | +alpha_t ~ alpha_max * round(sigma) |
| 94 | +``` |
| 95 | + |
| 96 | +However, this approach would complicate calibration by introducing a |
| 97 | +discontinuity in the log-likelihood. In practice, there is rarely a need |
| 98 | +to deviate from an approximate step function, since the approximation |
| 99 | +can be made arbitrarily sharp by increasing `switch_slope`. |
| 100 | + |
| 101 | +# Simulation |
| 102 | + |
| 103 | +We simulate the ODEs of this model using the following parameters. |
| 104 | + |
| 105 | +``` r |
| 106 | +spec = mp_tmb_library("starter_models" |
| 107 | + , "si_behaviour" |
| 108 | + , package = "macpan2" |
| 109 | +) |
| 110 | +params = c("beta", "alpha_max", "threshold", "switch_slope") |
| 111 | +mp_default_list(spec)[params] |
| 112 | +#> $beta |
| 113 | +#> [1] 0.1 |
| 114 | +#> |
| 115 | +#> $alpha_max |
| 116 | +#> [1] 0.1 |
| 117 | +#> |
| 118 | +#> $threshold |
| 119 | +#> [1] 50 |
| 120 | +#> |
| 121 | +#> $switch_slope |
| 122 | +#> [1] 1 |
| 123 | +``` |
| 124 | + |
| 125 | +Simulating and plotting the state variables is done using the following |
| 126 | +code. |
| 127 | + |
| 128 | +``` r |
| 129 | +traj = (spec |
| 130 | + |> mp_rk4() |
| 131 | + |> mp_simulator(100, c(mp_state_vars(spec), "sigma")) |
| 132 | + |> mp_trajectory() |
| 133 | +) |
| 134 | +(traj |
| 135 | + |> filter(matrix %in% mp_state_vars(spec)) |
| 136 | + |> ggplot() |
| 137 | + + aes(time, value) |
| 138 | + + geom_line() |
| 139 | + + facet_wrap(~matrix, scales = "free", ncol = 1) |
| 140 | + + theme_bw() |
| 141 | +) |
| 142 | +``` |
| 143 | + |
| 144 | +<!-- --> |
| 145 | + |
| 146 | +And here are the points at which the `sigma` function was evaluated |
| 147 | +during the simulation, showing a very steep change at `I = 50` from a |
| 148 | +regime where nobody is protecting themselves, to one where all |
| 149 | +susceptible individuals protect themselves at per-capita rate |
| 150 | +`alpha_max`. |
| 151 | + |
| 152 | +``` r |
| 153 | +(traj |
| 154 | + |> filter(matrix %in% c("sigma", "I")) |
| 155 | + |> pivot_wider(id_cols = time, names_from = matrix) |
| 156 | + |> ggplot() |
| 157 | + + aes(I, sigma) |
| 158 | + + geom_point() |
| 159 | + + theme_bw() |
| 160 | +) |
| 161 | +``` |
| 162 | + |
| 163 | +<!-- --> |
| 164 | + |
| 165 | +One can effectively make this switch a step function by increasing the |
| 166 | +`switch_slope` parameter. |
| 167 | + |
| 168 | +``` r |
| 169 | +(spec |
| 170 | + |> mp_tmb_update(default = list(switch_slope = 100)) |
| 171 | + |> mp_rk4() |
| 172 | + |> mp_simulator(100, c("I", "sigma")) |
| 173 | + |> mp_trajectory() |
| 174 | + |> pivot_wider(id_cols = time, names_from = matrix) |
| 175 | + |> ggplot() |
| 176 | + + aes(I, sigma) |
| 177 | + + geom_point() |
| 178 | + + theme_bw() |
| 179 | +) |
| 180 | +``` |
| 181 | + |
| 182 | +<!-- --> |
0 commit comments