Run the mean-field (ODE) malaria model.
Usage
run_simulation_ode(
timesteps,
parameters = NULL,
correlations = NULL,
tuning = ode_tuning(),
...
)Arguments
- timesteps
number of days to simulate. Output has
timesteps + 1rows (day 0 is the seeded equilibrium; days 1..timesteps are integrated).- parameters
a malariasimulation::get_parameters() list (falciparum), optionally with interventions layered on via the
set_*builders. The following are applied automatically from the list (no extra arguments): clinical treatment (time-varying), antimalarial resistance, bed nets, IRS, MDA/SMC/PMC, PEV (EPI + mass) and TBV vaccines, seasonality and flexible carrying capacity, and custom demography (set_demography: age-specific mortality and the resulting equilibrium age structure). P. vivax is rejected; the model is always compartmental (the individual-mosquito path does not apply).vignette("using")indexes the mean-field approximations and when each one matters.The target EIR is read from this list, as
parameters$init_EIR, which is wheremalariasimulationputs it and reads it from too. Seed it the same way you would for the IBM, before calling this function:parameters <- malariasimulation::set_equilibrium(parameters, init_EIR = 20). That call also storeseq_params, whichfleethonours if present. Settingparameters$init_EIRby hand works but skips that.Call
set_equilibrium()last. It freezes all 56 equilibrium parameters, 51 of them translated from the IBM list, at the values they held when it ran, andfleetmerges that stored set over the live list. So an edit made after it – anotherset_*()builder, orparameters$du <- 10by hand – is a silent no-op for anything in that set, and the run is bit-identical to one without the edit. The IBM reads those fields directly and would honour the edit, so this is a real difference between the two models rather than a detail of this one.fleetwarns when a live value disagrees with the stored one. If a calibration loop varies any of them, re-callset_equilibrium()each iteration.Three further optional fields tune how faithfully the mean field mirrors the IBM. The first two are per-individual arithmetic, the third is a seeding convention; all three default to the validated choice, so you normally leave them alone:
bite_dedup(default1): reproduce the IBM's per-timestep bite deduplication. malariasimulation collects the day's bitten individuals in a bitset, so a person bitten repeatedly in one timestep is infected at most once: the daily infection probability is(1 - exp(-EPS)) * b, which saturates. Setting0uses the unboundedb * EPSinstead, the linear formmalariaEquilibriumassumes, which over-predicts infection where exposure approaches one bite/person/day (seasonal peaks, high-zetastrata). It does not make themalariaEquilibriumseed an exact fixed point, and it does not make a run flat: deduplication is only the largest of four departures from that solution, whichvignette("model")lists in its initial-conditions section. What it does do is roughly halve the largest excursion from the seed: over an undisturbed 15-year run at EIR 20, PfPR(2-10) departs from its seeded value by at most 0.14% with0, against 0.28% at the default. That is the metric the package's own equilibrium tests assert, which is why they set it. Leave it at1for any scientific run.acquired_immunity_offset(default0): the IBM adds+0.5to positive acquired immunity inside theb/phi/thetaHill functions. That is a per-individual detail which does not carry over to a stratum mean; an A/B against the IBM ensemble mean favours0. Set0.5to reproduce the IBM's literal Hill calls.hold_init_EIR(defaultFALSE): only matters withset_demography(). malariasimulation'sset_equilibrium()sizes the mosquito population from the equilibrium under its default exponential age structure, so under a custom demography the IBM drifts to whatever transmission that density supports. By default fleet replicates that: it takes the IBM's mosquito density and seeds at the EIR its own equilibrium under the custom age structure then supports (a fixed point, so no burn-in), which is generally notinit_EIR. SetTRUEto seed atinit_EIRexactly instead.
- correlations
accepted so the first three arguments mirror
malariasimulation::run_simulation(timesteps, parameters, correlations)exactly (drop-in call compatibility). Intervention correlation is an individual-level feature with no mean-field analogue, so a non-NULL value is ignored with a warning (the ODE assumes independence between interventions).tuningis the fourth argument: anode_tuning()object (or a list of its fields) passed positionally here would land incorrelationsand be thrown away, so that is an error rather than a silently defaulted run.- tuning
ODE solver and discretisation settings, from
ode_tuning(). A plain named list of the fields you want to change is also accepted; every field left out keeps its default. These are numerical-approximation knobs, not model parameters: nothing epidemiological lives here.- ...
not used. Named arguments that were top-level before the signature was cut back to
malariasimulation::run_simulation()'s are caught here and reported with the call that replaces them.
Value
a wide, malariasimulation-style daily count table, one row per output
day. The column names do not change with the parameter set, so runs with and
without interventions are directly comparable and rbind-able. Columns:
timestep: output day (0..timesteps), the row key.per age band (tags in days,
<lo>_<hi>):n_age_*(population),n_detect_lm_*andp_detect_lm_*(LM-positive count and PfPR proportion),n_detect_pcr_*,n_inc_clinical_*,n_inc_severe_*andn_inc_*(all-infection incidence), all per-day counts. A band is aggregated by exact overlap: each model age group contributes the fraction of its own width that lies inside the band, so a band need not align with theage_lowergrid, and bands that partition a span of the age axis partition the population living in that span exactly. The one age group that is not a finite interval is the absorbing top group, which holds everyone from its lower edge upward: a band is given all of it if it extends above that edge and none of it if it stops exactly on it. So a band[0, 36500)renders the whole population on the default grid (top group from 29200 days) but leaves the top group out on a grid built withmax_age = 100– 1.3% of the population, at the default demography. That is not silent: the run warns, names the share of the population non_age_*band renders, and names the age groups it sits in. A band that overlaps no age group at all (it lies above the open-ended top group) warns too, and itsp_detect_lm_*isNArather than 0.ft(treated fraction),EIR(per adult per year),FOIM.population-total infection-state counts
S_count,D_count,A_count,U_count,Tr_count,Ph_count(diagnostic).
Incidence columns are per-day counts; do not thin rows before postie. Can be passed directly to postie::get_rates() / postie::get_prevalence().
Details
Seasonal runs oscillate around a limit cycle rather than holding flat;
the state is seeded at the annual-mean (aseasonal) equilibrium, so the first
~10 years are a transient onto the cycle and the seasonal annual-mean EIR
sits a few percent below the aseasonal init_EIR target (nonlinear
averaging). Use a burned-in cycle for calibration/comparison.
See also
ode_tuning() for the solver and discretisation settings.
Examples
if (FALSE) { # \dontrun{
# exactly the call you would make to malariasimulation::run_simulation()
p <- malariasimulation::set_equilibrium(
malariasimulation::get_parameters(), init_EIR = 20)
out <- run_simulation_ode(timesteps = 3650, parameters = p)
head(out[, c("timestep", "n_age_730_3650", "n_detect_lm_730_3650", "EIR")])
# a long projection, trading a little accuracy for speed
fast <- run_simulation_ode(3650, p, tuning = list(rtol = 1e-6, step_size_max = 10))
} # }
