Skip to content

API reference

Transmission models

EpiBranch.BranchingProcess Type
julia
struct BranchingProcess{O, G, P, L} <: TransmissionModel

Stochastic branching process transmission model.

Examples

julia
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-type

Fields

  • offspring::Any

  • generation_time::Any

  • population_size::Any

  • latent_period::Float64

  • n_types::Int64

  • type_labels::Any

Types

EpiBranch.Individual Type
julia
mutable struct Individual

A 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::Int64

  • parent_id::Int64

  • generation::Int64

  • chain_id::Int64

  • infection_time::Float64

  • susceptibility::Float64

  • infectiousness::Float64

  • secondary_case_ids::Vector{Int64}

  • state::Dict{Symbol, Any}

EpiBranch.SimulationState Type
julia
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::Int64

  • rng::Random.AbstractRNG

  • cumulative_cases::Int64

  • extinct::Bool

  • population_size::Any

  • latent_period::Float64

  • max_infection_time::Float64

  • attributes::Any

EpiBranch.SimOpts Type
julia
struct SimOpts

Options controlling simulation termination and setup. Contains only simulation control parameters – clinical and demographic properties are set via attributes functions and interventions.


Fields

  • max_cases::Int64

  • max_generations::Int64

  • max_time::Float64

  • n_initial::Int64

EpiBranch.DelayOpts Type
julia
struct DelayOpts{R, A, O}

Distributions for delays used in line list generation.


Fields

  • onset_to_reporting::Any

  • onset_to_admission::Any

  • onset_to_outcome::Any

EpiBranch.OutcomeOpts Type
julia
struct OutcomeOpts{C}

Parameters controlling case outcomes (hospitalisation and death).


Fields

  • prob_hospitalisation::Float64

  • prob_death::Float64

  • age_specific_cfr::Any

EpiBranch.DemographicOpts Type
julia
struct DemographicOpts{D}

Parameters for generating demographic data (age, sex).


Fields

  • age_distribution::Any

  • age_range::Tuple{Int64, Int64}

  • prob_female::Float64

Simulation

EpiBranch.simulate Function
julia
simulate(
    model::TransmissionModel;
    interventions,
    attributes,
    sim_opts,
    rng,
    condition,
    max_attempts
) -> SimulationState{Random.TaskLocalRNG, _A, NoAttributes} where _A
julia
simulate(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
julia
simulate_batch(
    model::TransmissionModel,
    n::Int64;
    interventions,
    attributes,
    sim_opts,
    rng,
    parallel
) -> Vector
julia
simulate_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
julia
step!(
    model::BranchingProcess,
    state::SimulationState,
    interventions
) -> Union{SimulationState{R, Int64} where R<:Random.AbstractRNG, SimulationState{R, NoPopulation} where R<:Random.AbstractRNG}
julia
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
julia
abstract type AbstractIntervention

Base 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
julia
struct Isolation <: AbstractIntervention

Isolate 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.Distribution

  • start_time::Float64

  • post_isolation_transmission::Float64

  • test_sensitivity::Float64

EpiBranch.ContactTracing Type
julia
struct ContactTracing <: AbstractIntervention

Trace 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::Float64

  • delay::Distributions.Distribution

  • quarantine_on_trace::Bool

  • start_time::Float64

EpiBranch.RingVaccination Type
julia
struct RingVaccination <: AbstractIntervention

Vaccinate 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::Float64

  • delay_to_immunity::Float64

  • mode::Symbol

EpiBranch.Scheduled Type
julia
struct Scheduled{I<:AbstractIntervention, F} <: AbstractIntervention

Wrap 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 &&:

julia
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:

julia
Scheduled(iso, state -> state.current_generation >= 3)

Fields

  • intervention::AbstractIntervention

  • condition::Any

EpiBranch.initialise_individual! Function
julia
initialise_individual!(
    _::AbstractIntervention,
    individual,
    state
) -> Any

Set up intervention-specific fields on a newly created individual. Default: no-op.

EpiBranch.resolve_individual! Function
julia
resolve_individual!(
    _::AbstractIntervention,
    individual,
    state
)

Determine intervention state before transmission. Default: no-op.

EpiBranch.apply_post_transmission! Function
julia
apply_post_transmission!(
    _::AbstractIntervention,
    state,
    new_contacts
)

Act on contacts after creation. All contacts are received. Default: no-op.

EpiBranch.intervention_time Function
julia
intervention_time(
    _::AbstractIntervention,
    _::Individual
) -> Float64
julia
intervention_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
julia
reset!(_::AbstractIntervention, _::Individual)
julia
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
julia
start_time(_::AbstractIntervention) -> Float64
julia
start_time(intervention)

Policy start time for an intervention. Default: 0.0 (always active).

EpiBranch.is_active Function
julia
is_active(
    _::AbstractIntervention,
    _::SimulationState
) -> Any

Whether an intervention is currently active given the simulation state. Default: always.

State accessors

EpiBranch.onset_time Function
julia
onset_time(ind::Individual) -> Float64

Symptom onset time (Float64, NaN if asymptomatic or not set).

EpiBranch.is_isolated Function
julia
is_isolated(ind::Individual) -> Bool

Whether the individual is isolated.

EpiBranch.isolation_time Function
julia
isolation_time(ind::Individual) -> Float64

Time of isolation (Float64, Inf if not isolated).

EpiBranch.is_traced Function
julia
is_traced(ind::Individual) -> Bool

Whether the individual was traced via contact tracing.

EpiBranch.is_quarantined Function
julia
is_quarantined(ind::Individual) -> Bool

Whether the individual is quarantined.

EpiBranch.is_vaccinated Function
julia
is_vaccinated(ind::Individual) -> Bool

Whether the individual is vaccinated.

EpiBranch.is_asymptomatic Function
julia
is_asymptomatic(ind::Individual) -> Bool

Whether the individual is asymptomatic.

EpiBranch.is_test_positive Function
julia
is_test_positive(ind::Individual) -> Bool

Whether the individual tested positive.

EpiBranch.is_infected Function
julia
is_infected(ind::Individual) -> Bool

Whether the individual was successfully infected (vs contact only).

EpiBranch.individual_type Function
julia
individual_type(ind::Individual) -> Int64

Type index for multi-type branching processes (default 1).

EpiBranch.set_isolated! Function
julia
set_isolated!(ind::Individual, time::Float64) -> Float64

Mark an individual as isolated at the given time.

Output

EpiBranch.linelist Function
julia
linelist(
    state::SimulationState;
    reference_date,
    delays,
    outcomes,
    demographics,
    rng
) -> Any
julia
linelist(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 present

  • date_infection — always present (from infection_time + reference_date)

  • date_onset — if :onset_time was set (via clinical_presentation())

  • age, sex — if set via demographics() attributes or multi-type. If not present and demographics kwarg is provided, generated post-hoc.

  • date_reporting, date_admission, date_outcome, outcome — if delays and/or outcomes are provided and onset times are available

  • Any 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
julia
contacts(
    state::SimulationState;
    reference_date
) -> DataFrames.DataFrame
julia
contacts(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
julia
chain_statistics(
    state::SimulationState
) -> DataFrames.DataFrame
julia
chain_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.

julia
chain_statistics(
    states::Vector{<:SimulationState}
) -> DataFrames.DataFrame
julia
chain_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
julia
containment_probability(
    states::Vector{<:SimulationState};
    max_cases
) -> Float64
julia
containment_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
julia
is_extinct(
    state::SimulationState;
    by_week,
    reference_date,
    max_cases
) -> Bool
julia
is_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.extinct

  • by_week::Int: extinct if no cases with onset in that week

  • by_week::UnitRange{Int}: extinct if no cases with onset in that week range

  • max_cases::Int: outbreaks hitting this cap are not considered extinct

EpiBranch.generation_R Function
julia
generation_R(state::SimulationState) -> DataFrames.DataFrame
julia
generation_R(state::SimulationState)

Compute effective reproduction number per generation. A DataFrame with columns generation and R_eff is returned.

EpiBranch.weekly_incidence Function
julia
weekly_incidence(
    state::SimulationState;
    reference_date
) -> DataFrames.DataFrame
julia
weekly_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
julia
extinction_probability(
    R::Real,
    k::Real;
    tol,
    max_iter
) -> Any
julia
extinction_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).

julia
extinction_probability(
    d::Distributions.Poisson;
    tol,
    max_iter
) -> Any
julia
extinction_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.

julia
extinction_probability(
    model::TransmissionModel;
    kwargs...
) -> Any
julia
extinction_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
julia
epidemic_probability(R::Real, k::Real; kwargs...) -> Any
julia
epidemic_probability(R::Real, k::Real; kwargs...)

Probability that a single introduction leads to a major epidemic. Complement of extinction probability.

julia
epidemic_probability(
    d::Distributions.Distribution;
    kwargs...
) -> Any
julia
epidemic_probability(d::Distribution; kwargs...)

Probability of a major epidemic for a given offspring distribution.

julia
epidemic_probability(
    model::TransmissionModel;
    kwargs...
) -> Any
julia
epidemic_probability(model::TransmissionModel; kwargs...)

Epidemic probability for a single-type transmission model.

EpiBranch.probability_contain Function
julia
probability_contain(
    R::Real,
    k::Real;
    n_initial,
    ind_control,
    pop_control,
    tol,
    max_iter
) -> Any
julia
probability_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 isolation

  • pop_control: population-level reduction in R, e.g. through social distancing. Effective R becomes (1 - pop_control) * R

  • n_initial: number of initial independent introductions

The containment probability for a single introduction is:

julia
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.

julia
probability_contain(
    d::Distributions.NegativeBinomial;
    kwargs...
) -> Any
julia
probability_contain(d::Distribution; n_initial=1, ind_control=0.0, pop_control=0.0)

Containment probability for a given offspring distribution.

julia
probability_contain(
    model::TransmissionModel;
    kwargs...
) -> Any
julia
probability_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
julia
proportion_transmission(R::Real, k::Real; prop_cases) -> Any
julia
proportion_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.

julia
proportion_transmission(
    d::Distributions.NegativeBinomial;
    prop_cases
) -> Any
julia
proportion_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
julia
proportion_cluster_size(
    R::Real,
    k::Real;
    cluster_size
) -> Any
julia
proportion_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]

julia
proportion_cluster_size(
    d::Distributions.NegativeBinomial;
    cluster_size
) -> Any
julia
proportion_cluster_size(d::NegativeBinomial; cluster_size=10)

Proportion of cases from large clusters for a NegBin offspring distribution.

julia
proportion_cluster_size(
    model::TransmissionModel;
    cluster_size
) -> Any
julia
proportion_cluster_size(model::BranchingProcess; cluster_size=10)

Proportion of cases from large clusters for a branching process model.

EpiBranch.network_R Function
julia
network_R(
    mean_contacts::Real,
    sd_contacts::Real,
    duration::Real,
    prob_transmission::Real
) -> NamedTuple{(:R, :R_net), <:Tuple{Any, Any}}
julia
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
julia
chain_size_distribution(
    m::PartiallyObserved
) -> ThinnedChainSize
julia
chain_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.

julia
chain_size_distribution(d::Distributions.Poisson) -> Borel
julia
chain_size_distribution(offspring::Poisson)

Analytical chain size distribution for Poisson offspring.

julia
chain_size_distribution(
    d::Distributions.NegativeBinomial
) -> GammaBorel
julia
chain_size_distribution(offspring::NegativeBinomial)

Analytical chain size distribution for NegativeBinomial offspring.

julia
chain_size_distribution(
    model::TransmissionModel
) -> ThinnedChainSize
julia
chain_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).

julia
chain_size_distribution(
    o::ClusterMixed
) -> PoissonGammaChainSize
julia
chain_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
julia
struct Borel{T<:AbstractFloat} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}
julia
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
julia
struct GammaBorel{T<:AbstractFloat} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}
julia
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::AbstractFloat

  • R::AbstractFloat

EpiBranch.PoissonGammaChainSize Type
julia
struct PoissonGammaChainSize{T<:AbstractFloat} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}
julia
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::AbstractFloat

  • R::AbstractFloat

Inference

Data types

EpiBranch.OffspringCounts Type
julia
struct OffspringCounts

Observed secondary case counts – the number of individuals each case infected. Used with loglikelihood and fit.

Examples

julia
data = OffspringCounts([0, 1, 2, 0, 3, 1, 0])
fit(data, NegativeBinomial)

Fields

  • data::Vector{Int64}
EpiBranch.ChainSizes Type
julia
struct ChainSizes

Observed transmission chain sizes (total number of cases per chain). Used with loglikelihood and fit.

Examples

julia
data = ChainSizes([1, 1, 3, 1, 5])
fit(data, NegativeBinomial)

Fields

  • data::Vector{Int64}
EpiBranch.ChainLengths Type
julia
struct ChainLengths

Observed transmission chain lengths (number of generations). Used with loglikelihood and fit.

Examples

julia
data = ChainLengths([0, 1, 0, 2, 1])
fit(data, Poisson)

Fields

  • data::Vector{Int64}

Observation models

EpiBranch.PartiallyObserved Type
julia
struct PartiallyObserved{M<:TransmissionModel} <: TransmissionModel
julia
PartiallyObserved(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

julia
base = BranchingProcess(NegBin(0.8, 0.5))
model = PartiallyObserved(base, 0.7)
loglikelihood(ChainSizes([1, 1, 2, 3]), model)

Fields

  • model::TransmissionModel

  • detection_prob::Float64

EpiBranch.ThinnedChainSize Type
julia
struct ThinnedChainSize{D<:Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}
julia
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
julia
struct ClusterMixed{F, D<:Distributions.Distribution}
julia
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

julia
# 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::Any

  • mixing::Distributions.Distribution

EpiBranch.ChainSizeMixture Type
julia
struct ChainSizeMixture{F, D<:Distributions.Distribution} <: Distributions.Distribution{Distributions.Univariate, Distributions.Discrete}
julia
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::Any

  • mixing::Distributions.Distribution

Likelihood and fitting

EpiBranch extends Distributions.loglikelihood and Distributions.fit with methods that accept the data types above:

julia
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
julia
Disease(; incubation_period, prob_asymptomatic)
julia
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.

julia
disease = Disease(incubation_period = LogNormal(1.5, 0.5), prob_asymptomatic = 0.3)
simulate(model; attributes = disease, interventions = [iso])
EpiBranch.clinical_presentation Function
julia
clinical_presentation(
;
    incubation_period,
    prob_asymptomatic
)
julia
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
julia
demographics(
;
    age_distribution,
    age_range,
    prob_female
) -> EpiBranch.var"#62#63"{NoAgeDistribution, Tuple{Int64, Int64}, Float64}
julia
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
julia
compose(
    fs...
) -> EpiBranch.var"#compose##0#compose##1"{<:Tuple}
julia
compose(fs...)

Compose multiple attributes functions into one, called in order.

Convenience constructors

EpiBranch.NegBin Function
julia
NegBin(R::Real, k::Real) -> Distributions.NegativeBinomial
julia
NegBin(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
julia
scale_distribution(
    d::Distributions.Poisson,
    factor::Real
) -> Distributions.Poisson
julia
scale_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
julia
ringbp_generation_time(
;
    presymptomatic_fraction,
    omega
) -> EpiBranch.var"#18#19"{Float64, Float64}
julia
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:

julia
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
julia
get_generation_time(
    gt::Distributions.Distribution,
    individual
) -> Distributions.Distribution
julia
get_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
julia
_draw_offspring(
    rng::Random.AbstractRNG,
    offspring::Distributions.Distribution,
    individual,
    state::SimulationState
) -> Any

Single-type offspring draw.

julia
_draw_offspring(
    rng::Random.AbstractRNG,
    offspring::Function,
    individual,
    state::SimulationState
) -> Any

Function-based offspring draw. The function receives the RNG and individual.

julia
_draw_offspring(
    rng::Random.AbstractRNG,
    offspring::ClusterMixed,
    individual,
    state::SimulationState
) -> Any
julia
_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
julia
_create_contacts!(
    new_contacts,
    new_infected_ids,
    n_contacts::Int64,
    parent,
    state,
    gt_dist,
    pop_suscept,
    residual,
    interventions,
    next_id
) -> Any

Single-type contacts.

julia
_create_contacts!(
    new_contacts,
    new_infected_ids,
    counts::Vector{Int64},
    parent,
    state,
    gt_dist,
    pop_suscept,
    residual,
    interventions,
    next_id
) -> Any

Multi-type contacts.

EpiBranch._resolve_infection Function
julia
_resolve_infection(
    rng::Random.AbstractRNG,
    parent,
    contact,
    generation_time::Float64,
    pop_suscept::Float64,
    post_isolation_transmission::Float64
) -> Bool

Determine whether a contact is successfully infected via competing risks.

EpiBranch._susceptible_fraction Function
julia
_susceptible_fraction(
    state::SimulationState{<:Any, NoPopulation}
) -> Float64

Fraction of the population still susceptible (1.0 for infinite population).

EpiBranch.post_isolation_transmission Function
julia
post_isolation_transmission(
    _::AbstractIntervention
) -> Float64

Fraction of transmission that occurs after isolation. Default: 0 (no transmission after isolation).

EpiBranch._post_isolation_transmission Function
julia
_post_isolation_transmission(interventions) -> Float64
julia
_post_isolation_transmission(interventions)

Maximum residual transmission across all interventions in the stack.

EpiBranch.population_size Function
julia
population_size(_::TransmissionModel) -> Any

Interface methods with defaults for any TransmissionModel.

EpiBranch._create_individual Function
julia
_create_individual(
    state::SimulationState,
    parent_id::Int64,
    chain_id::Int64,
    next_id::Int64,
    inf_time::Float64,
    interventions
) -> Individual

Create a new Individual with attributes and intervention state.

EpiBranch.logsumexp Function
julia
logsumexp(x) -> Any
julia
logsumexp(x)

Numerically stable log-sum-exp. Any iterable is accepted (generators are collected first to allow two-pass computation).

EpiBranch.required_fields Function
julia
required_fields(_::AbstractIntervention) -> Vector{Symbol}

Fields that an intervention requires on individuals. Default: none.

EpiBranch._validate_required_fields Function
julia
_validate_required_fields(individual, interventions)

Check that all required fields are present on an individual.

EpiBranch._column_order Function
julia
_column_order(ks) -> Vector{Symbol}

Sensible column ordering for the linelist DataFrame.

EpiBranch._with_start_time Function
julia
_with_start_time(
    intervention::AbstractIntervention,
    t::Float64
) -> Any

Reconstruct an intervention with start_time set, if the type supports it.

EpiBranch._enforce_start_time! Function
julia
_enforce_start_time!(intervention, individual)
julia
_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
julia
_chain_length_ll_negbin(
    data,
    offspring::Distributions.NegativeBinomial
) -> Any

Analytical chain length likelihood for NegBin offspring.

EpiBranch._borel_logpdf Function
julia
_borel_logpdf(μ, n::Integer) -> Any

Log-PDF of the Borel distribution. Accepts any numeric type for μ (AD-compatible).

EpiBranch._gammaborel_logpdf Function
julia
_gammaborel_logpdf(k, R, n::Integer) -> Any

Log-PDF of the GammaBorel distribution. Accepts any numeric type for k, R (AD-compatible).

EpiBranch._single_type_offspring Function
julia
_single_type_offspring(model::TransmissionModel) -> Any

Extract 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
julia
_empirical_ll(
    observed,
    simulated;
    min_val,
    censored,
    cap
) -> Any

Empirical 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
julia
_golden_section_min(
    f,
    lo::Float64,
    hi::Float64;
    tol,
    maxiter
) -> Float64

Golden section search for minimum of a 1D function on [lo, hi].