API reference
Transmission models
EpiBranch.BranchingProcess Type
struct BranchingProcess{O, G, P, L} <: TransmissionModelStochastic branching process transmission model.
Examples
BranchingProcess(NegBin(2.5, 0.16), LogNormal(1.6, 0.5))
BranchingProcess(NegBin(0.8, 0.5)) # no timing, pure chain statistics
BranchingProcess(M, R_j -> NegBin(R_j, 0.16), LogNormal(1.6, 0.5)) # multi-typeFields
offspring::Anygeneration_time::Anypopulation_size::Anylatent_period::Float64n_types::Int64type_labels::Any
Types
EpiBranch.Individual Type
mutable struct IndividualA single contact in the transmission tree (infected or not).
Core fields (used by the engine): id, parent_id, generation, chain_id, infection_time, susceptibility, infectiousness, secondary_case_ids.
The state dict holds everything else: intervention state, clinical state, demographics, and user-defined fields.
Note: id is the 1-based index into state.individuals. This invariant is relied on for O(1) parent lookups.
Fields
id::Int64parent_id::Int64generation::Int64chain_id::Int64infection_time::Float64susceptibility::Float64infectiousness::Float64secondary_case_ids::Vector{Int64}state::Dict{Symbol, Any}
EpiBranch.SimulationState Type
mutable struct SimulationState{R<:Random.AbstractRNG, P, A}State of a running or completed simulation.
Fields
individuals::Vector{Individual}active_ids::Vector{Int64}current_generation::Int64rng::Random.AbstractRNGcumulative_cases::Int64extinct::Boolpopulation_size::Anylatent_period::Float64max_infection_time::Float64attributes::Any
EpiBranch.SimOpts Type
struct SimOptsOptions controlling simulation termination and setup. Contains only simulation control parameters – clinical and demographic properties are set via attributes functions and interventions.
Fields
max_cases::Int64max_generations::Int64max_time::Float64n_initial::Int64
EpiBranch.DelayOpts Type
struct DelayOpts{R, A, O}Distributions for delays used in line list generation.
Fields
onset_to_reporting::Anyonset_to_admission::Anyonset_to_outcome::Any
EpiBranch.OutcomeOpts Type
struct OutcomeOpts{C}Parameters controlling case outcomes (hospitalisation and death).
Fields
prob_hospitalisation::Float64prob_death::Float64age_specific_cfr::Any
EpiBranch.DemographicOpts Type
struct DemographicOpts{D}Parameters for generating demographic data (age, sex).
Fields
age_distribution::Anyage_range::Tuple{Int64, Int64}prob_female::Float64
Simulation
EpiBranch.simulate Function
simulate(
model::TransmissionModel;
interventions,
attributes,
sim_opts,
rng,
condition,
max_attempts
) -> SimulationState{Random.TaskLocalRNG, _A, NoAttributes} where _Asimulate(model::TransmissionModel; interventions=[], attributes=nothing,
sim_opts=SimOpts(), rng=Random.default_rng(),
condition=nothing, max_attempts=10_000)Run a single outbreak simulation.
If condition is provided (a UnitRange{Int}), simulations are repeated until one produces an outbreak whose cumulative cases fall within the range, up to max_attempts.
EpiBranch.simulate_batch Function
simulate_batch(
model::TransmissionModel,
n::Int64;
interventions,
attributes,
sim_opts,
rng,
parallel
) -> Vectorsimulate_batch(model, n; parallel=false, kwargs...)Run n independent outbreak simulations.
When parallel=true, simulations are distributed across available threads using independent RNG streams derived from the provided rng. Use julia --threads N to enable multi-threading.
EpiBranch.step! Function
step!(
model::BranchingProcess,
state::SimulationState,
interventions
) -> Union{SimulationState{R, Int64} where R<:Random.AbstractRNG, SimulationState{R, NoPopulation} where R<:Random.AbstractRNG}step!(model::BranchingProcess, state::SimulationState, interventions)Process one generation of the branching process.
Mutates state in place: appends new contacts to state.individuals, updates state.cumulative_cases, state.current_generation, state.active_ids, and state.extinct. Individual-level fields are also modified by interventions via their hooks. See the Design section for implications on automatic differentiation.
Interventions
EpiBranch.AbstractIntervention Type
abstract type AbstractInterventionBase type for all interventions. Subtypes implement one or more of: initialise_individual!, resolve_individual!, apply_post_transmission!.
To support time-based scheduling via start_time, also implement intervention_time and reset!.
Fields
EpiBranch.Isolation Type
struct Isolation <: AbstractInterventionIsolate symptomatic, test-positive individuals after a delay from symptom onset.
Requires :onset_time and :asymptomatic on individuals (set via Disease or clinical_presentation).
test_sensitivity is the probability that a symptomatic individual tests positive and is therefore eligible for isolation.
Initialises: :isolated, :isolation_time, :test_positive.
Fields
delay::Distributions.Distributionstart_time::Float64post_isolation_transmission::Float64test_sensitivity::Float64
EpiBranch.ContactTracing Type
struct ContactTracing <: AbstractInterventionTrace contacts of isolated symptomatic cases with given probability and delay.
Requires fields set by Isolation: :isolated, :isolation_time. Also requires :asymptomatic and :onset_time (from clinical_presentation()).
Initialises: :traced, :quarantined.
Fields
probability::Float64delay::Distributions.Distributionquarantine_on_trace::Boolstart_time::Float64
EpiBranch.RingVaccination Type
struct RingVaccination <: AbstractInterventionVaccinate traced contacts, reducing their susceptibility to infection.
Applied to contacts that have been traced (:traced == true, set by ContactTracing). Contacts not yet infected at the time of vaccination have their susceptibility reduced.
For post-exposure prophylaxis (PEP, cf. pepbp), set delay_to_immunity = 0.0 (the default). For ring vaccination with a vaccine that takes time to confer protection, set delay_to_immunity to the appropriate delay.
Requires :traced (set by ContactTracing).
Initialises: :vaccinated, :vaccination_time.
Fields
efficacy::Float64delay_to_immunity::Float64mode::Symbol
EpiBranch.Scheduled Type
struct Scheduled{I<:AbstractIntervention, F} <: AbstractInterventionWrap an intervention so it only runs when a condition on the simulation state is met. Individuals are always initialised (so fields exist before the policy activates), but resolve_individual! and apply_post_transmission! are skipped while the condition returns false.
When start_time is provided, it is also forwarded to the inner intervention's own start_time field (if it has one), so that individual-level filtering on action time is handled by the framework's _enforce_start_time! mechanism automatically.
Keyword constructor
Any combination of start_time, end_time, and start_after_cases is accepted. They are combined with &&:
Scheduled(Isolation(delay=Exponential(2.0)); start_time=14.0)
Scheduled(ContactTracing(probability=0.5); start_after_cases=50)
Scheduled(iso; start_time=10.0, end_time=30.0)Predicate constructor
Pass any f(::SimulationState) -> Bool:
Scheduled(iso, state -> state.current_generation >= 3)Fields
intervention::AbstractInterventioncondition::Any
EpiBranch.initialise_individual! Function
initialise_individual!(
_::AbstractIntervention,
individual,
state
) -> AnySet up intervention-specific fields on a newly created individual. Default: no-op.
EpiBranch.resolve_individual! Function
resolve_individual!(
_::AbstractIntervention,
individual,
state
)Determine intervention state before transmission. Default: no-op.
EpiBranch.apply_post_transmission! Function
apply_post_transmission!(
_::AbstractIntervention,
state,
new_contacts
)Act on contacts after creation. All contacts are received. Default: no-op.
EpiBranch.intervention_time Function
intervention_time(
_::AbstractIntervention,
_::Individual
) -> Float64intervention_time(intervention, individual)Time at which this intervention's effect occurs for an individual. Used by the framework to enforce start_time: if the intervention time is earlier than start_time, the effect is undone via reset!.
Default: -Inf (effect always applies).
EpiBranch.reset! Function
reset!(_::AbstractIntervention, _::Individual)reset!(intervention, individual)Undo the effect of an intervention on an individual. Called by the framework when intervention_time falls before start_time.
Default: no-op.
EpiBranch.start_time Function
start_time(_::AbstractIntervention) -> Float64start_time(intervention)Policy start time for an intervention. Default: 0.0 (always active).
EpiBranch.is_active Function
is_active(
_::AbstractIntervention,
_::SimulationState
) -> AnyWhether an intervention is currently active given the simulation state. Default: always.
State accessors
EpiBranch.onset_time Function
onset_time(ind::Individual) -> Float64Symptom onset time (Float64, NaN if asymptomatic or not set).
EpiBranch.is_isolated Function
is_isolated(ind::Individual) -> BoolWhether the individual is isolated.
EpiBranch.isolation_time Function
isolation_time(ind::Individual) -> Float64Time of isolation (Float64, Inf if not isolated).
EpiBranch.is_traced Function
is_traced(ind::Individual) -> BoolWhether the individual was traced via contact tracing.
EpiBranch.is_quarantined Function
is_quarantined(ind::Individual) -> BoolWhether the individual is quarantined.
EpiBranch.is_vaccinated Function
is_vaccinated(ind::Individual) -> BoolWhether the individual is vaccinated.
EpiBranch.is_asymptomatic Function
is_asymptomatic(ind::Individual) -> BoolWhether the individual is asymptomatic.
EpiBranch.is_test_positive Function
is_test_positive(ind::Individual) -> BoolWhether the individual tested positive.
EpiBranch.is_infected Function
is_infected(ind::Individual) -> BoolWhether the individual was successfully infected (vs contact only).
EpiBranch.individual_type Function
individual_type(ind::Individual) -> Int64Type index for multi-type branching processes (default 1).
EpiBranch.set_isolated! Function
set_isolated!(ind::Individual, time::Float64) -> Float64Mark an individual as isolated at the given time.
Output
EpiBranch.linelist Function
linelist(
state::SimulationState;
reference_date,
delays,
outcomes,
demographics,
rng
) -> Anylinelist(state::SimulationState; reference_date=Date(2020, 1, 1),
delays=DelayOpts(), outcomes=OutcomeOpts(),
demographics=nothing, rng=Random.default_rng())Convert simulation output to a line list DataFrame with one row per case (infected individuals only).
Columns are built dynamically from what is available on each individual:
id,parent_id,generation,chain_id,case_type— always presentdate_infection— always present (frominfection_time+reference_date)date_onset— if:onset_timewas set (viaclinical_presentation())age,sex— if set viademographics()attributes or multi-type. If not present anddemographicskwarg is provided, generated post-hoc.date_reporting,date_admission,date_outcome,outcome— ifdelaysand/oroutcomesare provided and onset times are availableAny other state dict fields are included as additional columns.
If a DemographicOpts is passed via the demographics kwarg, age/sex are generated post-hoc for individuals that don't already have them.
EpiBranch.contacts Function
contacts(
state::SimulationState;
reference_date
) -> DataFrames.DataFramecontacts(state::SimulationState; reference_date=Date(2020, 1, 1))Generate a contacts DataFrame with one row per contact (infected and non-infected).
EpiBranch.chain_statistics Function
chain_statistics(
state::SimulationState
) -> DataFrames.DataFramechain_statistics(state::SimulationState)Compute chain size and length for each transmission chain. Only infected individuals are counted. Returns a DataFrame with columns: chain_id, size, length.
chain_statistics(
states::Vector{<:SimulationState}
) -> DataFrames.DataFramechain_statistics(states::Vector{<:SimulationState})Compute chain statistics across multiple simulations. A DataFrame with columns sim_id, chain_id, size, length is returned.
EpiBranch.containment_probability Function
containment_probability(
states::Vector{<:SimulationState};
max_cases
) -> Float64containment_probability(states::Vector{<:SimulationState}; max_cases=nothing)Fraction of simulations that went extinct (i.e. the outbreak was contained).
If max_cases is provided, simulations that hit the case cap are not considered extinct (they are assumed to have continued growing).
EpiBranch.is_extinct Function
is_extinct(
state::SimulationState;
by_week,
reference_date,
max_cases
) -> Boolis_extinct(state::SimulationState; by_week=nothing, reference_date=Date(2020,1,1),
max_cases=nothing)Extinction classification for a single simulation with optional criteria.
No keyword args: returns
state.extinctby_week::Int: extinct if no cases with onset in that weekby_week::UnitRange{Int}: extinct if no cases with onset in that week rangemax_cases::Int: outbreaks hitting this cap are not considered extinct
EpiBranch.generation_R Function
generation_R(state::SimulationState) -> DataFrames.DataFramegeneration_R(state::SimulationState)Compute effective reproduction number per generation. A DataFrame with columns generation and R_eff is returned.
EpiBranch.weekly_incidence Function
weekly_incidence(
state::SimulationState;
reference_date
) -> DataFrames.DataFrameweekly_incidence(state::SimulationState; reference_date::Date=Date(2020, 1, 1))Compute weekly case counts from a single simulation. A DataFrame with columns week (Date) and cases (Int) is returned.
Analytical
EpiBranch.extinction_probability Function
extinction_probability(
R::Real,
k::Real;
tol,
max_iter
) -> Anyextinction_probability(R::Real, k::Real; tol=1e-10, max_iter=1000)Compute the extinction probability of a branching process with Negative Binomial offspring distribution parameterised by mean R and dispersion k.
Fixed-point iteration on the probability generating function is used. For R ≤ 1, returns 1.0 (certain extinction).
extinction_probability(
d::Distributions.Poisson;
tol,
max_iter
) -> Anyextinction_probability(d::Distribution; tol=1e-10, max_iter=1000)Compute extinction probability for any discrete offspring distribution via fixed-point iteration on the PGF.
For Poisson(λ): the PGF exp(λ(s-1)) is used. For NegativeBinomial: R and k are extracted and the closed-form PGF is applied.
extinction_probability(
model::TransmissionModel;
kwargs...
) -> Anyextinction_probability(model::TransmissionModel; kwargs...)Extinction probability for a single-type transmission model, extracted from the model's offspring specification via _single_type_offspring. Works for BranchingProcess and wrappers that delegate that accessor (e.g. PartiallyObserved).
EpiBranch.epidemic_probability Function
epidemic_probability(R::Real, k::Real; kwargs...) -> Anyepidemic_probability(R::Real, k::Real; kwargs...)Probability that a single introduction leads to a major epidemic. Complement of extinction probability.
epidemic_probability(
d::Distributions.Distribution;
kwargs...
) -> Anyepidemic_probability(d::Distribution; kwargs...)Probability of a major epidemic for a given offspring distribution.
epidemic_probability(
model::TransmissionModel;
kwargs...
) -> Anyepidemic_probability(model::TransmissionModel; kwargs...)Epidemic probability for a single-type transmission model.
EpiBranch.probability_contain Function
probability_contain(
R::Real,
k::Real;
n_initial,
ind_control,
pop_control,
tol,
max_iter
) -> Anyprobability_contain(R, k; n_initial=1, ind_control=0.0, pop_control=0.0)Probability that an outbreak is contained (goes extinct), accounting for individual-level and population-level control measures and multiple initial infections.
ind_control: probability each case is individually controlled (removed before transmitting), e.g. through case isolationpop_control: population-level reduction in R, e.g. through social distancing. Effective R becomes(1 - pop_control) * Rn_initial: number of initial independent introductions
The containment probability for a single introduction is:
q = ind_control + (1 - ind_control) * pgf(q)where pgf is the PGF of the offspring distribution with effective R. For n_initial independent introductions, the probability is q^n_initial.
probability_contain(
d::Distributions.NegativeBinomial;
kwargs...
) -> Anyprobability_contain(d::Distribution; n_initial=1, ind_control=0.0, pop_control=0.0)Containment probability for a given offspring distribution.
probability_contain(
model::TransmissionModel;
kwargs...
) -> Anyprobability_contain(model::TransmissionModel; kwargs...)Containment probability for a single-type transmission model. Delegates through _single_type_offspring, so wrappers such as PartiallyObserved work too.
EpiBranch.proportion_transmission Function
proportion_transmission(R::Real, k::Real; prop_cases) -> Anyproportion_transmission(R::Real, k::Real; prop_cases::Real=0.2)Compute the proportion of transmission caused by the most infectious fraction prop_cases of cases, under a Negative Binomial offspring distribution with mean R and dispersion k.
This is the "80/20 rule" metric for superspreading: with prop_cases=0.2, returns the proportion of all transmission events caused by the top 20% of transmitters.
Computed via the regularised incomplete beta function.
proportion_transmission(
d::Distributions.NegativeBinomial;
prop_cases
) -> Anyproportion_transmission(model::BranchingProcess; prop_cases=0.2)Proportion of transmission from the most infectious fraction of cases, extracted from the model's offspring distribution (must be NegativeBinomial).
EpiBranch.proportion_cluster_size Function
proportion_cluster_size(
R::Real,
k::Real;
cluster_size
) -> Anyproportion_cluster_size(R, k; cluster_size=10)Proportion of secondary cases that arise from transmission events where the infector caused at least cluster_size secondary cases.
This quantifies case concentration: with high overdispersion (low k), a large fraction of cases come from a few superspreading events.
Uses the tail expectation of the NegBin distribution: E[X | X ≥ c] × P(X ≥ c) / E[X]
proportion_cluster_size(
d::Distributions.NegativeBinomial;
cluster_size
) -> Anyproportion_cluster_size(d::NegativeBinomial; cluster_size=10)Proportion of cases from large clusters for a NegBin offspring distribution.
proportion_cluster_size(
model::TransmissionModel;
cluster_size
) -> Anyproportion_cluster_size(model::BranchingProcess; cluster_size=10)Proportion of cases from large clusters for a branching process model.
EpiBranch.network_R Function
network_R(
mean_contacts::Real,
sd_contacts::Real,
duration::Real,
prob_transmission::Real
) -> NamedTuple{(:R, :R_net), <:Tuple{Any, Any}}network_R(mean_contacts, sd_contacts, duration, prob_transmission)Compute the basic reproduction number adjusted for heterogeneous contact patterns in a network.
Returns a named tuple (R=..., R_net=...):
R: unadjusted, assuming homogeneous mixing (β × mean_contacts × duration)R_net: network-adjusted, accounting for contact heterogeneity (β × duration × (mean + variance/mean))
The adjustment reflects that high-contact individuals both acquire and transmit more, amplifying R beyond what homogeneous mixing predicts.
EpiBranch.chain_size_distribution Function
chain_size_distribution(
m::PartiallyObserved
) -> ThinnedChainSizechain_size_distribution(m::PartiallyObserved)Return ThinnedChainSize wrapping the chain size distribution of the inner model. Because the call recurses, a nested PartiallyObserved produces a nested ThinnedChainSize, and logpdf on that gives the right likelihood without any pairwise dispatch.
chain_size_distribution(d::Distributions.Poisson) -> Borelchain_size_distribution(offspring::Poisson)Analytical chain size distribution for Poisson offspring.
chain_size_distribution(
d::Distributions.NegativeBinomial
) -> GammaBorelchain_size_distribution(offspring::NegativeBinomial)Analytical chain size distribution for NegativeBinomial offspring.
chain_size_distribution(
model::TransmissionModel
) -> ThinnedChainSizechain_size_distribution(model::TransmissionModel)Analytical chain size distribution extracted from the model's offspring specification via _single_type_offspring. Works for BranchingProcess and any wrapper that delegates that accessor (e.g. PartiallyObserved delegates before applying its own transformed distribution).
chain_size_distribution(
o::ClusterMixed
) -> PoissonGammaChainSizechain_size_distribution(o::ClusterMixed)Return the chain size distribution for a cluster-mixed offspring. Uses the closed form when one is known (e.g. Poisson + Gamma returns PoissonGammaChainSize); otherwise returns ChainSizeMixture, which evaluates the PMF pointwise by numerical quadrature.
EpiBranch.Borel Type
struct Borel{T<:AbstractFloat} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}Borel(μ)The Borel distribution with parameter μ > 0.
P(X = n) = (μn)^(n-1) * exp(-μn) / n! for n = 1, 2, ...
This is the chain size distribution for a Poisson(μ) branching process. For μ > 1 (supercritical) the PMF is still valid at each n, but its total mass is less than 1: chains are infinite with positive probability. We keep the PMF defined in the supercritical region so that integrating chain size PMFs over a mixing distribution that spans both sides of 1 works pointwise.
Fields
μ::AbstractFloat
EpiBranch.GammaBorel Type
struct GammaBorel{T<:AbstractFloat} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}GammaBorel(k, R)Chain size distribution for a NegativeBinomial(k, R) branching process, derived via Lagrange inversion.
For R > 1 (supercritical) the PMF is still valid at each n, but its total mass is less than 1: chains are infinite with positive probability.
Fields
k::AbstractFloatR::AbstractFloat
EpiBranch.PoissonGammaChainSize Type
struct PoissonGammaChainSize{T<:AbstractFloat} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}PoissonGammaChainSize(k, R)Chain size distribution when the per-chain offspring distribution is Poisson(λ) with λ ~ Gamma(shape = k, mean = R). This corresponds to rate heterogeneity at the chain (cluster) level rather than the individual level, and matches the gborel likelihood in epichains.
Note: this is different from GammaBorel, which is the chain size distribution for NegativeBinomial offspring (Gamma-Poisson mixing at the individual level).
Fields
k::AbstractFloatR::AbstractFloat
Inference
Data types
EpiBranch.OffspringCounts Type
struct OffspringCountsObserved secondary case counts – the number of individuals each case infected. Used with loglikelihood and fit.
Examples
data = OffspringCounts([0, 1, 2, 0, 3, 1, 0])
fit(data, NegativeBinomial)Fields
data::Vector{Int64}
EpiBranch.ChainSizes Type
struct ChainSizesObserved transmission chain sizes (total number of cases per chain). Used with loglikelihood and fit.
Examples
data = ChainSizes([1, 1, 3, 1, 5])
fit(data, NegativeBinomial)Fields
data::Vector{Int64}
EpiBranch.ChainLengths Type
struct ChainLengthsObserved transmission chain lengths (number of generations). Used with loglikelihood and fit.
Examples
data = ChainLengths([0, 1, 0, 2, 1])
fit(data, Poisson)Fields
data::Vector{Int64}
Observation models
EpiBranch.PartiallyObserved Type
struct PartiallyObserved{M<:TransmissionModel} <: TransmissionModelPartiallyObserved(model, detection_prob)Wrap a TransmissionModel with independent per-case detection. Each case in a chain is detected with probability detection_prob. Chains with zero detected cases are unobserved.
Likelihoods marginalise over the true (unobserved) chain sizes. If the wrapped model has an analytical chain size distribution, that is used; otherwise the likelihood falls back to simulation.
Examples
base = BranchingProcess(NegBin(0.8, 0.5))
model = PartiallyObserved(base, 0.7)
loglikelihood(ChainSizes([1, 1, 2, 3]), model)Fields
model::TransmissionModeldetection_prob::Float64
EpiBranch.ThinnedChainSize Type
struct ThinnedChainSize{D<:Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}ThinnedChainSize(base, detection_prob)Distribution of observed chain sizes when each case in a base chain is detected with probability detection_prob. Nesting applies a second round of Binomial thinning.
logpdf(d, obs) sums logpdf(base, n) + logpdf(Binomial(n, p), obs) over n >= obs until the tail is negligible. The computation only needs logpdf on the base, so a nested ThinnedChainSize gives the same answer as a single thinning with a compounded probability without any specialised method.
Fields
base::Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}detection_prob::Float64
Cluster-level heterogeneity
EpiBranch.ClusterMixed Type
struct ClusterMixed{F, D<:Distributions.Distribution}ClusterMixed(build, mixing)Offspring specification with cluster-level heterogeneity: each chain draws θ from mixing, and the offspring distribution within that chain is build(θ).
If build is a distribution family type (e.g. Poisson) and a closed form exists for the combination, dispatch uses it automatically. For everything else the likelihood falls back to numerical quadrature over mixing.
Examples
# Poisson offspring with Gamma-distributed rate uses the closed-form
# PoissonGammaChainSize via dispatch.
o = ClusterMixed(Poisson, Gamma(2.0, 0.4))
loglikelihood(ChainSizes([1, 2, 1, 5]), o)
# NegBin offspring with Gamma-distributed R (fixed k) has no closed
# form and is evaluated by quadrature.
o = ClusterMixed(R -> NegBin(R, 0.5), Gamma(2.0, 0.3))
loglikelihood(ChainSizes([1, 1, 3, 2]), o)Fields
build::Anymixing::Distributions.Distribution
EpiBranch.ChainSizeMixture Type
struct ChainSizeMixture{F, D<:Distributions.Distribution} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}ChainSizeMixture(build, mixing)Chain size distribution defined by integrating the chain size PMF of build(θ) over mixing. logpdf(d, n) uses adaptive Gauss-Kronrod quadrature on the 0.001-0.999 quantile range of mixing.
This is the generic chain size distribution for a ClusterMixed offspring. When a closed form exists (e.g. PoissonGammaChainSize for Poisson + Gamma), chain_size_distribution dispatches to it directly instead.
Fields
build::Anymixing::Distributions.Distribution
Likelihood and fitting
EpiBranch extends Distributions.loglikelihood and Distributions.fit with methods that accept the data types above:
loglikelihood(OffspringCounts(data), Poisson(0.5))
loglikelihood(ChainSizes(data), NegBin(0.8, 0.5))
loglikelihood(ChainLengths(data), Poisson(0.5))
loglikelihood(ChainSizes(data), model; interventions=[iso])
fit(Poisson, OffspringCounts(data))
fit(NegativeBinomial, OffspringCounts(data))
fit(Poisson, ChainSizes(data))
fit(NegativeBinomial, ChainSizes(data))
fit(Poisson, ChainLengths(data))See the chains tutorial for examples.
Init functions
EpiBranch.Disease Function
Disease(; incubation_period, prob_asymptomatic)Disease(; incubation_period, prob_asymptomatic=0.0)Convenience wrapper for specifying disease properties. Can be passed directly as the attributes argument to simulate.
Sets :onset_time and :asymptomatic on each individual. Equivalent to clinical_presentation(; incubation_period, prob_asymptomatic) but with a name that reflects what is being specified.
disease = Disease(incubation_period = LogNormal(1.5, 0.5), prob_asymptomatic = 0.3)
simulate(model; attributes = disease, interventions = [iso])EpiBranch.clinical_presentation Function
clinical_presentation(
;
incubation_period,
prob_asymptomatic
)clinical_presentation(; incubation_period, prob_asymptomatic=0.0)Return an attributes function. :onset_time and :asymptomatic are set on each individual. Required by Isolation.
EpiBranch.demographics Function
demographics(
;
age_distribution,
age_range,
prob_female
) -> EpiBranch.var"#62#63"{NoAgeDistribution, Tuple{Int64, Int64}, Float64}demographics(; age_distribution=nothing, age_range=(0, 90), prob_female=0.5)Return an attributes function. :age and :sex are set on each individual.
EpiBranch.compose Function
compose(
fs...
) -> EpiBranch.var"#compose##0#compose##1"{<:Tuple}compose(fs...)Compose multiple attributes functions into one, called in order.
Convenience constructors
EpiBranch.NegBin Function
NegBin(R::Real, k::Real) -> Distributions.NegativeBinomialNegBin(R, k)Convenience constructor for a Negative Binomial offspring distribution parameterised by mean reproduction number R and dispersion parameter k.
A NegativeBinomial from Distributions.jl is returned, with mean R and variance R + R²/k.
Note: NegativeBinomial(r, p) from Distributions.jl uses a different parameterisation (number of successes and success probability). Using it directly as an offspring distribution will produce silently wrong results. Always use NegBin(R, k) for epidemiological parameterisation.
EpiBranch.scale_distribution Function
scale_distribution(
d::Distributions.Poisson,
factor::Real
) -> Distributions.Poissonscale_distribution(d::Distribution, factor::Real)Scale a distribution's mean by factor, preserving distribution family and shape. For Poisson: a Poisson(λ * factor) is returned. For NegativeBinomial: a NegativeBinomial with same k and scaled mean is returned.
EpiBranch.ringbp_generation_time Function
ringbp_generation_time(
;
presymptomatic_fraction,
omega
) -> EpiBranch.var"#18#19"{Float64, Float64}ringbp_generation_time(; presymptomatic_fraction=0.3, omega=2.0)Return a function suitable for the generation_time field of a BranchingProcess, implementing ringbp's incubation-linked generation time model.
The returned function takes an incubation period (Float64) and produces a truncated skew-normal distribution SN(ξ, ω, α), where ξ = incubation period, and α is chosen so that the fraction of generation times shorter than the incubation period equals presymptomatic_fraction. This matches the generation time model in ringbp (Hellewell et al. 2020).
Usage:
model = BranchingProcess(
NegBin(2.5, 0.16),
ringbp_generation_time(presymptomatic_fraction=0.3)
)Internals
These functions are not part of the public API but are documented for developers extending the package.
EpiBranch.get_generation_time Function
get_generation_time(
gt::Distributions.Distribution,
individual
) -> Distributions.Distributionget_generation_time(gt, individual)Return the generation time distribution for a specific individual. For a Distribution, the distribution is returned unchanged. For a Function, it is called with the individual's incubation period.
EpiBranch._draw_offspring Function
_draw_offspring(
rng::Random.AbstractRNG,
offspring::Distributions.Distribution,
individual,
state::SimulationState
) -> AnySingle-type offspring draw.
_draw_offspring(
rng::Random.AbstractRNG,
offspring::Function,
individual,
state::SimulationState
) -> AnyFunction-based offspring draw. The function receives the RNG and individual.
_draw_offspring(
rng::Random.AbstractRNG,
offspring::ClusterMixed,
individual,
state::SimulationState
) -> Any_draw_offspring(rng, offspring::ClusterMixed, individual, state)Draw offspring under a cluster-mixed specification. Samples θ ~ mixing once per chain, caches it on the index case, and looks it up via parent_id for every descendant so all members of a chain share θ.
EpiBranch._create_contacts! Function
_create_contacts!(
new_contacts,
new_infected_ids,
n_contacts::Int64,
parent,
state,
gt_dist,
pop_suscept,
residual,
interventions,
next_id
) -> AnySingle-type contacts.
_create_contacts!(
new_contacts,
new_infected_ids,
counts::Vector{Int64},
parent,
state,
gt_dist,
pop_suscept,
residual,
interventions,
next_id
) -> AnyMulti-type contacts.
EpiBranch._resolve_infection Function
_resolve_infection(
rng::Random.AbstractRNG,
parent,
contact,
generation_time::Float64,
pop_suscept::Float64,
post_isolation_transmission::Float64
) -> BoolDetermine whether a contact is successfully infected via competing risks.
EpiBranch._susceptible_fraction Function
_susceptible_fraction(
state::SimulationState{<:Any, NoPopulation}
) -> Float64Fraction of the population still susceptible (1.0 for infinite population).
EpiBranch.post_isolation_transmission Function
post_isolation_transmission(
_::AbstractIntervention
) -> Float64Fraction of transmission that occurs after isolation. Default: 0 (no transmission after isolation).
EpiBranch._post_isolation_transmission Function
_post_isolation_transmission(interventions) -> Float64_post_isolation_transmission(interventions)Maximum residual transmission across all interventions in the stack.
EpiBranch.population_size Function
population_size(_::TransmissionModel) -> AnyInterface methods with defaults for any TransmissionModel.
EpiBranch._create_individual Function
_create_individual(
state::SimulationState,
parent_id::Int64,
chain_id::Int64,
next_id::Int64,
inf_time::Float64,
interventions
) -> IndividualCreate a new Individual with attributes and intervention state.
EpiBranch.logsumexp Function
logsumexp(x) -> Anylogsumexp(x)Numerically stable log-sum-exp. Any iterable is accepted (generators are collected first to allow two-pass computation).
EpiBranch.required_fields Function
required_fields(_::AbstractIntervention) -> Vector{Symbol}Fields that an intervention requires on individuals. Default: none.
EpiBranch._validate_required_fields Function
_validate_required_fields(individual, interventions)Check that all required fields are present on an individual.
EpiBranch._column_order Function
_column_order(ks) -> Vector{Symbol}Sensible column ordering for the linelist DataFrame.
EpiBranch._with_start_time Function
_with_start_time(
intervention::AbstractIntervention,
t::Float64
) -> AnyReconstruct an intervention with start_time set, if the type supports it.
EpiBranch._enforce_start_time! Function
_enforce_start_time!(intervention, individual)_enforce_start_time!(intervention, individual)Check whether the intervention's effect on this individual falls before the policy start time. If so, undo it.
EpiBranch._chain_length_ll_negbin Function
_chain_length_ll_negbin(
data,
offspring::Distributions.NegativeBinomial
) -> AnyAnalytical chain length likelihood for NegBin offspring.
EpiBranch._borel_logpdf Function
_borel_logpdf(μ, n::Integer) -> AnyLog-PDF of the Borel distribution. Accepts any numeric type for μ (AD-compatible).
EpiBranch._gammaborel_logpdf Function
_gammaborel_logpdf(k, R, n::Integer) -> AnyLog-PDF of the GammaBorel distribution. Accepts any numeric type for k, R (AD-compatible).
EpiBranch._single_type_offspring Function
_single_type_offspring(model::TransmissionModel) -> AnyExtract the offspring specification from a single-type model.
Returns whatever the model stores in offspring (typically a Distribution, but can also be any type for which chain_size_distribution is defined — e.g. ClusterMixed). Callers that need a Distribution specifically should check the return type. Throws only for multi-type (function-based) offspring, which this accessor cannot sensibly return.
Wrapper models (e.g. PartiallyObserved) should specialise this to delegate through to the wrapped model.
EpiBranch._empirical_ll Function
_empirical_ll(
observed,
simulated;
min_val,
censored,
cap
) -> AnyEmpirical log-likelihood with Laplace smoothing and right-censoring.
When censored is provided, simulated values flagged as censored contribute to P(size >= cap) rather than P(size = cap). Observed values at or above cap are evaluated as P(size >= cap).
EpiBranch._golden_section_min Function
_golden_section_min(
f,
lo::Float64,
hi::Float64;
tol,
maxiter
) -> Float64Golden section search for minimum of a 1D function on [lo, hi].