Skip to content

Internals ​

Names the package does not export. These are implementation details of the public API and may change without notice. Nothing here is part of the interface the report pages build on.

Index ​

Reference ​

BVDOutbreakSize.M_PRIOR_DOUBLING_DAYS Constant
julia
M_PRIOR_DOUBLING_DAYS

Median doubling time (days) for the size and growth priors, from the BEAST X analysis (mbalaplacide2026, exponential growth model, 11.7 d, 95% HPD 6.8-17.5). Sets the median of the growth-rate prior in exponential_growth_model, r ~ LogNormal(log(log2 / M_PRIOR_DOUBLING_DAYS), 0.30).

source
BVDOutbreakSize._bounded_density! Method
julia
_bounded_density!(ax, x; lower, kwargs...)

Kernel density for a quantity that cannot fall below lower, with the axis clipped to the side of the bound the quantity can reach. A Gaussian KDE puts mass past the smallest draw, so a bounded quantity otherwise shows a tail on the impossible side.

The axis is clipped rather than the estimator narrowed with boundary. That tabulation drops draws landing on the first grid point while still normalising by the full sample size, so it discards the mass piled against the bound.

source
BVDOutbreakSize._composition_totals Method
julia
_composition_totals(modelled_confirmed) -> Any

Per-vintage totals a predictive province composition allocates: the modelled column sums, rounded to whole cases.

source
BVDOutbreakSize._dedup_onset_blocks Method
julia
_dedup_onset_blocks(
    blocks
) -> Vector{@NamedTuple{sitrep::String, report_date::Dates.Date, onsets::Dict{Dates.Date, Int64}}}
julia
_dedup_onset_blocks(blocks)

Collapse byte-identical reprinted onset-curve blocks. Some SitRep vintages reprint the same figure as an earlier one (the digitisation reads an identical onset-date -> total map), which would otherwise fabricate increments of exactly zero across the reprint and bias the fitted delay towards implausibly fast reporting.

Blocks are canonicalised as their sorted (onset_date, total) pairs, and blocks sharing a canonical form collapse to the one with the earliest report date. This is exact-value equality over the digitised content rather than a hardcoded vintage-id list, so a future reprint is caught automatically. Returns the surviving blocks sorted by ascending report_date.

source
BVDOutbreakSize._patch_analysed_increments Function
julia
_patch_analysed_increments(
    carried::AbstractMatrix,
    p_drc::Real,
    asc::AbstractVector,
    bg_carried::AbstractVector,
    w::AbstractVector,
    lab_days::AbstractVector{<:Integer}
) -> Any
_patch_analysed_increments(
    carried::AbstractMatrix,
    p_drc::Real,
    asc::AbstractVector,
    bg_carried::AbstractVector,
    w::AbstractVector,
    lab_days::AbstractVector{<:Integer},
    lab_bins::AbstractVector{<:Integer}
) -> Any

Modelled per-patch analysed-specimen volume summed over the laboratory bins (lab_bins[i] is the bin of printed day lab_days[i]), up to the national factors the composition normalises away. carried is each patch's onsets through the onset-to-confirmation kernel (report ⊕ receipt, _patch_carried); the national BVD volume p_drc times the patch sum is split by ascertainment-weighted incidence (asc, the case composition's relative ascertainment), so the BVD-to-background proportion stays the national one, and joined by the patch's share w[p] of the non-BVD background carried to receipt (bg_carried). Summed over the patches this is the national suspect pipeline confirmed_cases_model scales into the analysed volume, so the shares are exact. lab_days are grid day indices. Returns an (n_patches × n_days) matrix.

source
BVDOutbreakSize._patch_bvd_admissions Method
julia
_patch_bvd_admissions(
    reports_matrix::AbstractMatrix,
    asc::AbstractVector,
    p_iso_bvd::Real,
    p_drc::Real,
    adm_pmf::AbstractVector
) -> Matrix

Per-patch latent BVD admissions for the province splits: each patch's reports through the admission delay adm_pmf at p_iso_bvd · p_drc, then the national BVD admissions (their sum) re-split by the relative ascertainment asc, so the BVD-to-background proportion stays national. Returns an (n_patches × n) matrix whose columns sum to the national BVD admissions.

source
BVDOutbreakSize._patch_carried Method
julia
_patch_carried(
    onsets_matrix::AbstractMatrix,
    kernel::AbstractVector
) -> Matrix

Each patch's onsets carried through a delay kernel, an (n_patches × n) matrix. The confirmed composition and the laboratory composition both carry the onsets through the onset-to-confirmation kernel, so bvd_joint carries them once and hands the matrix to both.

source
BVDOutbreakSize._patch_confirmed_increments Method
julia
_patch_confirmed_increments(
    onsets_matrix::AbstractMatrix,
    kernel::AbstractVector,
    s_test::Real,
    province_days::AbstractVector{<:Integer}
) -> Matrix

Modelled per-province confirmed increments, binned to the vintages of the per-province spatial tables. Each patch's onsets are pushed through the same onset-to-confirmation kernel and test sensitivity as the national confirmed stream (kernel is the onset-to-report pmf convolved with the report-to-receipt pmf; the laboratory pipeline is national, only the incidence feeding it is provincial) and then binned onto the shared vintage days.

province_days are the (shared) grid-day indices of the spatial-table vintages. Returns an (n_patches × n_vintages) matrix.

Ascertainment and incidence are confounded by province

This matrix reaches province_composition_model only through normalised shares, so every factor common to all provinces cancels (s_test here, and ascertainment, background and positivity in the national confirmed stream). That keeps the composition from re-scoring the national total.

It does not fix the case-finding probability across provinces. The composition weights each patch by asc_p * lambda_p, its relative ascertainment times its modelled incidence, and the data identify only that product. The per-province laboratory series shows very differently-selected testing pools (Ituri 31.8% positivity against Nord-Kivu's 5.5%), so asc_p is sampled and partially pooled toward equality rather than fixed, which widens the per-patch Rt contrast and C_T split to their honest width.

Read log_rt_contrast and province_ascertainment together. Neither is interpretable alone. The national headline does not depend on the split.

source
BVDOutbreakSize._patch_death_increments Method
julia
_patch_death_increments(
    onsets_matrix::AbstractMatrix,
    kernel::AbstractVector,
    province_days::AbstractVector{<:Integer}
) -> Matrix

Modelled per-province confirmed-death increments, binned to the vintages of the Tableau 1 spatial tables. Each patch's onsets are pushed through the onset-to-death delay and then the report-to-receipt delay, so kernel is the convolution of the two, giving the deaths confirmed by each vintage.

This is the term that makes the provincial split identifiable. The case-fatality ratio and the death-confirmation probability are properties of the virus and of a national laboratory pipeline, not of a province, so they cancel out of the normalised shares. What remains weights each patch by its delay-convolved incidence alone, free of case ascertainment.

The delay convolution also separates ascertainment from epidemic phase. A fast-growing province has proportionally fewer deaths to date than a flat one at the same true CFR, because its recent cases have not died yet. Predicting each province's deaths to date from its own incidence curve accounts for that censoring, so only the residual is ascertainment.

source
BVDOutbreakSize._patch_demand Method
julia
_patch_demand(
    A_bvd::AbstractMatrix,
    A_bg::AbstractVector,
    w::AbstractVector,
    S_clin::AbstractVector,
    ruleout_pmf::AbstractVector,
    κ::Real,
    demand::AbstractVector
) -> Matrix

Per-patch latent bed demand for the province splits: the national demand demand shared out by each patch's approximate stock. The stock is the patch's BVD admissions A_bvd (_patch_bvd_admissions) through the clinical-stay survival S_clin, plus its share w[p] of the national non-BVD admissions A_bg through the rule-out stay ruleout_pmf and the abscond rate κ. The national stock in treatment_flow_model carries the confirmation dynamics; the patches share them, so they cancel in the shares approximately rather than exactly, and the split needs the stays alone, one convolution per patch rather than a copy of the flow machinery. Returns an (n_patches × n) matrix whose columns sum to demand; an equal split of the reports gives equal halves.

source
BVDOutbreakSize._patch_headlines Method
julia
_patch_headlines(
    infections_matrix::AbstractMatrix,
    g,
    n::Integer
) -> NamedTuple{(:infections_total, :cumulative_total, :C_T_patch, :C_T, :R_T, :r, :doubling_time, :seeding_age), <:NTuple{8, Any}}

National totals and headline quantities of patch_infection_model, read off the per-patch infections.

infections_total and cumulative_total sum the patches, C_T_patch is each patch's cumulative at the cut-off and C_T the national one. R_T inverts the renewal equation on the summed infections on the cut-off day (implied_national_Rt_at). With I_{p,t} = R_{p,t} · force_{p,t} that gives Σ_p R_{p,t} force_{p,t} / Σ_p force_{p,t}, the incidence-weighted mean of the patch Rts and the Rt that reproduces the national trajectory. Read off the depleted infections, it is net of depletion. r, doubling_time and seeding_age follow from it as in infection_model.

source
BVDOutbreakSize._patch_reports Method
julia
_patch_reports(
    onsets_matrix::AbstractMatrix,
    report_pmf::AbstractVector
) -> Matrix

Per-patch BVD reports, each patch's onsets through the shared onset-to-report delay, the rows summing to the national bvd_reports_daily of reported_cases_model. Returns an (n_patches × n) matrix.

source
BVDOutbreakSize._read_onset_curve_blocks Method
julia
_read_onset_curve_blocks(
    path::AbstractString
) -> Vector{@NamedTuple{sitrep::String, report_date::Dates.Date, onsets::Dict{Dates.Date, Int64}}}
julia
_read_onset_curve_blocks(path)

Parse the digitised onset-curve CSV at path into one block per SitRep vintage, in file order. Each row is sitrep,report_date,onset_date, confirmed_alive,confirmed_dead,confirmed_total. Only confirmed_total is used, since the alive/dead split is not modelled here. A manual line-split parser rather than CSV.jl, as the file has a fixed six-column schema and no quoting or embedded commas. Returns a Vector of (; sitrep::String, report_date::Date, onsets::Dict{Date,Int}), one entry per distinct sitrep id in first-seen order.

source
BVDOutbreakSize.abscond_thinned Method
julia
abscond_thinned(pmf::AbstractVector, κ::Real) -> Any
julia
abscond_thinned(pmf::AbstractVector, κ::Real) -> Any

Length-of-stay PMF thinned by survival against absconding over cohort age: pmf[d+1] * (1 - κ)^d. It sums to the fraction of a cohort that leaves clinically rather than by absconding, so it is a sub-probability schedule rather than a PMF.

Thinning makes absconding a competing risk against the clinical exits, which accumulate_occupancy needs: unthinned, the schedules account for the whole admitted mass and the abscond flow removes it a second time. κ = 0 returns the PMF unchanged.

The hazard applies on every day of a stay, which is right for the rule-out schedule, since a background admission is never confirmed and so is at risk throughout. True-case schedules take abscond_thinned_flow instead.

source
BVDOutbreakSize.abscond_thinned_flow Method
julia
abscond_thinned_flow(
    adm::AbstractVector,
    pmf::AbstractVector,
    κ::Real,
    conf_hazard::AbstractVector
) -> Any
julia
abscond_thinned_flow(
    adm::AbstractVector,
    pmf::AbstractVector,
    κ::Real,
    conf_hazard::AbstractVector
) -> Any

Clinical discharge flow from adm on the length-of-stay schedule pmf, with absconding competing only while a cohort is still unconfirmed.

accumulate_occupancy charges its abscond flow against the suspect sub-stock, so a confirmed patient cannot abscond. Each admission day is walked forward carrying two survivals: U, the probability still unconfirmed, which decays by 1 - conf_hazard each day, and the abscond survival, which decays by 1 - κ U and so stops decaying once a cohort is confirmed. A cohort admitted on day t contributes pmf[d+1] * S(t, d) to day t + d. Absconds are charged on the stock carried in from the previous day, so a cohort's admission day carries no abscond hazard.

A conf_hazard of zero recovers abscond_thinned convolved with adm, and κ = 0 recovers the plain convolve_delay.

source
BVDOutbreakSize.abscond_thinned_flows Method
julia
abscond_thinned_flows(
    adm1::AbstractVector,
    pmf1::AbstractVector,
    adm2::AbstractVector,
    pmf2::AbstractVector,
    κ::Real,
    conf_hazard::AbstractVector
) -> Tuple{Any, Any}
julia
abscond_thinned_flows(adm1, pmf1, adm2, pmf2, κ, conf_hazard)

The two abscond_thinned_flow flows that share an abscond rate and a confirmation hazard, walked in one pass. The cohort survival S(t, d) depends on neither the admissions nor the stay schedule, so the two flows run off one recurrence. Returns (out1, out2).

source
BVDOutbreakSize.accumulate_occupancy Method
julia
accumulate_occupancy(
    A_bvd::AbstractVector,
    A_bg::AbstractVector,
    deaths::AbstractVector,
    recover::AbstractVector,
    ruleout::AbstractVector,
    κ::Real,
    conf_hazard::AbstractVector
) -> NamedTuple{(:demand, :O_bvd, :O_conf, :O_susp, :abscond), <:NTuple{5, Any}}
julia
accumulate_occupancy(A_bvd, A_bg, deaths, recover, ruleout, κ, conf_hazard)

Treatment-centre occupancy built as a forward day-by-day running balance of admission, discharge and abscond events, carved into a confirmed and a suspect sub-stock by a confirmation overlay.

The clinical layer is label-independent and carried as two sub-stocks that sum to the total demand by construction. The occupied BVD true-case stock O_bvd accumulates A_bvd minus the BVD discharges (deaths + recover) and its share of the abscond outflow. The occupied non-case stock O_bg accumulates A_bg minus the rule-out exits (ruleout) and its share of the abscond outflow:

and the total demand is their sum D(t) = O_bvd(t) + O_bg(t) exactly. The abscond outflow κ · O_susp(t-1) drains the suspect (unconfirmed) pool only — confirmed patients do not abscond — and is split proportionally between its unconfirmed-BVD portion O_bvd(t-1) − O_conf(t-1) and the non-case portion O_bg(t-1), the two parts of the suspect pool O_susp(t-1):

The confirmation overlay relabels occupied true cases at the daily hazard conf_hazard(t) = τ_test · p_pos(t) borrowed from the lab pipeline, so the confirmed sub-stock is the confirmed subset of O_bvd, never an extra compartment, draining at the confirmed share of the BVD discharges:

so O_conf ≤ O_bvd ≤ D. The suspect sub-stock is the remainder O_susp(t) = D(t) − O_conf(t) = (O_bvd(t) − O_conf(t)) + O_bg(t) ≥ 0, the not-yet-confirmed BVD occupancy plus the non-case rule-out occupancy, so O_conf + O_susp = D closes without a clamp. Each sub-stock is floored at zero, O_conf is clamped into [0, O_bvd], and the abscond denominator O_susp(t-1) is floored with eps so a zero suspect pool gives a zero split. Returns (; demand, O_bvd, O_conf, O_susp, abscond), each a length-n vector.

source
BVDOutbreakSize.adjusted_rt Method
julia
adjusted_rt(
    Rt::AbstractVector,
    fraction::AbstractVector,
    days
) -> Any
julia
adjusted_rt(Rt, fraction, days)

The reproduction number net of depletion on each of days, R_t times the susceptible fraction at the end of the day before (pool_fraction). This is the number of infections each infection causes given the pool left, where Rt is the number in a fully susceptible population. Day one takes the full pool.

source
BVDOutbreakSize.bartlett_factor Method
julia
bartlett_factor(d::AbstractVector, o::AbstractVector) -> Any

Lower-triangular k × k Bartlett factor with diagonal d and strictly lower entries o, filled row by row.

With d[j] ~ Chi(ν - j + 1) and o ~ N(0, I) the product A Aᵀ is a Wishart(ν, I_k) draw (Bartlett decomposition). That prior is invariant under any rotation of the k directions, so with sum_to_zero_factor(Q, c, A) the implied prior on the sum-to-zero vector is the same whatever order its entries come in. A is the unique Cholesky factor of A Aᵀ, so its k (k + 1) / 2 entries are exactly the free parameters of the covariance.

source
BVDOutbreakSize.bin_increments Method
julia
bin_increments(
    daily::AbstractVector,
    days::AbstractVector{<:Integer}
) -> Any

Modelled between-vintage increments of a daily series daily, summed directly into the bins delimited by the vintage day indices days (1-based into the grid, ascending). The first increment is the cumulative count up to the first vintage day (sum(daily[1:days[1]])). Each later increment is the inter-vintage sum sum(daily[days[i-1]+1:days[i]]), so only the first bin needs the cumulative. Day indices are clamped to the grid. Pure and AD-transparent. The output element type follows daily.

source
BVDOutbreakSize.capped_stock_forecast Method
julia
capped_stock_forecast(
    occupied,
    beds,
    admit,
    uncapped,
    flows
) -> NamedTuple{(:occupancy, :admissions, :free, :scale), <:Tuple{Matrix, Matrix, Matrix, Any}}
julia
capped_stock_forecast(occupied, beds, admit, uncapped, flows)

Mean path of the occupied beds by patch over the forecast days, each patch a stock capped at its beds. occupied is each patch's cut-off occupancy. beds and admit are its beds and modelled admissions, one row per patch and one column per day. uncapped is the national uncapped occupancy mean on the day before each day, and flows the national modelled exits, one row per day and one column per flow. The exits are scaled by the occupied beds over uncapped, at most 1 (1 when uncapped is not positive), and shared by occupancy. Each patch admits up to its free beds, its beds less its previous occupancy plus its exits. Returns the occupancy, admissions and free beds by patch and the exit scale by day.

source
BVDOutbreakSize.cdf_nmax Method
julia
cdf_nmax(dist; q, cap, minlag) -> Any
julia
cdf_nmax(dist; q = 0.98, cap = 120, minlag = 5)

Maximum lag for discretising a delay dist: the smallest integer covering q of its CDF (the qth quantile rounded up), clamped to [minlag, cap]. Sizing the truncation by the distribution keeps a consistent tail mass across every delay. It is evaluated once outside the Turing model when each delay submodel is constructed, so the PMF length is fixed and AD-safe.

source
BVDOutbreakSize.censored_occupancy_model Method
julia
censored_occupancy_model(
    means::AbstractVector,
    ceilings::AbstractVector,
    obs::Union{Missing, AbstractVector{<:Integer}},
    k::Real
) -> Any

Right-censored occupancy likelihood. Each day's observed bed count obs[i] is a NegBinomial around the latent bed demand means[i], right-censored at the effective capacity ceilings[i]. The censored tail probability still depends on the demand above the ceiling, so demand stays identified when beds are full rather than the occupancy going flat in demand. A missing obs samples (the predictive path) under the <prefix>.obs key. Both go through one censored NegBinomialVector, so a supplied vector is scored as one summed term. Shares the surveillance dispersion k.

source
BVDOutbreakSize.censoring_cap Method
julia
censoring_cap(iso_days, iso_obs, capacity_history) -> Any
julia
censoring_cap(iso_days, iso_obs, capacity_history)

Per-report-day right-censoring bound for the isolation-occupancy likelihood, built from data only (no sampled parameter), so the censored NegativeBinomial never sits against a moving wall. Each isolation day takes the nearest recorded implied-capacity value (capacity_history), floored at that day's observed occupancy so the bound is never below the count (a count above the cap has zero probability under the censored NB). With the cap fixed the censored likelihood is smooth in the sampled demand. A capacity_history with no counts (empty, or days-only as the predictive generator supplies) returns a large finite cap far above any bed count, so the censoring is a no-op (a literal Inf cannot be used because safe_rate maps non-finite values to eps). A missing iso_obs skips the occupancy floor.

The latent bed demand is left uncensored, so demand above capacity is carried by the renewal and length-of-stay model rather than by the bound. Occupancy can only say demand was at least the beds filled, so the bed shortfall is a model-informed quantity. See treatment_flow_model.

source
BVDOutbreakSize.clinical_stay_survival Method
julia
clinical_stay_survival(
    death_pmf::AbstractVector,
    recover_pmf::AbstractVector,
    cfr::Real
) -> Any
julia
clinical_stay_survival(death_pmf, recover_pmf, cfr)

Clinical-stay survival of the BVD discharge mixture, indexed from cohort age 0, matching the discharge balance accumulate_occupancy runs. An admitted true case leaves by death (weight cfr, the in-care fatality CFR_iso) on the admission→death stay or by recovery (weight 1 − cfr) on the admission→recovery stay, so the cumulative discharge fraction by age d is G(d) = Σ_{j=0}^{d} (cfr · death_pmf[j+1] + (1 − cfr) · recover_pmf[j+1]) and the survival is its complement

so S_clin[d+1] is the probability the case has not yet been discharged by the end of day d. This is the per-cohort weight the running-balance BVD stock carries: with no absconds O_bvd(t) = Σ_{u ≤ t} A_bvd(u) · S_clin(t − u). The discharge-complement P(stay > d), rather than the inclusive P(stay ≥ d), keeps the two-clock confirmed sub-stock ≤ O_bvd by construction. The two PMFs need not share a length. Each contributes zero beyond its support, so the result takes the longer length.

source
BVDOutbreakSize.confirmed_break_steps Method
julia
confirmed_break_steps(
    obs;
    deaths
) -> Vector{Tuple{Int64, Float64}}

The (grid day, correction) pairs a confirmed stream's listed harmonisation-break days carry, one per listed day the stream's own history has a matching vintage for.

On a listed break day ([confirmed_break_dates]) the reported cumulative jumps by more than that day's notifications because INSP reattached previously unlinked records. The excess is net - gross, the vintage's own step less the printed 24h count. Subtracting it turns a raw cumulative difference into the count actually notified across the window.

deaths selects the confirmed-death stream rather than confirmed cases. A break day with no matching vintage is left out. A break day on the stream's first vintage takes the whole cumulative as its step. The correction is floored at zero so a smaller vintage cannot inflate the truth.

source
BVDOutbreakSize.confirmed_positives_model Function
julia
confirmed_positives_model(
    positives::Union{Missing, AbstractVector{<:Integer}},
    analysed::AbstractVector{<:Integer},
    p_pos::AbstractVector
) -> Any
confirmed_positives_model(
    positives::Union{Missing, AbstractVector{<:Integer}},
    analysed::AbstractVector{<:Integer},
    p_pos::AbstractVector,
    ρ::Real
) -> Any

Per-window confirmed-positives likelihood. Given the per-window analysed counts analysed, positivities p_pos and the intra-window overdispersion ρ, scores the observed positives with one BetaBinomial(analysed[i], p_pos[i], ρ) per window (see safe_betabinomial). ρ → 0 recovers the plain Binomial(analysed[i], p_pos[i]).

positives is a model argument on the left of ~, so a supplied vector is observed data DynamicPPL conditions on and a missing argument is sampled (the predictive-generator path). Under a prefixed attachment the predict keys are <prefix>.positives[i].

source
BVDOutbreakSize.cumulative_occupancy_offset Method
julia
cumulative_occupancy_offset(iso_days, break_days, b) -> Any
julia
cumulative_occupancy_offset(iso_days, break_days, b)

Per-isolation-day cumulative occupancy reclassification offset Δ from the fitted break steps b on the manually specified break_days (grid day-indices). Δ(t) sums b_j over the break days at or before iso_days[t], zero before the first break day. Added to the modelled occupancy mean in the censored-occupancy likelihood so the modelled occupancy tracks a between-report measurement-basis discontinuity in the observed series (a Tableau 6-sum to page-1 headline transition, say). Each b_j is sampled in treatment_flow_model, so the fit partitions each step into reporting artefact and real demand change. Returns a length-length(iso_days) vector of eltype(b), zeros when there are no break days.

source
BVDOutbreakSize.cutoff_occupancy Method
julia
cutoff_occupancy(
    occupied,
    demand,
    capacity,
    floors
) -> NamedTuple{(:beds, :occupancy, :shortfall), <:Tuple{Any, Any, Any}}
julia
cutoff_occupancy(occupied, demand, capacity, floors)

Cut-off beds, occupancy and shortfall by patch. Each patch's beds are its modelled capacity floored at its recorded beds floors. The national occupied, floored at zero, is shared out on the patches' demand, and each patch's share is capped at its beds, the rest its shortfall. The occupancy never exceeds the beds in any patch, and patients above the beds are not moved to another patch.

source
BVDOutbreakSize.dated_event_bins Method
julia
dated_event_bins(
    event_days::AbstractVector{<:Integer},
    n::Integer
) -> Tuple{Any, Any}

Group a sorted event-day list event_days (grid day-indices, ascending, one entry per dated event, possibly with repeats) into its unique days and the per-day occupancy count, clamped to [1, n]. Returns (days, counts) with days the unique detection/death days and counts[i] the number of events on days[i]. Used by exports_model and the export-deaths likelihood to place one Poisson term per detection/death day with an observed count equal to that day's occupancy, so simultaneous imports on one day share a single edge. event_days must be non-empty.

source
BVDOutbreakSize.dated_poisson_model Method
julia
dated_poisson_model(
    means::AbstractVector,
    counts::Union{Missing, AbstractVector{<:Integer}}
) -> Any

Per-day Poisson likelihood for a dated event series. Scores the observed per-day counts against the modelled per-day means with one Poisson term each, NaN/Inf-safe via safe_rate. A missing counts samples instead (the predictive-generator path), saturating at typemax(Int) (SafePoisson). The indexed counts[i] keeps the predict keys (<prefix>.counts[i]) replicable. Used by exports_model and the export-deaths likelihood for the dated Uganda export series.

source
BVDOutbreakSize.dirichlet_multinomial_logpdf Method
julia
dirichlet_multinomial_logpdf(
    y::AbstractVector{<:Integer},
    α::AbstractVector
) -> Any
julia
dirichlet_multinomial_logpdf(
    y::AbstractVector{<:Integer},
    α::AbstractVector
) -> Any

Log probability mass of counts y under a Dirichlet-multinomial with concentration vector α, Σ y trials:

Plain arithmetic and loggamma, so it differentiates under Mooncake.

source
BVDOutbreakSize.gamma_median_wh Method
julia
gamma_median_wh(mean::Real, sd::Real) -> Any

Wilson and Hilferty (1931) approximation to the continuous median of a Gamma as a function of its mean and sd: median ≈ mean·(1 − sd²/(9·mean²))³. Smooth in the mean and SD with no quantile inversion, so it enters a gradient-based likelihood cleanly, and accurate to a few percent for the shapes here. Used to match the confirmed onset-to-sample convolution's continuous median to the cohort's reported median.

source
BVDOutbreakSize.gate_before Method
julia
gate_before(v::AbstractVector, start::Integer) -> Any

Zero a daily series v before grid day start (1-based), modelling a process that did not exist before start. Gates the laboratory analysed-specimen volume before testing began, so it does not accrue over the pre-surveillance cryptic phase. The suspected-case and suspected-death streams are not gated, since those counts did accumulate over that phase. start ≤ 1 returns v unchanged. Pure and AD-transparent. The element type follows v. Callers concretise any Vector{Any} (predict mode) before gating, so zero(eltype(v)) is well defined.

source
BVDOutbreakSize.gravity_pull Method
julia
gravity_pull(pops::AbstractVector; distances, decay)
julia
gravity_pull(pops; distances, decay)

Unnormalised gravity pull, pull[p, q] the relative attraction of destination p to an origin q,

zero on the diagonal, and the destination population alone where the distance is zero. This is the one gravity form both spatial levels use. province_importation_kernel normalises its columns over the provinces; the health-zone model normalises the same pull within and between patches (zone_importation_blocks).

source
BVDOutbreakSize.grid_date Method
julia
grid_date(obs, day::Integer) -> Dates.Date

Calendar date of a grid day-index, where day obs.n is the cut-off and day 1 is the seeding day. Every dated history stores day-indices on this grid, so this is how a vintage is read back as a date.

source
BVDOutbreakSize.incare_census Method
julia
incare_census(
    demand::AbstractVector,
    O_bvd::AbstractVector,
    O_conf_raw::AbstractVector,
    κ::Real,
    offset::AbstractVector
) -> NamedTuple{(:confirmed, :suspect, :abscond, :total), <:NTuple{4, Any}}
julia
incare_census(demand, O_bvd, O_conf_raw, κ, offset)

Confirmed and suspect in-care census of the treatment-flow model. The two-clock confirmed stock O_conf_raw (two_clock_confirmed) is clamped into [0, O_bvd], and the suspect stock is the demand remainder O_susp(t) = max(D(t) − O_conf(t), 0). Absconds drain the previous day's suspect stock, κ · O_susp(t − 1), with none on day 1. The census total adds the reclassification offset to the demand, and the suspect census is max(total − O_conf, 0).

Returns (; confirmed, suspect, abscond, total), each a length-n vector.

source
BVDOutbreakSize.late_confirmed_model Function
julia
late_confirmed_model(
    increments::Union{Missing, AbstractVector},
    modelled::AbstractVector,
    analysed::AbstractVector{<:Integer},
    p_pos::AbstractVector,
    k::Real
) -> Any
late_confirmed_model(
    increments::Union{Missing, AbstractVector},
    modelled::AbstractVector,
    analysed::AbstractVector{<:Integer},
    p_pos::AbstractVector,
    k::Real,
    ρ::Real
) -> Any

Late confirmed-vintage likelihood, one per-window confirmed increment over the days after the last cumulative laboratory date. A day with a published 24h analysed denominator (analysed[i] > 0) is an overdispersed BetaBinomial(analysed[i], p_pos[i], ρ) of that denominator (see safe_betabinomial); an unanchored day is a NegativeBinomial of the modelled laboratory volume modelled[i] sharing the surveillance dispersion k. Keeping both in one submodel gives a single per-window predict-key vector (<prefix>.increments[i]) over all late days in order, so the posterior-predictive trajectory reconstructs without interleaving.

increments is a model argument on the left of ~, so a supplied vector is observed data and a missing argument is sampled (the predictive-generator path). A supplied integer vector is scored as one SplitCountVector, a summed BetaBinomial term over the anchored days plus a summed NegativeBinomial term over the rest. A Vector{Union{Missing, Int}} with some entries missing scores only the present ones, one ~ per day, used to observe the anchored days while leaving the unanchored days latent under the no-extrapolation probe.

source
BVDOutbreakSize.nbinomial_logtail Method
julia
nbinomial_logtail(
    d::Distributions.NegativeBinomial,
    x::Real
) -> Any

Log-probability that a draw from the NegativeBinomial d is at least x, the censored tail of a count at its ceiling. Equal to logccdf(d, x - 1), computed as 1 − I_p(r, x) from SpecialFunctions.beta_inc, whose plain-Julia body Mooncake differentiates. logccdf calls Rmath's pnbinom through a ccall, which Mooncake cannot. A tail too small for a normal float is summed term by term in log space from x up, until a term adds less than exp(-40) of the total. A fractional x takes the tail from the next count up.

source
BVDOutbreakSize.nearest_recorded Method
julia
nearest_recorded(
    days::AbstractVector{<:Integer},
    counts::AbstractVector,
    day::Integer
) -> Any
julia
nearest_recorded(days, counts, day)

Entry of counts whose days value is nearest day, ties going to the earliest. A data lookup with no sampled input. Written as a scan rather than an argmin over a broadcast so that it allocates nothing on the gradient path.

source
BVDOutbreakSize.onset_forecast_model Method
julia
onset_forecast_model(onsets, state, n, vintages) -> Any

Future vintages of the symptom-onset reporting triangle. Each future vintage v in vintages prints a total the fitted reporting hazard puts at onset_report_expected_total as of v, over the onsets the model runs past the cut-off n. Its increment over the total at the cut-off is drawn with the Student-t the scored cells take, at the scale onset_report_scale gives a correction read off two bars with the fitted read SD τ. The increment is drawn on the whole figure, not per onset date, since the figure's total is the quantity the forecast is scored on. The split of each increment into reports of onsets up to the cut-off (backfill) and after it (future) is tracked, with the total the triangle should already have printed by the cut-off.

source
BVDOutbreakSize.onset_increments_model Method
julia
onset_increments_model(
    means::AbstractVector,
    sds::AbstractVector,
    increments::Union{Missing, AbstractVector{<:Real}},
    ν::Real
) -> Any
julia
onset_increments_model(means, sds, increments, ν)

Heavy-tailed likelihood for the reporting-triangle increment cells: cell i is Student-t about the modelled increment means[i] with scale sds[i] and fixed degrees of freedom ν (see safe_studentt).

increments is a positional model argument rather than a field read out of a container inside the body, and that is load bearing. DynamicPPL decides observe-versus-assume from whether the tilde's symbol is in the enclosing model's argument names (DynamicPPL.inargnames), so a local variable on the left of ~ is treated as latent, silently dropping the likelihood. Same argument shape as vintage_increments_model. A missing argument samples instead (the predictive-generator path) under the <prefix>.increments key. Both go through one StudentTVector, so a supplied vector is scored as one summed term.

source
BVDOutbreakSize.onset_to_sample_logweight Method
julia
onset_to_sample_logweight(
    report_mean::Real,
    report_sd::Real,
    receipt_mean::Real,
    receipt_sd::Real,
    cfg
) -> Any

Log-density grounding the confirmed onset-to-sample convolution on the cohort: soft Normal fits of the convolution's continuous mean (the sum of the report and receipt leg means) and continuous median (from gamma_median_wh of the summed leg variances) to the reported mean_obs/median_obs with SDs mean_se/median_se. The fixed Gaussian normalising constants are dropped.

source
BVDOutbreakSize.patch_infections_with_state Method
julia
patch_infections_with_state(
    Rt_matrix::AbstractMatrix,
    g::AbstractVector,
    seeds_matrix::AbstractMatrix,
    importation_kernel::AbstractMatrix,
    epsilon::Union{Real, AbstractMatrix},
    N::AbstractVector
) -> NamedTuple{(:infections, :importation, :susceptible, :rate), <:NTuple{4, Any}}

The patch_infections trajectories with the state they were built from: susceptible, the pool each patch has left at the end of each day (the pool after its seed on the seeded days), and rate, each day's depletion rate y_{p,t} / N_p. The derivative rule reads both.

source
BVDOutbreakSize.patch_members Method
julia
patch_members(province_names::AbstractVector) -> Any
julia
patch_members(province_names)

Source provinces each patch pools, in the order of province_names.

A name with no PROVINCE_MEMBERS entry is its own province, which keeps the province helpers usable with an arbitrary province list. province_increment_matrix resolves patches through this, so how a patch maps to the manifest blocks it covers is written once.

source
BVDOutbreakSize.pool_fraction Method
julia
pool_fraction(cumulative::AbstractVector, N::Real) -> Any
julia
pool_fraction(cumulative, N)

Share of the pool N left susceptible at the end of each day, 1 − C_t / N for the cumulative infections C_t, seed included, floored at zero. Exact for the depletion in renewal_infections, where each day's infections are what leaves the pool and a seed larger than N leaves none.

source
BVDOutbreakSize.precompile_workload_joint Method
julia
precompile_workload_joint() -> Any
julia
precompile_workload_joint() -> Any

The model the precompile workload compiles: the headline joint on the real observations, which is the call docs/fits/registry.jl samples.

Separate from the workload block so a test can assert it is the same method instance the fit builds. Gradient rules are keyed by type, not by data size, so the grid length does not affect what is cached.

source
BVDOutbreakSize.province_counts_model Method
julia
province_counts_model(
    means::AbstractMatrix,
    ceilings::AbstractMatrix,
    k::Real
) -> Any

Future counts by patch. Each patch's count on each day is the censored negative binomial around its mean in means (one row per patch, one column per day) at its ceiling in ceilings, drawn as province. obs is their sum over the patches, the national count.

source
BVDOutbreakSize.province_split_logpdf Method
julia
province_split_logpdf(
    days::AbstractVector{<:Integer},
    patches::AbstractVector{<:Integer},
    counts::AbstractVector{<:Integer},
    level::AbstractMatrix,
    ρ::Real
) -> Any
julia
province_split_logpdf(days, patches, counts, level, ρ)

Log-density of per-province counts as a split of the printed sum of the provinces present each day: the stick-breaking BetaBinomial of province_composition_model over whichever patches print a figure that day, scored through stick_breaking_loglik with each day as one group. days, patches and counts are one row per printed figure, sorted by day (see province_care_observations); level[p, d] is the modelled level of patch p on grid day d, up to a factor common to all patches; ρ is the overdispersion. A day with one patch printed carries no split and adds nothing. Added with @addlogprob! for the isolation-occupancy and bed splits, whose coverage varies by day, so the observation cannot be the full matrix the composition model takes.

source
BVDOutbreakSize.province_split_model Method
julia
province_split_model(
    rows,
    level::AbstractMatrix,
    ρ::Real
) -> Any

Province split of printed daily sums, as a submodel: the counts of rows (; days, patches, counts) scored by province_split_logpdf over the modelled level, or, with counts missing, drawn by the same stick-breaking at each day's printed sum rows.totals, one draw per position within a day, the last printed province taking the remainder. Returns (; counts), and records the counts as split_counts.

source
BVDOutbreakSize.relative_multiplier_dims Method
julia
relative_multiplier_dims(
    groups::AbstractVector{<:UnitRange}
) -> Any

Number of draws relative_multiplier takes over groups.

source
BVDOutbreakSize.renewal_infections_with_state Method
julia
renewal_infections_with_state(
    Rt::AbstractVector,
    g::AbstractVector,
    seed::AbstractVector,
    N::Real
) -> NamedTuple{(:infections, :force, :susceptible), <:Tuple{Any, Any, Any}}

The renewal trajectory with the state it was built from, as (; infections, force, susceptible): the per-day force Σ_{s ≥ 1} I_{t−s} g_s and the pool left at the end of each day, which is the pool after the seed on the seeded days. renewal_infections returns the first; the derivative rule needs the others, which it would otherwise have to rebuild from a copy of this loop.

Each day's force is one dot of the most recent infections with the generation interval reversed, a single BLAS call on float arrays.

source
BVDOutbreakSize.safe_betabinomial Method
julia
safe_betabinomial(
    n::Integer,
    p,
    ρ
) -> Distributions.BetaBinomial

NaN / Inf-safe overdispersed Binomial (BetaBinomial) constructor parameterised by the trial count n, the mean positive probability p and an intra-window overdispersion ρ ∈ (0, 1). With concentration s = (1 − ρ)/ρ, α = s·p and β = s·(1 − p), the BetaBinomial(n, α, β) has mean n·p and variance n·p·(1 − p)·(1 + (n − 1)·ρ), so ρ → 0 recovers the plain Binomial(n, p) and larger ρ inflates the variance above it. This carries the day-to-day laboratory batching and within-window positivity heterogeneity a pooled per-window p does not. ρ is floored away from 0 and p clamped into (0, 1) so the distribution stays defined under extreme NUTS proposals. Shared by the confirmed-positives windows.

source
BVDOutbreakSize.safe_nbinomial Method
julia
safe_nbinomial(k, μ) -> Distributions.NegativeBinomial

NaN / Inf-safe NegativeBinomial constructor parameterised by mean μ and dispersion k, with clamping on the success probability so extreme NUTS proposals during warmup do not trip the distribution domain check. Shared by the count-stream observation submodels.

source
BVDOutbreakSize.select_daily_releases Method
julia
select_daily_releases(entries) -> Any

Select at most one results release per data day from entries, a collection of (tag, created, cutoff) triples: the release tag, its creation timestamp (a UTC DateTime, as reported by gh release list) and the data cut-off it was built from (as_of_date in the release's observations.toml). Both tagged releases (results-vX.Y.Z) and main-build releases (results-<run number>, published by every push to main) are eligible. Any other tag is ignored.

Days are cut-off days, not creation days, because a release's forecast is a function of the data it saw. Two releases sharing a cut-off carry the same forecast however far apart they were published, so grouping on the creation timestamp would score that forecast twice.

Within a day a tagged release is preferred, then the newest build of the day. Releases sharing a timestamp are separated by version or run number, keeping the selection deterministic.

Returns the selected tags, newest cut-off first.

source
BVDOutbreakSize.stick_breaking_loglik Method
julia
stick_breaking_loglik(
    groups::AbstractVector{<:Integer},
    counts::AbstractVector{<:Integer},
    shares::AbstractVector,
    ρ::Real
) -> Any
julia
stick_breaking_loglik(groups, counts, shares, ρ)

Log-likelihood of counts split between the members of each group by stick-breaking. Rows are one per count, sorted by group, and a group is a run of equal groups entries, so groups may differ in size. shares[r] is row r's share of its group, the shares of a group summing to one. The count in row r is a safe_betabinomial draw from the group total less the counts before it in the group, with the overdispersion ρ and the conditional share share_r / tail_r clamped into [0, 1]. The running tail tail_r = 1 − Σ_{q < r} share_q is floored at 1e-10. The last row of a group is the remainder and adds nothing, so a one-row group adds nothing. The rows are scored as one BetaBinomialVector.

source
BVDOutbreakSize.sum_to_zero Method
julia
sum_to_zero(F::AbstractMatrix, z::AbstractVector) -> Any

Sum-to-zero vector F z for a loading matrix F from sum_to_zero_factor and n - 1 standard-normal draws z.

source
BVDOutbreakSize.sum_to_zero_basis Method
julia
sum_to_zero_basis(n::Integer) -> Any

Orthonormal basis of the sum-to-zero subspace of R^n, as an n × (n - 1) matrix Q with Qᵀ Q = I and Qᵀ 1 = 0.

Column j is the Helmert contrast of the first j entries against entry j + 1,

zero below. Any vector Q y sums to zero, and Q Qᵀ = I - J / n is the centring projector, so σ Q z with z ~ N(0, I_{n-1}) has exactly the distribution of a standard-normal n-vector scaled by σ and centred. It does so with n - 1 draws, one per direction a sum-to-zero vector can move in. With n = 1 the basis is empty (1 × 0).

source
BVDOutbreakSize.sum_to_zero_factor Function
julia
sum_to_zero_factor(Q::AbstractMatrix, s) -> Any
sum_to_zero_factor(Q::AbstractMatrix, s, L) -> Any

Loading matrix F = Q diag(s) L (n × (n - 1)) of a sum-to-zero vector δ = F z with z ~ N(0, I_{n-1}).

Q is sum_to_zero_basis, s the scales of the n - 1 basis directions (a vector, or one scalar for an exchangeable vector) and L a lower-triangular Cholesky factor of their (n - 1) × (n - 1) correlation, or nothing for none. The covariance of δ is then

a full covariance of the sum-to-zero vector with its n (n - 1) / 2 free parameters and no more. With a scalar s and no L, Σ = s² (I - J / n), the covariance of a centred vector of independent N(0, s²) draws.

source
BVDOutbreakSize.sum_to_zero_moments Method
julia
sum_to_zero_moments(
    F::AbstractMatrix
) -> NamedTuple{(:sd, :cor), <:Tuple{Any, Any}}

Per-entry standard deviations and correlation matrix of the sum-to-zero vector F z, from its covariance Σ = F Fᵀ. Returns (; sd, cor).

The correlations of a sum-to-zero vector are constrained: its entries cannot all be positively correlated, since Σ 1 = 0 makes each row of Σ sum to zero. With equal standard deviations each entry's correlations with the others average -1 / (n - 1), and they all equal it only when the vector is exchangeable. For n = 3 the three standard deviations determine the three correlations exactly, cor_{12} = (sd_3² - sd_1² - sd_2²) / (2 sd_1 sd_2), and for larger n they constrain them. With n = 1 the vector is identically zero and the correlation is reported as one.

source
BVDOutbreakSize.treatment_flow_defaults Method
julia
treatment_flow_defaults(

) -> @NamedTuple{admission::DynamicPPL.Model{typeof(isolation_admission_model), (), (:p_prior,), (), Tuple{}, Tuple{Distributions.Beta{Float64}}, DynamicPPL.DefaultContext, false}, severity::DynamicPPL.Model{typeof(isolation_severity_model), (), (:logodds_prior,), (), Tuple{}, Tuple{Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}}, DynamicPPL.DefaultContext, false}, dispersion::DynamicPPL.Model{typeof(surveillance_dispersion_model), (), (:inv_sqrt_k_prior,), (), Tuple{}, Tuple{Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}}, DynamicPPL.DefaultContext, false}, cfr_modifier_prior::Distributions.Normal{Float64}, abscond_prior::Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}, incare_confirm_log_prior::Distributions.Normal{Float64}, admission_delay::DynamicPPL.Model{typeof(censored_delay_model), (:nmax,), (:mean_prior, :sd_prior), (), Tuple{Int64}, Tuple{Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}, Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}}, DynamicPPL.DefaultContext, false}, death_los::DynamicPPL.Model{typeof(gamma_delay_model), (:nmax,), (:alpha_prior, :theta_prior), (), Tuple{Int64}, Tuple{Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}, Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}}, DynamicPPL.DefaultContext, false}, recovery_los::DynamicPPL.Model{typeof(censored_delay_model), (:nmax,), (:mean_prior, :sd_prior), (), Tuple{Int64}, Tuple{Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}, Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}}, DynamicPPL.DefaultContext, false}, ruleout_los::DynamicPPL.Model{typeof(censored_delay_model), (:nmax,), (:mean_prior, :sd_prior), (), Tuple{Int64}, Tuple{Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}, Distributions.Truncated{Distributions.Normal{Float64}, Distributions.Continuous, Float64, Float64, Nothing}}, DynamicPPL.DefaultContext, false}}

Default priors and delay submodels of treatment_flow_model, as one named tuple. A composer builds it once, when its model is constructed, and passes it as treatment_flow_model's defaults, so it is not rebuilt on every evaluation.

source
BVDOutbreakSize.treatment_forecast_model Method
julia
treatment_forecast_model(
    state,
    fd,
    capacity_history,
    k
) -> Any

Future isolation and treatment counts from a fitted treatment_flow_model state run past the cut-off, drawn by patch from a stock capped at its beds (capped_stock_forecast) and summed to national. The beds are the modelled capacity floored at the cut-off beds and never fall. With no recorded capacity the stock starts from the uncapped cut-off occupancy and nothing is censored.

source
BVDOutbreakSize.two_clock_confirmed Method
julia
two_clock_confirmed(
    A_bvd::AbstractVector,
    conf_hazard::AbstractVector,
    S_clin::AbstractVector
) -> Any
julia
two_clock_confirmed(A_bvd, conf_hazard, S_clin)

Cohort-tracked confirmed-in-care sub-stock. A true-case admission cohort admitted on day u (A_bvd[u]) contributes to the confirmed stock on day t only once it has been confirmed and while it has not yet clinically departed:

a product of two clocks running from admission. The confirmation clock is the per-cohort cumulative confirmation probability under the time-varying daily hazard conf_hazard(t) = τ_test · p_pos(t) borrowed from the lab pipeline, with exposure from the day after admission:

so a just-admitted cohort (u = t) has CDF_conf = 0. The clinical clock is the BVD stay survival S_clin(d) = P(stay > d) (clinical_stay_survival), so Σ_{u ≤ t} A_bvd(u) · S_clin(t − u) reconstructs the abscond-free BVD stock. The confirmed-and-present cohort is a subset of the present cohort, so O_conf ≤ O_bvd holds by construction, without the proportional split's mean-field approximation: a true case that dies before its test returns is never counted as confirmed. Returns the length-n confirmed-in-care sub-stock. incare_census forms the suspect sub-stock as the demand remainder D − O_conf.

source
BVDOutbreakSize.upto Method
julia
upto(x::AbstractVector, n::Integer) -> Any

The first n entries of x, or x itself when it has no more.

source
BVDOutbreakSize.vintage_increments_model Method
julia
vintage_increments_model(
    modelled::AbstractVector,
    increments::Union{Missing, AbstractVector{<:Integer}},
    k::Real
) -> Any

Per-vintage increment likelihood for one stream. Scores the modelled per-vintage increments modelled (see bin_increments) against the observed increments with one NegativeBinomial per vintage, sharing the dispersion k.

increments is a model argument on the left of ~, so a supplied vector is observed data DynamicPPL conditions on and a missing argument is sampled (the predictive-generator path) under the whole-vector predict key <prefix>.increments. Both go through one NegBinomialVector, so a supplied vector is scored as one summed term. An empty vector (zero vintages) adds no variable and nothing to the log density.

source
BVDOutbreakSize.vintage_obs Method
julia
vintage_obs(
    history,
    total::Union{Missing, Integer},
    n::Integer
) -> NamedTuple{(:days, :obs_increments), <:Tuple{Any, Any}}

Resolve a stream's per-vintage observation into the vintage day indices and the observed between-vintage increment vector to score, given the dated cumulative history (; days, counts), the cut-off total total and the grid length n (day n is the cut-off). A non-empty history already ends at the cut-off, so the increments are differenced from it alone and total is not scored again. When the history is empty but a total is supplied (the tests-analysed stream, which has no dated vintage history), the cut-off becomes a single vintage point at day n. A history with days but empty counts keeps the vintage day grid and leaves the increments missing, the generator path predict resamples. When both are empty or total is missing, the increments are missing over zero days. Returns (; days, obs_increments) with obs_increments either an Int vector or missing.

source
BVDOutbreakSize.zone_composition_logpdf Method
julia
zone_composition_logpdf(
    counts::AbstractMatrix{<:Integer},
    C::AbstractMatrix,
    cell_patch::AbstractVector{<:Integer},
    cell_vintage::AbstractVector{<:Integer},
    cell_total::AbstractVector{<:Integer},
    cell_const::AbstractVector{<:Real},
    patch_ranges::AbstractVector{<:UnitRange},
    κ::Real
) -> Any
julia
zone_composition_logpdf(
    counts::AbstractMatrix{<:Integer},
    C::AbstractMatrix,
    cell_patch::AbstractVector{<:Integer},
    cell_vintage::AbstractVector{<:Integer},
    cell_total::AbstractVector{<:Integer},
    cell_const::AbstractVector{<:Real},
    patch_ranges::AbstractVector{<:UnitRange},
    κ::Real
) -> Any

The composition log likelihood summed over the scored cells: for cell c, patch cell_patch[c] at vintage cell_vintage[c] with allocated total cell_total[c], the observed zone counts in counts (n_zones × n_vintages) follow a Dirichlet-multinomial with concentration κ π, where π is the modelled expected reports C of that patch's zones normalised within the patch. cell_const[c] is the parameter-free part of the mass, log N! − Σ log y!.

source
BVDOutbreakSize.zone_delay_operator Method
julia
zone_delay_operator(
    f::AbstractVector,
    nd::Integer
) -> Matrix{Float64}
julia
zone_delay_operator(
    f::AbstractVector,
    nd::Integer
) -> Matrix{Float64}

The delay convolution over the days t0 … n as an (n_days × n_days) lower-triangular Toeplitz matrix F with F[j, j − s] = f_s (lag 0 on the diagonal), so the convolution of every zone's infections is one matrix product F I. The days before t0 enter separately through zone_report_pre_rows.

source
BVDOutbreakSize.zone_fixed_terms Method
julia
zone_fixed_terms(
    I_bar::AbstractMatrix,
    g::AbstractVector,
    f::AbstractVector,
    t0::Integer
) -> NamedTuple{(:force_pre, :report_pre, :report_pre_cum, :infections_pre), <:NTuple{4, Any}}
julia
zone_fixed_terms(
    I_bar::AbstractMatrix,
    g::AbstractVector,
    f::AbstractVector,
    t0::Integer
) -> NamedTuple{(:force_pre, :report_pre, :report_pre_cum, :infections_pre), <:NTuple{4, Any}}

The parts of the zone renewal and its delays that involve only patch infections before the grid start t0, which are constant under the cut. Every zone holds its initial share over those days, so each term enters a zone's series multiplied by w_z(t0) only.

Returns, with I_bar (n_patches × n) and PMFs g (lag 1) and f (lag 0):

  • force_pre[p, t] = Σ_{s ≥ 1, t − s < t0} g_s Ī_p(t − s), the pre-t0 force of infection on every day;

  • report_pre[p, t] = Σ_{s ≥ 0, t − s < t0} f_s Ī_p(t − s), the pre-t0 contribution to the daily expected reports;

  • report_pre_cum[p, t] = Σ_{t' ≤ min(t, t0 − 1)} report_pre[p, t'], the expected reports accrued over the days before t0, so a vintage window opening before the grid start bins them by difference;

  • infections_pre[p] = Σ_{t < t0} Ī_p(t), the patch infections before the grid start.

source
BVDOutbreakSize.zone_forward Function
julia
zone_forward(
    zd,
    δ_knots::AbstractMatrix,
    w0::AbstractVector,
    ε::Union{Nothing, AbstractVector}
) -> NamedTuple{(:shares, :forces, :infections, :imports, :reports, :increments), <:NTuple{6, Any}}
zone_forward(
    zd,
    δ_knots::AbstractMatrix,
    w0::AbstractVector,
    ε::Union{Nothing, AbstractVector},
    def
) -> NamedTuple{(:shares, :forces, :infections, :imports, :reports, :increments), <:NTuple{6, Any}}
julia
zone_forward(
    zd,
    δ_knots::AbstractMatrix,
    w0::AbstractVector,
    ε::Union{Nothing, AbstractVector}
) -> NamedTuple{(:shares, :forces, :infections, :imports, :reports, :increments), <:NTuple{6, Any}}
zone_forward(
    zd,
    δ_knots::AbstractMatrix,
    w0::AbstractVector,
    ε::Union{Nothing, AbstractVector},
    def
) -> NamedTuple{(:shares, :forces, :infections, :imports, :reports, :increments), <:NTuple{6, Any}}

One forward pass of the zone model from its fixed data zd (the model_data of zone_fit_inputs) and a draw's deviation knots (n_zones × n_knots), initial shares w0 and per-zone mixing fractions ε (nothing when mixing is off): the daily deviations (the interpolation weights times the knots), the share renewal and the binned expected reports (the delay operator times the infections plus the pre-t0 rows). def is the patch trajectory the draw's shared quantity implies (zone_deformation); its default is the cut, the province model's posterior mean. Called by the model and, per draw, by the render. Returns (; shares, forces, infections, imports, reports, increments).

source
BVDOutbreakSize.zone_importation_blocks Method
julia
zone_importation_blocks(
    pops::AbstractVector,
    coords::AbstractVector,
    patch_of_zone::AbstractVector{<:Integer},
    parent_kernel::AbstractMatrix;
    decay
) -> NamedTuple{(:within, :between), <:Tuple{Any, Any}}
julia
zone_importation_blocks(
    pops::AbstractVector,
    coords::AbstractVector,
    patch_of_zone::AbstractVector{<:Integer},
    parent_kernel::AbstractMatrix;
    decay
) -> NamedTuple{(:within, :between), <:Tuple{Any, Any}}

The two blocks of the zone importation kernel, both normalisations of one gravity pull over every zone (gravity_pull).

within[z, q], for z and q in the same patch, is column-stochastic within that patch, so a zone's within-patch spill is a transfer that conserves the patch total.

between[z, q], for zones in different patches, carries the province model's own patch-to-patch flow:

so its column over a destination patch's zones sums to that patch's entry of parent_kernel, the province model's own province_importation_kernel. Summed over the zones of a patch, the zone stage's between-patch flow is the province model's for the same origin intensity, which is what keeps one movement from being counted at both levels.

source
BVDOutbreakSize.zone_initial_shares Method
julia
zone_initial_shares(
    z_w::AbstractVector,
    patch_ranges::AbstractVector{<:UnitRange},
    scale::Real
) -> Any
julia
zone_initial_shares(
    z_w::AbstractVector,
    patch_ranges::AbstractVector{<:UnitRange},
    scale::Real
) -> Any

Initial zone shares at the grid start: a within-patch softmax of the centred standard-normal draws z_w at a fixed scale, w_z = exp(scale (z_z − mean_p z)) / Σ_p. The centring removes the flat direction of the softmax. Returns a vector over the zones, summing to one within every patch range.

source
BVDOutbreakSize.zone_interpolation_weights Method
julia
zone_interpolation_weights(
    knots::AbstractVector{<:Integer},
    t0::Integer,
    n::Integer
) -> Any
julia
zone_interpolation_weights(
    knots::AbstractVector{<:Integer},
    t0::Integer,
    n::Integer
) -> Any

The linear map from knot values to the days t0 … n, as an (n_days × n_knots) weight matrix W with δ_daily = W δ_knotsᵀ, so the interpolation of every zone is one matrix product. Each column is interpolate_knots applied to a unit vector.

source
BVDOutbreakSize.zone_report_increments Method
julia
zone_report_increments(
    reports::AbstractMatrix,
    w0::AbstractVector,
    patch_ranges::AbstractVector{<:UnitRange},
    days::AbstractVector{<:Integer},
    t0::Integer,
    report_pre_cum::AbstractMatrix
) -> Any
julia
zone_report_increments(
    reports::AbstractMatrix,
    w0::AbstractVector,
    patch_ranges::AbstractVector{<:UnitRange},
    days::AbstractVector{<:Integer},
    t0::Integer,
    report_pre_cum::AbstractMatrix
) -> Any

Bin the daily expected reports (n_days × n_zones) of zone_forward into the vintage windows (d_{v−1}, d_v] given by the grid days days, the first window opening on day one. Days before the grid start t0 contribute the initial share times the patch's pre-t0 accrual (report_pre_cum, from zone_fixed_terms). Returns the (n_zones × n_vintages) expected increments.

source
BVDOutbreakSize.zone_report_pre_rows Method
julia
zone_report_pre_rows(
    report_pre::AbstractMatrix,
    patch_of_zone::AbstractVector{<:Integer},
    t0::Integer,
    n::Integer
) -> Any
julia
zone_report_pre_rows(
    report_pre::AbstractMatrix,
    patch_of_zone::AbstractVector{<:Integer},
    t0::Integer,
    n::Integer
) -> Any

The pre-t0 reporting term of each zone's patch on the days t0 … n, as an (n_days × n_zones) matrix so that a zone's daily reports are F I[:, z] + w_z(t_0) pre[:, z]. report_pre is the (n_patches × n) term of zone_fixed_terms.

source
Distributions.censored Method
julia
censored(d::BVDOutbreakSize.NegBinomialVector; upper)
julia
censored(d::NegBinomialVector; upper::AbstractVector)

Right-censor each entry of d at the matching upper, as censored does for one NegativeBinomial. Returns a CensoredNegBinomialVector.

source