Multipathogen#76
Open
Julian-Patzner wants to merge 161 commits into
Open
Conversation
…ity instead of the entire SoA
…fire on aggregate health status changes
This reverts commit 3dad491.
…ith infectiousness profiles)
…ing overhead in update_immunity!
…file) for consistency and clarity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multipathogen: Concurrent Multi-Disease Simulation with a Customizable Transmission, Immunity & Infectiousness Architecture
Overview
This PR generalizes GEMS from a single-disease simulator into a true multipathogen framework, allowing an arbitrary number of distinct pathogens to circulate, interact, and co-infect the same population concurrently within a single simulation run.
The core change is a move away from per-individual scalar disease fields toward a per-
(individual, pathogen)infection model backed by a cache + sharded-overflow registry architecture. On top of this, the PR introduces a fully composable transmission system (base rates × stackable modifiers), customizable infectiousness and immunity profiles, cross-pathogen interactions (viral interference and cross-immunity), and a type-stable pathogen parameterization that keeps the multi-pathogen hot loops dispatch-free. The PR also makes the entire logging, post-processing, reporting, and plotting pipeline pathogen-aware, and performs a large-scale repository restructure to organize the codebase around its actual domains (pathogen,population,settings,registries,simulation,analysis,logging,contacts).Key Changes & Features
1. Per-
(Individual, Pathogen)Infection ModelThe simulation no longer tracks a single infection per agent. Each individual can now carry multiple concurrent infections, one per distinct pathogen.
InfectionState: A new immutable,isbitsInfectionStaterecord encapsulates the entire disease timeline for a single infection (exposure,infectiousness_onset,symptom_onset…recovery,death), plus itspathogen_id, a precomputed per-tickinfectiousness::Int8, and anactiveflag. The matchingImmunityStatemirrors this for immunity records, tagged with a source flag (IMMUNITY_SOURCE_NATURAL/IMMUNITY_SOURCE_VACCINE).Individualholds a small inlineinfection_cache::NTuple{INFECTIONS_CACHE_SIZE, InfectionState}(and a parallelimmunity_cache) for its hot infection, plus aninfection_head::Int32/immunity_head::Int32pointer into a registry-backed linked list that holds any additional concurrent infections. This keeps the common single-infection case allocation-free and cache-local while supporting unbounded concurrency through overflow.infected::Boolsemantics with primitiveactive_pathogens_mask::UInt32anddetected_mask::UInt32fields on theIndividualstruct. Bitid - 1flags active infection / detection for a given pathogen, givinginfected!(ind, pid, val)/detected!(ind, pid, val)setters to keep the invariant single-sourced.infect!andset_progression!now guard against concurrent re-infection with the same pathogen (@warn+ skip), enforcing the ≤1 active infection per(individual, pathogen)rule.2. Sharded Registries & Lock-Free Concurrency
InfectionRegistry,ImmunityRegistry, andTestRegistry(with a per-pathogenTestState/get_test_state), holding the overflow records and test bookkeeping that no longer fit on the agent struct.Simulationas aVectorsharded by_owner_shard(id) = mod(id - 1, maxthreadid()) + 1, so each agent deterministically belongs to exactly one shard. Element-wise reads/writes during the threaded update and spread phases are therefore contention-free.flush_pending_infections!,flush_ended_infections!) between phases, keeping the parallel hot loops purely element-wise. Overflow unlinks are ordered before cache promotions to prevent an ended overflow record from being resurrected into a freed cache slot.registry_iterators.jlprovides type-constrained iterators (InfectionIterator/ImmunityIterator) that lazily resolve shards and walk an individual's cache + overflow chain, presenting all active infections through a single uniform interface (each_infection/each_immunity).3. Composable Transmission System
Transmission was decomposed into a base rate × stackable modifiers architecture, cleanly separating
TransmissionFunction(produces a base probability) fromTransmissionModifier(adjusts it via atransmission_factorcontract).CompositeTransmissionRate: Wraps a baseTransmissionFunctiontogether with a statically-typed tuple ofTransmissionModifiers, multiplying in each modifier'stransmission_factorwithout re-multiplying base rates.SinusoidalSeasonalTransmissionRate/SinusoidalSeasonalModifier: Adds seasonal forcing of the transmission rate.CrossImmunityTransmissionRate/CrossImmunityModifier: Models cross-protection, where prior exposure/immunity to one pathogen reduces susceptibility to another. Pathogens are referenced bypathogen_names(resolved to ids at construction) to avoid id-ordering bugs.ViralInterferenceTransmissionRate/ViralInterferenceModifier: Models interference, where an active infection with one pathogen suppresses transmission of another. Reads interfering state from the registry (not same-tick staged state), so it is order-independent of intra-tick spread.AgeDependentTransmissionRate/ConstantTransmissionRate: Retained and adapted to the new interface, with concrete typing and allocation-free evaluation.4. Customizable Infectiousness & Immunity Profiles
InfectiousnessProfile: A new abstraction (calculate_infectiousness) computing an individual's shedding level over the course of an infection from itsInfectionStatesnapshot, cached per-tick so the spread phase reads it directly off the agent. Two concrete profiles ship:ConstantInfectiousness(a fixed level across the whole infectious window) andStagedInfectiousness(a per-stage mapping —asymptomatic/presymptomatic/symptomatic/severe/critical).ImmunityProfile: A new abstraction (calculate_immunity) defining how immunity is acquired and decays —FullImmunity,NoImmunity, and the waning modelsExponentialWaning/SigmoidalWaning(now with a configurablevaccine_buildup_duration). Animmunity_is_stablehook lets the engine clearneeds_immunity_updateonce an individual's immunity has settled, skipping per-tick recomputation while immunity is constant.Pathogenstruct composes progressions, a progression-assignment function, a transmission function, an infectiousness profile, and an immunity profile — making each axis independently swappable per pathogen.5. Pathogen Parameterization & Type-Stable Dispatch
To keep the multi-pathogen hot loops allocation- and dispatch-free, the pathogen set is encoded in the type system rather than queried dynamically.
Pathogen:Pathogen{PRG, PA, TF, IP, IM}carries the concrete types of its progressions tuple, progression-assignment function, transmission function, and infectiousness/immunity profiles, so every dispatch on a pathogen resolves to a concrete method.Simulation{P<:Tuple}: Pathogens are stored as a statically-typed heterogeneous tuple, making the entire pathogen configuration part of theSimulation's type.@generatedLookup:get_pathogen(sim, pid)is a@generatedfunction that unrolls the tuple search at compile time, recovering the concretePathogen{…}type even when indexed by a runtimepid::Int8. The spread and progression loops can therefore specialize on each pathogen's concrete transmission/infectiousness/immunity types instead of paying dynamic-dispatch overhead per contact.6. Multi-Pathogen Seeding, Configuration & Start Conditions
MultiStartCondition: A composite start condition that applies a vector of per-pathogenStartConditions, enabling each pathogen to be seeded independently (e.g. differentInfectedFraction/PatientZero/RegionalSeedsper disease).determine_start_conditionconstructs one automatically when multiple pathogens are supplied.[Pathogens.<Name>]tables, each with its owntransmission_function,infectiousness_profile, andimmunity_profileblocks, and start conditions can target a specificpathogenby name._finalize_pathogen_ids!assigns and validates contiguous pathogen ids (1..N), bounded byMAX_PATHOGENS, regardless of whether pathogens arrive via config or as a user-supplied tuple.7. Pathogen-Aware Analysis, Reporting & Logging
pathogen_id, and the logging layer was reorganized intologging/{logger, event_loggers, infection_logger, tick_loggers}.jl.pp_*post-processing suite (incidence, attack rates, effective R, R0, serial/generation intervals, tick cases/deaths/tests, detection) was made pathogen-aware, splitting and grouping results per pathogen.aggregate_by_pathogenand updatedResultData/Batchaggregation (and constructors, now thatSimulationis parameterized) to render and store per-pathogen results across runs.8. Repository Restructure
A large structural cleanup with no behavioral intent, reorganizing the source tree around its domains:
structs/+methods/were dissolved into domain folders:pathogen/(pathogens, progressions, transmission functions, profiles, vaccines),population/(individuals + methods, populations, age groups),settings/(settings, containers, ags),registries/,simulation/(simulation, infections, calibration, batch, initialization, termination),contacts/,analysis/(formerlymodel_analysis/), andlogging/(formerlylogger/).agents.jlwas split intopopulation/individuals.jl(struct) andpopulation/individual_methods.jl(behavior). Setting membership/lookup moved onto the individual._prefixes to internal symbols (e.g._basefolder).Unit-Testing
test/registriestest.jlcovering cache/overflow promotion, sharded ownership, deferred flush ordering, and iterator traversal.test/pathogentest.jlfor the new transmission functions and modifiers (composite, seasonal, cross-immunity, viral interference), both infectiousness profiles, immunity/waning profiles, and the single-active-infection guard.test/settingstest.jland updatedagentstest,infectionstest,interventionstest,simulationtest, andutilstestfor the per-(individual, pathogen)model, bitmask getters/setters, andMultiStartConditionseeding.