# What's new All notable changes to the codebase are documented in this file. Changes that may result in differences in model output are flagged with the term "Regression". Changes that may require update to downstream code are flagged with the term "Migration". ## Version 3.7.2 (2026-10-06) - `ss.ClusterNet` (and so `ss.HybridNet`) and `ss.MSMNet` now shuffle agents using `float64` random numbers and a stable sort. Previously, tied `float32` random numbers were ordered differently on different CPUs, so the same seed could give slightly different results on different machines. - *GitHub info*: PR [1435](https://github.com/starsimhub/starsim/pull/1435) ## Version 3.7.1 (2026-10-03) ### New features - `sim.add_module()` adds a module (e.g. an intervention) to a sim that has already been initialized or partly run. - `ss.RandomNet(uniform_targets=True)` (and `ss.HybridNet(uniform_targets=True)`) chooses the target of each edge uniformly from the eligible agents, as in Covasim v3, rather than in proportion to their number of contacts. This gives less variable numbers of contacts (e.g. a variance of 15 rather than 30 with `n_contacts=ss.poisson(20)`). - `sim.people.dead` now covers every agent ever created (indexed by UID), so `people.dead.sum()` is the number of deaths and `people.dead.uids` are the agents who died. Dead agents are still removed from all other arrays. Previously, it was always `False` after each timestep. (Note: `ti_dead` is unchanged, since this is used to schedule deaths.) ### Performance - Network transmission, `ss.find_contacts()`, and removing dead agents from networks now use Numba for speed (up to 3x faster for some sims). Random numbers for transmission are now only drawn for edges that can transmit. `Network.find_contacts()` is a further 1.3–4x faster, since it no longer creates a set; `ss.find_contacts()` has a new `as_array` argument to do the same. ### Changes that may affect results - *Regression*: with `ss.options.crn = False`, transmission results differ stochastically, since fewer random numbers are drawn. Results with CRN (the default) are unchanged. - *Regression*: `ss.RandomSafeNet` now avoids ties by using `float64`, fixing a bug which gave agent 0 too many contacts. ### Changes that may require migration - *Migration*: sim and module parameters are now locked when the sim is initialized, so setting a parameter that doesn't exist (e.g. a typo such as `sim.pars['rand_sed'] = 2`) raises an error instead of silently doing nothing. To add a new parameter after initialization, use `pars.update(my_par=..., create=True)`. ### Bugfixes - `dist.shrink()` now removes the SciPy distribution and the random number generator, so modules with many distributions no longer fail the size check in `sim.shrink()`. - `ss.Results()` can be created without a module, so results can be merged like dicts, e.g. `sc.mergedicts(sim.results, other)`. - `network.validate()` no longer fails for a network with no edges, and `sim.current_scale` no longer fails if called during initialization with `rescale=True`. - *GitHub info*: PR [1433](https://github.com/starsimhub/starsim/pull/1433) ## Version 3.7.0 (2026-10-02) This release adds features needed to port Covasim to Starsim (Covasim v4), and which are also useful for other disease models: 2D results and agent arrays (e.g. by variant), CRN-safe choice of exactly *N* agents, dynamic rescaling, Covasim's "hybrid" network (household, school, workplace, and community networks), and more MultiSim methods. ### New features - 2D results: `res = ss.Result(..., columns=['wild', 'alpha'])` has shape `(npts, ncols)`. `res.alpha` (or `res.col('alpha')`) returns a 1D result for that column; scaling, `to_df()`, `sim.summarize()`, `resample()`/`annualize()`, `MultiSim.reduce()` and plotting all handle one column/line per result column. - 2D agent arrays: `ss.FloatArr('sus_imm', columns=n_variants)` (and the same for `BoolArr`/`IntArr`) grow with the population like other states. Columns can be named, e.g. `columns=['wildtype', 'mutant']` allows `arr.mutant` (or `arr['mutant']` or `arr.col('mutant')`, and e.g. `arr.mutant += 0.1` updates the column), and more dimensions are supported via e.g. `columns=(2,3)`. - `ss.choose_n(n, weights=None)` chooses exactly `n` agents without replacement, and is CRN-safe: if the candidates change slightly, most of the same agents are chosen. It can be used for exact-count seeding, e.g. `ss.SIR(init_prev=ss.choose_n(20))`, or anywhere else an `ss.bernoulli()` can be used. - Distributions accept a `round` option at creation, e.g. `ss.lognorm_ex(ss.days(5), ss.days(2), round='nearest')`; `round=True` (or `'stochastic'`) rounds stochastically as before, and `round='nearest'` rounds deterministically (also accepted by `dist.rvs()`). - Dynamic rescaling, as in Covasim: with `ss.Sim(rescale=True)`, the population scale factor starts at 1 and increases to `pop_scale` as agents stop being naive, making some of them naive again via the new `disease.make_naive()`; the per-timestep scale is in `sim.results.pop_scale`, and `sim.current_scale` gives the current value. Controlled by the new sim parameters `rescale`, `rescale_threshold` and `rescale_factor`. See the new [Population rescaling](https://docs.starsim.org/user_guide/advanced_rescaling.html) user guide page. - `ss.Infection.infect()` is split into `infect_route()`, which computes transmission along one network, and `finalize_infections()`, which removes duplicate infections, so multi-strain diseases can call `infect_route()` once per strain. - New networks: `ss.ClusterNet` (fully connected clusters, e.g. households) and `ss.HybridNet` (Covasim's household, school, work and community networks, which become four networks `h`, `s`, `w` and `c` when added to a sim). `ss.RandomNet` and `ss.RandomExactNet` have new `dynamic` (set `False` for a static network) and `age_range` parameters. - New network methods: `net.remove_edges()` (indices or a boolean mask), `net['p1']` to get an edge column, and `net.eligible()`; `net.from_df()` also accepts a dict of arrays, and returns the network. - New MultiSim methods: `combine()` (merge the sims into one larger sim), `ss.MultiSim.merge()` and `split()`, and `compare()`/`plot_compare()`. - The infection log (`ss.infection_log`) now records the network of each infection, and extra data per infection (e.g. `log.add_entries(uids, sources, t, variant=variants)`); it stores entries as arrays, so it is much faster. - The docs now publish a machine-readable index of the whole API, including default module parameters, for LLMs and other tools: (also `llms-full.txt`, with examples, and `api.json`). This replaces the root `llms.txt`, which was a copy of `CLAUDE.md`. ### Changes that may affect results - *Regression*: stochastic rounding (`dist.rvs(round=True)`) is now CRN-safe, so results that use it (e.g. `ss.NCD`) will differ. - *Regression*: results inside nested `ss.Results` are now scaled by `pop_scale` (previously only top-level results were). - *Regression*: cumulative infections are now summed after scaling, which may give differences at the level of floating-point precision. ### Changes that may require migration - *Migration*: comparing `sim.t` (an `ss.Timeline`) to a number, or using it as an index, now raises an error; use `sim.ti` instead (e.g. `if sim.ti == 10`). Setting `self.t` on a module also gives a clearer error. - *Migration*: module names must be unique across all module types. Two modules with the same default name (e.g. two `ss.RandomNet()`s) are now renamed with a warning (`randomnet`, `randomnet_1`) rather than raising an error; explicitly set names that clash still raise an error. - *Migration*: `ss.InfectionLog` is no longer a NetworkX graph; use `log.to_df()`, or `log.to_graph()` for a graph. - *Migration*: `init_prev` values greater than 1 now raise an error (previously they infected everyone); use `ss.choose_n(n)` to seed a fixed number of infections. - *GitHub info*: PR [1431](https://github.com/starsimhub/starsim/pull/1431) ## Version 3.6.2 (2026-09-30) This release contains many LLM-assisted bugfixes. Most changes should have relatively little impact on users, although results may differ slightly for some results in some projects. ### Changes that may affect results - *Regression*: `ss.Deaths` treated death rates given per month/week/day as per year. - *Regression*: `ss.SIR` recovery/death and `ss.Pregnancy` losses were mistimed when the module had its own `dt`; CBR, CMR, and burn-in fetus ages also used the sim's `dt` instead of the module's. - *Regression*: `ss.Pregnancy` burn-in started one step late and its events were counted at `ti=0`; fetal deaths caused by maternal death are now classified by gestational age; fertility data are now looked up at the mean pregnancy duration rather than the shortest. - *Regression*: `ss.NCD` agents with a zero-length prognosis never died. - *Regression*: `ss.randint` UID draws now use 64-bit uniforms (previously correlated for wide ranges, e.g. `ssl.ErdosRenyiNet`) and floor correctly for negative `low`; CRN uniforms are never exactly 0; zero-scale distributions (e.g. `ss.normal(std=0)`) return the constant instead of NaN; `ss.years(ss.poisson(x))` now scales with `dt`. - *Regression*: rate/probability conversions now use `np.expm1()`/`np.log1p()` instead of `1 - np.exp()`/`np.log(1 - x)`, which is more accurate for small probabilities and fixes results differing between x86 and ARM machines (issue [1426](https://github.com/starsimhub/starsim/issues/1426)). - *Regression*: `ss.prob.to_prob()` with no argument now uses the module's `dt`, and adding/subtracting rates with different units now gives the right value. - *Regression*: modules given time arguments via `super().__init__(**kwargs)` now inherit the sim's `dt`. - *Regression*: `ss.PostnatalNet` edges ended one step early; `ss.MSMNet` redrew participation every step, paired deterministically, and had unitless `duration`/`acts`. - *Regression*: `ssl.ErdosRenyiNet` and `ssl.DiskNet` could connect dead agents; `ssl.DiskNet` agents could leave the unit square; `ssl.HouseholdNet` re-aged unborn agents. - *Regression*: `ss.routine_triage`/`ss.campaign_triage` never delivered; `ss.treat_num` queued duplicate and ineligible agents; screening outcomes persisted between rounds. - *Regression*: `ssl.Cholera` environmental transmission was effectively off, and agents could be infected twice; `ssl.Ebola` bodies stopped transmitting after one timestep, rather than at burial (deaths are now recorded at burial). - *Regression*: fixed `ssl.mnch` `fetal_infection` never applying damage during pregnancy, `NeonatalSepsis` killing non-neonates, and `treat_pregnant` not cancelling deaths or applying `tx_growth_reversal` as a fraction. - *Regression*: `sim.summarize()` now uses the final value for `cumulative` results (e.g. `births_cumulative`). - *Regression*: date-based sims with sub-daily timesteps (e.g. `dt=ss.days(0.25)`) now step exactly; previously steps were slightly too long in leap years (0.2507 days). - *Regression*: `ss.options.set('defaults')` no longer switches to 64-bit precision, and `STARSIM_PRECISION` is now respected. - *Regression*: `ss.Normal` calibration likelihoods computed the default variance incorrectly and misapplied per-timepoint `sigma2`; `step_containing()` picked the next step rather than the containing one. - *Regression*: `MultiSim(debug=True)` now runs all `n_runs`; functions added via `sim.loop.insert()` now run on the right sim after the sim is copied. ### Other bugfixes - Campaign years outside the sim now raise an error, rather than running at the first/last timestep; `DateArray.to_date(inplace=True)` on a float array now raises an error rather than doing nothing; the unused `interpolate` argument of `ss.CampaignDelivery` and `col_names` argument of `Result.resample()` have been removed. - Fixed crashes: comparing an `Arr` with a NumPy array; `Arr.astype()`; `ss.Births` with `crn=False` and dataframe rates; short sims with `ss.Pregnancy` burn-in; `ss.BreastfeedingNet` with deliveries during burn-in; saving an uninitialized sim; `demographics=True` with `birth_rate`/`death_rate`; `ss.RandomSafeNet(dur=)`; `ss.rand_raw` under CRN; multi-disease `ss.Dx`/`ss.Tx` dataframes; `ss.RoutineDelivery` with date-based sims or non-annual `years`; `ssl.HIV` with a custom name; nested `ss.options.context()`; `Module.create()`; `Results.annualize()`/`append()`; several calibration paths (`die=False`, float-year `combine_reps`, `Binomial` `p` column, interrupted calibrations). - Fixed plotting: `MultiSim.plot()` deleting `auto_plot=False` results and ignoring `fig`/`alpha`; `sim.plot('new_deaths')` with several matching keys; `People.plot_ages()` percentages and `bins`; `Result.plot(fig=...)`; `Loop.plot()`/`plot_cpu()`; date-axis ticks with Sciris 3.4. - `ss.MFNet(rel_part_rates=...)` now scales participation (previously it had no effect). - `ss.Pregnancy(dur_pregnancy=...)` now accepts a number, duration, or any distribution, not only an `ss.choice`. - Added `DateArray.days_since()`, which gives the exact number of days since a date; `ss.DateArray()` now reads strings as dates (previously as years). - `for p in sim.people` now iterates over active agents only; `timeline.relvec` is now numeric; `ss.StaticNet` passes extra arguments to networkx generators; `Network.plot()` passes kwargs to `nx.draw_networkx()`; `ss.SIR(name=...)` sets the label; `ss.Samples` no longer adds an `Unnamed: 0` column. - Fixed diagnostics (`sim.set_diagnostics()`) never recording random variates; `sim.init_dists()` no longer changes dist seeds when called again; and other small fixes to time, distribution, and debugging utilities. - Fixed several docstring examples. - *Migration*: `ss.Network` now takes `pars` as its first argument, like other modules, plus an optional `edges` dict or dataframe; standard module arguments (`name`, `label`, `dt`, etc.) are passed to `update_pars()` and other keyword arguments are still treated as edges. `ss.Network('name')` no longer works; use `ss.Network(name='name')`. - *GitHub info*: PR [1427](https://github.com/starsimhub/starsim/pull/1427) ## Version 3.6.1 (2026-08-29) This release contains two performance improvements: - `nb_indexer()` (used for `ss.Arr` UID indexing) is now written as an explicit loop rather than `arr[inds]`, due to a regression in Numba 0.66. - `ss.RandomNet.get_edges()` now gathers edge targets from the agent array rather than the (much larger) edge array when every agent has the same number of contacts. - *GitHub info*: PR [1423](https://github.com/starsimhub/starsim/pull/1423) ## Version 3.6.0 (2026-08-28) This release delivers substantial framework-overhead performance improvements, focused on simulation initialization and per-timestep execution. The gains are largest for lightweight/ODE-style simulations (e.g. a bare 10-year daily sim now initializes roughly 35x faster, and runs ~2x faster with `people_results=False`); simulations with many modules see large initialization speedups from cheaper module timeline copies, and simulations run with common random numbers disabled (`ss.options.crn = False`) are ~1.2-1.3x faster than with CRN. It also contains several bugfixes, a new `ss.SEIR` base class, and a new syntax for defining state aliases. ### Initialization performance - When a module shares the sim's timeline (the common case), its time vectors were previously deep-copied. They are now copied as containers that share those (immutable) elements. - `ss.Timeline` now builds only the necessary vectors ( `tvec`, `tivec`, and `yearvec`); the human-friendly `datevec`, `timevec`, and `relvec` are created lazily on first access (as cached properties). This avoids constructing date sequences that may not be required by all sims. - When every module shares the sim's timeline, plan entries are now generated directly in execution order with no sort; otherwise a numeric `np.lexsort` on the canonical time vectors is used. Previously the plan was sorted using a `(date/dur, int)` object key, which required a slow comparison step. - `LoopEntry` now uses `@dataclass(slots=True)`, reducing the memory footprint of the 10⁴–10⁵ entries in a typical plan (`starsim/loop.py`). ### Per-timestep performance - `sim.run()` no longer records timing for every plan entry, since most runs do not use them. Pass `sim.run(profile=True)` to record per-entry CPU timing. The plan metadata via `loop.to_df()` is still accessible. - A `people_results` parameter was added (default `True`). Setting `ss.Sim(..., people_results=False)` skips the automatic per-timestep People-level results, saving 40% of runtime in a bare sim. Disease/module dynamics are unaffected, though some modules (e.g. demographics) rely on `results.n_alive`, so this optimization can only be used for some sims. - A module's `update_results` is now omitted from the integration plan when the call does nothing. - Common random numbers (CRN) can now be skipped with `ss.options.crn = False`. With CRN disabled, both distribution jumping and state copying are skipped, increase run performance by 1.2-1.3x. Note that CRN should still be used in most cases; this pathway is intended for sims that are already not CRN-safe (e.g., use `RandomFastNet`), and where performance is most important. ### SEIR models and infection incidence - Added `ss.SEIR`, which extends `ss.SIR` with a latent, non-infectious exposed state. Its `exposed` and `infectious` states are the literal E and I compartments; `infected` means "has the infection" and is derived as E plus I, so `n_infected` and `prevalence` include latent infections while transmission depends on `infectious` alone. `ti_exposed` is the time of acquisition and `ti_infectious` is the time of becoming infectious; `ti_infected` is deliberately *not* defined for `ss.SEIR`, since it is too easily confused with `ti_infectious` (accessing it raises an `AttributeError`). `ss.SIR` continues to schedule its outcomes from `ti_infected`, since for an SIR model the two coincide; `ss.SEIR` overrides `set_progression()` to schedule from `ti_infectious` instead. - `ss.SIR.set_prognoses()` now splits into two methods that subclasses can override separately: `set_infection()` (make the agents infectious, which `ss.SEIR` delays by the latent period) and `set_progression()` (schedule recovery, death, and any other stages, relative to the onset of infectiousness). - `ss.SIR.step_state()` and `ss.SIR.step_die()` now clear the infection via a new `clear_infection()` method, which `ss.SEIR` overrides (since its `infected` is derived and so can't be written to). - *Regression/bugfix*: `new_infections` is now recorded by `ss.Infection.set_prognoses()` as infections happen, rather than being inferred afterwards from `ti_infected`. Previously, a model that set `ti_infected` to a future time (as any SEIR-type model must) had its infections counted at the wrong timestep, and the seed infections subtracted on the first timestep produced a negative first value, e.g. `new_infections[0] = -196` for measles seeded with 196 initial cases (issue [1389](https://github.com/starsimhub/starsim/issues/1389)). This fixes user-written models as well as the library ones. Seed infections are still excluded from incidence, since they are prevalent rather than incident. - *Migration*: an `ss.Infection` subclass that overrides `set_prognoses()` without calling `super()` now leaves `new_infections`, `cum_infections` and `incidence` permanently zero, where previously the base class recomputed them from `ti_infected` afterwards. Add `super().set_prognoses(uids, sources)` to the override, or override `update_results()` to compute incidence some other way. - `ss.mock_sim()` now includes `initialized = True`, since a mock sim stands in for an initialized one; `set_prognoses()` uses this to tell seeding apart from transmission. - Which compartments `ss.SIR.plot()` shows is now set by a `plot_states` class attribute, so a subclass that adds a compartment can show it in one line (`ss.SEIR` sets `plot_states = ['n_susceptible', 'n_exposed', 'n_infected', 'n_recovered']`). - `ssl.Measles`, `ssl.Cholera` and `ssl.Ebola` now subclass `ss.SEIR`. `Measles` supplies only parameter values; `Ebola` and `Cholera` also override `set_progression()`. - *Migration*: `ssl.Cholera.dur_exp2inf` and `ssl.Ebola.dur_exp2symp` are now both called `dur_exp`, matching `ss.SEIR`. `ssl.Cholera` and `ssl.Ebola` no longer accept `dur_inf`, which they never used. - *Regression/bugfix*: `ssl.Measles` called `ss.SIR.set_prognoses()`, which set `infected = True` without clearing it, so exposed agents were simultaneously infectious: `dur_exp` had no effect on transmission and the model behaved as an SIR model. It also sampled `dur_inf` and `p_death` twice per infection. Both are fixed. `ssl.Cholera` and `ssl.Ebola` were not affected by this, since neither inherited `ss.SIR.set_prognoses()`. Note that `ssl.Measles.infected` continues to mean exposed plus infectious, as it did in v3.5.x; it is `infectious` that now correctly excludes the latent period. - *Migration*: for `ss.SEIR` and its subclasses (`ssl.Measles`, `ssl.Cholera`, `ssl.Ebola`), the I compartment is now `infectious` rather than `infected`, and `n_exposed` and `n_infectious` are the disjoint compartment counts. Code that read `infected`/`n_infected` to mean "currently transmitting" should use `infectious`/`n_infectious`; code that scheduled events off `ti_infected` should use `ti_infectious` (or `ti_exposed`, for the time of acquisition). - *Regression/bugfix*: `ssl.Ebola` set `rel_trans` in `set_prognoses()`, at which point newly exposed agents are neither severe nor dead, and never updated it as agents progressed — so `sev_factor` and `unburied_factor` had essentially no effect. It is now updated in `step_state()` as agents change state. - `ssl.Cholera.asymptomatic` is now defined via `define_aliases()` rather than as a property; its meaning (infectious but not symptomatic, and therefore shedding much less) is unchanged. - *Bugfix*: `ssl.Cholera` recorded environmental infections with `np.full_like(..., np.nan)` on an integer array, which warns and writes the minimum integer value; it now uses `ss.dtypes.int_nan`. - *Regression/bugfix*: `prevalence` counted only the I compartment in SEIR-type models, silently excluding every latent infection. Since `n_infected` is auto-generated from the `infected` state and `prevalence` is computed from it, an `ss.SEIR` model seeded with 218 measles cases and run for less than the latent period reported `n_infected = 0` and `prevalence = 0` at every timestep; during exponential growth, when E is disproportionately full, the majority of infections could be excluded (at peak incidence: ~8% of infected agents for `ssl.Measles`, ~87% for `ssl.Cholera`, ~89% for `ssl.Ebola`). `n_infected` and `prevalence` now count E plus I, matching standard epidemiological usage and the library docstrings, which already described `exposed` as "infected but not yet infectious". - *Regression/bugfix*: `ssl.Cholera` and `ssl.Ebola` scheduled their extra stages relative to `ti_infected`, and tested membership of the I compartment with `self.infected`. Both now use `ti_infectious` and `self.infectious`. In particular, `ssl.Cholera.asymptomatic` (which drives environmental shedding) was `infected & ~symptomatic`, which under the corrected naming would have made every exposed agent shed. - Since SEIR is now built in, the worked example in [Tutorial 4](https://docs.starsim.org/tutorials/t4_diseases.html) extends `ss.SIR` into an SIRS model with waning immunity instead, and the [Diseases](https://docs.starsim.org/user_guide/modules_diseases.html) user guide has a new section on the infected/infectious distinction. ### State aliases - Added `Module.define_aliases()`, which defines a state name that resolves to something else: either the name of another state (e.g. `self.define_aliases(infectious='infected')`), or a callable taking the module as its only argument (e.g. `self.define_aliases(asymptomatic=lambda self: self.infected & ~self.symptomatic)`). A callable alias behaves like a property: it is recomputed on each access, so it can be read but not written to. - An alias is only consulted if the attribute isn't otherwise defined, so it acts as a default that a subclass replaces just by defining a state, or another alias, of the same name. - Any keyword arguments to `define_states()` other than `check`, `reset` and `lock` are passed to `define_aliases()`, so aliases can be declared alongside the states they refer to. - *Migration*: `ss.Infection.infectious` is no longer a property; it is now an alias of `infected`, defined via `define_aliases()`. Reading `disease.infectious` is unchanged, and overriding it with a property still works. - `Module` now defines `__getattr__` to resolve aliases; a missing attribute still raises `AttributeError` as before. - A callable alias now generates an automatic `n_` result, in the same way an `ss.BoolState` does, since unlike a string alias it is a derived quantity that isn't counted anywhere else. This is what lets `ss.SEIR` derive `infected` from `exposed` and `infectious` while keeping `n_infected` and `prevalence`. The new `Module.derived_state_names` property lists them. *Regression*: modules with a callable alias (e.g. `ssl.Cholera.asymptomatic`) gain a corresponding result. - Writing to a callable alias now raises an error rather than being silently discarded. Previously `self.asymptomatic[uids] = True` would appear to work, but modified a temporary array. - `Module.define_states(reset=...)` now accepts a state or alias name (or list of names) as well as `True`, so a subclass can replace or remove individual inherited states and aliases rather than all of them. Removing a name without replacing it leaves it undefined, which is how `ss.SEIR` drops `ti_infected`. *Migration*: `reset=True` now clears inherited aliases as well as states, so `ss.SIR` re-declares `infectious` explicitly. - *Migration*: `define_states(check=False)` is replaced by `define_states(overwrite=True)`, which replaces an existing state or alias of the same name. `check=False` did not actually replace anything: it skipped the "already present" check but still appended, so the state ended up listed twice in `state_list` and `_auto_states`, and initialization then failed with `Another result named "n_infected" already exists`. `overwrite=True` removes the old entry first. `check=False` also disabled type validation, which is now always on. `define_aliases()` takes `overwrite` too. - `define_aliases()` now raises if an alias would be shadowed by an existing state of the same name, rather than silently defining an alias that is never consulted; pass `overwrite=True` to replace the state instead. - *Migration*: `Module.define_results()` no longer takes a `check` argument, which was accepted but never used. Its duplicate-name check now also covers derived states, so defining a result that collides with one generated by a callable alias gives the same explanatory error as for a `BoolState`. ### Timepars and timesteps A timepar only means something once it has been resolved against a timestep, but there were two boundaries at which the unit was silently discarded, producing plausible-looking numbers that were wrong by whatever the unit ratio was. Both now raise, and there are explicit methods for doing the conversion. - *Migration*: `float()` on an `ss.TimePar` now raises a `TypeError`, since it discarded the unit: `ss.years(1)`, `ss.months(1)` and `ss.days(1)` all gave `1.0`, as did `ss.peryear(1)`, `ss.permonth(1)` and `ss.perday(1)`, despite differing by a factor of 365. Use `.value` for the raw value, `.years` for the value in years, and `.to_dt()` to convert to timesteps (or `.to_prob()`, `.to_events()` and `.to()` for rates). Single-input NumPy ufuncs on a rate (e.g. `np.exp(rate)`) raise the same error, apart from the unit-independent ones (`np.isnan`, `np.isinf`, `np.isfinite`, `np.sign`, `np.signbit`); `float()` on an `ss.date` is unchanged. Any code this breaks was already computing the wrong number: `np.log(2)/ss.years(1)` and `np.log(2)/ss.years(12)` floated to the same value, so a half-life expressed that way had no effect at all. Durations remain plottable: `ss.DateConverter` is now registered for `ss.dur` as well as `ss.date` and `ss.DateArray`, so matplotlib converts them itself rather than via `float()`. This also fixes plotting a list or pandas column of durations, which previously fell back to the bare magnitude. *Migration*: `TimePar.to_numpy()` was removed (use `to_array()`, which is unchanged), since it made matplotlib unpack a timepar to a plain number before it could find the converter. - Added `TimePar.to_dt()`, which resolves a timepar against the timestep: for a duration it returns the number of timesteps (`ss.years(100).to_dt(ss.months(1))` is `1200.0`), and for a rate the per-timestep value. This is the conversion that must happen before a duration is used in timestep arithmetic, e.g. `self.ti_clearance[uids] = self.ti_infected[uids] + self.pars.dur_persist.to_dt()`. See the new [Durations and timesteps](https://docs.starsim.org/user_guide/advanced_time.html#sec-to-dt) section of the time user guide. - *Regression/bugfix*: three internal places used `float()` where they needed years. Only the first changes simulation output: `ss.MFNet.step()` passed `float(self.t.dt)` as the upper age for setting network states, so a monthly timestep was treated as one *year*, re-rolling `debut` and `participant` across a 12x too wide age band each step (a 40-year monthly sim shifted `cum_infections` by ~4%; a 10-year one was unchanged, since the wrongly-included agents are all far below debut age). `ss.DateArray.subdaily` and `Timeline.now('yearvec')` were also affected, but only outside the simulation period or in ways that did not propagate to results. - `ss.Rate.to()` now accepts a duration unit as well as a rate class, and picks the matching rate subclass: `ss.peryear(1).to('month')` gives `permonth(0.0833)` and `ss.probperyear(0.5).to('month')` gives `probpermonth(0.0561)`. Previously only `.to(ss.permonth)` or `.to('permonth')` worked, while `dur.to('month')` worked fine -- an asymmetry that was easy to trip over. - *Regression/bugfix*: converting an `ss.prob` between units now goes via the underlying rate, so it agrees with `prob.to_prob()`. Previously the value was rescaled linearly, which is right for `ss.per` and `ss.freq` but not for a probability: `ss.probperyear(0.5).to(ss.probpermonth)` gave `probpermonth(0.0417)` rather than the correct `probpermonth(0.0561)`, and scaling upwards raised `ValueError: Probabilities must be in [0, 1], not 182.5` instead of saturating towards 1. This applies to `.to()`, to direct cross-unit construction (`ss.probpermonth(ss.probperyear(0.5))`), and to conversions between an `ss.prob` and an `ss.per` or `ss.freq`. Models that converted probabilities this way were understating event rates; those that used `.to_prob()` are unaffected. `ss.Rate` also gained a `rate` property (an alias to `value`, except for `ss.prob`, where it is `-log(1-value)`), which is what the conversion uses. - *Migration*: `Module.link_rates()` is now `Module.link_timepars()`, since it links durations as well as rates (previously only rates were given a `default_dur`, so only rates could resolve themselves against `dt` with no argument). ### Other bugfixes - *Regression/bugfix*: `prevalence` was computed as `n_infected/len(sim.people)`, but `len(sim.people)` counts the agents in `auids`, which still includes agents who died on the current timestep — they aren't removed until `people.finish_step()`, so that analyzers can inspect them. Their disease states have already been cleared by then, so the numerator excluded them while the denominator included them, and `prevalence` was systematically understated by exactly the number of deaths that timestep (3.5% relative in an SIR run with `p_death=0.2`). It now uses the number of living agents, so `prevalence == n_infected/n_alive` exactly, as users expect. This affects `ss.Infection` and `ss.NCD`, and applies to every v3 release: the expression dates from v3.0.0, and the loop order that makes it wrong is unchanged since then, so `prevalence` from any previous version is understated for a model with mortality. Added `People.n_alive`, which is what `results.n_alive` records; use it rather than `len(people)` for any per-timestep denominator. - *Regression/bugfix*: `results.cum_deaths` is now a true running cumulative sum; previously it recalculated the total on each timestep, resulting in an off-by-one timestep error. - *Regression/bugfix*: `ss.RandomNet` now samples edge targets from the list of sources rather than the agent list, so it produces different (and, for stratified networks, correct) realizations than Starsim v3.5.x; `ss.RandomExactNet` and `ss.RandomSafeNet` are unaffected for contiguous UIDs. - *Regression/bugfix*: `ss.RandomSafeNet` now forms edges between UIDs rather than positional indices. - *Bugfix:* `sim.to_json()` now works for sims containing modules that have different timelines (and thus different results timesteps). - *Bugfix:* `ss.Births()` now accepts a `Time` data column, matching `ss.Deaths` and `ss.Pregnancy` (the previous label of `Year` also still works, however). - *GitHub info*: PR [1408](https://github.com/starsimhub/starsim/pull/1408) ## Version 3.5.2 (2026-07-14) - Starsim v3.5.0 added several optimizations affecting how random numbers are drawn. However, these changes introduced several `dtype`-related bugs that have been fixed in this release: - Fixed a bug where `ss.random()` always returned `float64` regardless of the configured type `ss.dtypes.float`. - Fixed `ss.poisson()` sometimes returning `float64` and sometimes returning an integer. It now always returns `ss.dtypes.int`. - Fixed `ss.multi_random.rvs()` returning an incorrect number of samples due to type mismatches. - *Regression*: Because the default CRN mechanism precision has changed to consistently match `ss.dtypes.float`, the exact random values produced for agent-indexed draws are different from v3.5.1. - `ss.uids()` now performs additional validation to more consistently ensure it only contains integers. - *Migration*: Previously `ss.uids([1.5])` would convert the input to an integer and thus return `ss.uids([1])`. However, because this usage would often reflect an error, this operation will now raise a `TypeError` to prevent inadvertent conversion of floats silently succeeding. To maintain previous functionality, convert the input to integers before calling `ss.uids()`. - `Timeline.now()` returns durations rather than bare floats, for simulations that use duration-based units. - Made the `folder` argument of `ss.Samples` optional, and moved it from first to third. - *Migration:* Positionally-created Samples should use kwargs instead, e.g. `ss.Samples(resultsdir, outputs)` should be changed to `ss.Samples(outputs, folder=resultsdir)`. - *GitHub info*: PR [1393](https://github.com/starsimhub/starsim/pull/1393) ## Version 3.5.1 (2026-07-09) - Added `ss.Arr.isin()`, for comparing an array against multiple values. For example, code like `recent = (self.ti_infected == self.ti) | (self.ti_infected == self.ti-1)` could now be `recent = self.ti_infected.isin([self.ti, self.ti-1])`. - `ss.poisson` now uses precomputed CDFs for a 60x performance gain in some cases. - `ss.choice` (with the default `replace=True`) now uses the inverse-CDF/hash path for a ~10x performance gain in some cases. - *Regression*: because the `ss.choice` random-number path changed, the exact values produced for `ss.choice` UID draws differ from previous versions (results remain statistically equivalent). Models using `ss.choice` (including `ss.Pregnancy`) will produce different stochastic realizations, and stored regression baselines should be regenerated. - Fixed a bug in `ss.library.HouseholdNet` move-out timing that only manifested when the `ss.Pregnancy` module ran on a coarser `dt` than the sim (e.g. `ss.Pregnancy(dt=ss.months(3))`). Eligibility was determined by comparing `pregnancy.ti_delivery` (an index in the pregnancy module's *own* timeline) against the sim timestep; with mismatched dt, pregnant non-heads became re-eligible far too often, greatly over-fragmenting households and suppressing household transmission (e.g. ~40% fewer infections in a TB model with quarterly pregnancy). Move-out is now evaluated exactly once per pregnancy (keyed to `ti_pregnant`), which is dt-agnostic and bit-identical when the pregnancy dt equals the sim dt. - *Regression*: This fix will change the results obtained from using this module. - *GitHub info*: PR [1388](https://github.com/starsimhub/starsim/pull/1388) ## Version 3.5.0 (2026-07-05) This release provides several major performance improvements, focused on random number generation, networks, and pregnancy. On a representative large simulation (50,000 agents, 100 years, SIS on a random network with births and deaths), the random number changes alone make the default configuration roughly 1.4x faster, or about 2x faster with common random numbers disabled (`ss.options.crn = False`); the network, transmission, and pregnancy changes described below provide further ~1.3x speedups on top of this, for a total of ~1.8-2.6x speedups. ### Common random numbers - Common random numbers (CRN) work by giving each agent a "slot" and drawing one random number per slot, so that the same agent makes the same decision regardless of what other agents do. Previously this was implemented by drawing `slots.max()+1` numbers and keeping only those at the requested slots. Because newborn agents are assigned slots spread over a wide range (up to `slot_scale` times the population size), this could mean generating up to 5x more random numbers than were actually needed. The default CRN path now generates only the number of values needed, via a counter-based hash (`ss.distributions.hash_uniforms()`, a Numba-compiled [splitmix64](https://rosettacode.org/wiki/Pseudo-random_numbers/Splitmix64) algorithm keyed by the distribution seed and draw index) that maps each slot directly to a uniform value. This preserves CRN reproducibility while removing the redundant draws. - Added a new `crn` option (`ss.options.crn`, default `True`). Setting `ss.options.crn = False` skips the slot machinery entirely. This is not CRN-safe across scenarios, but is statistically valid and considerably faster; it is useful when reproducibility across counterfactual scenarios is not required. - Sped up `ss.multi_random` (used for pairwise transmission probabilities) by including a dedicated fast path for the common two-distribution case that avoids constructing a typed list on each call. - *Migration*: the `single_rng` option has been removed. Its purpose was to demonstrate the "single centralized RNG" behavior of other agent-based modeling frameworks; use `ss.options.crn = False` instead, which is both faster and demonstrates the same stochastic-branching problem. (Note, however, that it does *not* use a single RNG; this option is no longer available.) - *Regression*: because the default CRN mechanism changed, the exact random values produced for agent-indexed draws are different from previous versions. Results remain statistically equivalent and CRN reproducibility is preserved, but stored regression baselines will need to be regenerated. ### RandomNet and HouseholdNet - *Regression*: `ss.RandomNet` is now a faster (~1.8x), approximate network in which the target end of each edge is drawn independently. The per-agent degree is therefore only approximately Poisson rather than exact. The previous exact behavior is available as the new `ss.RandomExactNet`; switch to it if you rely on the exact degree distribution. - Edge generation for `ss.RandomExact` networks (what were previously called `ss.Random`) now uses an in-place Fisher-Yates shuffle (`ss.networks.fisher_yates_shuffle()`) instead of `np.random.permutation`, about 1.3x faster. - Networks whose edges last a single timestep (`dur=0`, e.g. `ss.RandomNet`) now regenerate edges via a zero-copy fast path that avoids concatenating onto the previous edge list each step. - `ss.library.HouseholdNet` initialization is now fully vectorized across household assignment, edge construction, and female-head-of-household selection, for a performance improvement of 10-20x for large populations. - `ss.library.HouseholdNet.add_births()` was also vectorized, for a performance improvement of ~1.3x in high-turnover simulations. ### Pregnancy - The `ss.Pregnancy` conception logic was optimized, for a ~20% performance improvement. - *Regression*: the default `slot_scale` for `ss.Pregnancy` was increased from 5 to 100. Newborns are assigned a random slot in the range `[n_agents, slot_scale·n_agents]`; a larger range reduces the chance that two newborns share a slot (and therefore make identical random draws). Previously a larger `slot_scale` also meant proportionally more random draws, but with the new hash-based CRN the number of draws no longer depends on the slot range, so the default was raised to reduce these collision artifacts at no performance cost. This changes results for models with pregnancy. ### Other performance optimizations - The disease transmission step now uses a Numba-compiled kernel (`_nb_transmit`) that identifies transmitting edges in a single branchless pass and returns its result as a zero-copy view, about 1.4x faster at typical transmission densities. Effective transmissibility and susceptibility are computed directly on the raw state arrays, avoiding intermediate `Arr` wrapper allocations. - Extracting the UIDs of a boolean state (`Arr.true()`, `Arr.uids`, and boolean filtering) now uses a branchless Numba compaction kernel above a threshold number of elements. - The `numba_indexing` threshold (the array size above which Numba is used instead of NumPy for indexing) was lowered from 5000 to 2000, since Numba wins for both gather and compaction above ~2000 elements. ### Other changes - `starsim.library` now exports its contents at the top level, so classes can be accessed either via their submodule (e.g. `ss.library.networks.HouseholdNet`, `ss.library.diseases.Cholera`) or directly (e.g. `ss.library.HouseholdNet`, `ss.library.Cholera`), like core Starsim. - Added `tests/benchmark_large.py` and associated benchmark data for tracking performance on larger simulations. ### Regression information - The default random number values for agent-indexed draws have changed (see "Common random numbers" above); results are statistically equivalent but not bit-for-bit identical, and baselines should be regenerated. - `ss.RandomNet` is now approximate; use `ss.RandomExactNet` for the previous exact-degree behavior. - The `single_rng` option has been removed; use `ss.options.crn = False` instead. - The default `slot_scale` for `ss.Pregnancy` was increased from 5 to 100, which changes results for models with pregnancy. - *GitHub info*: PR [1378](https://github.com/starsimhub/starsim/pull/1378) ## Version 3.4.0 (2026-06-21) This release introduces the **Starsim library** (`starsim.library`), which absorbs the former standalone `starsim_examples` package. This release also adds memory, reproducibility, and usability improvements along with numerous bugfixes. ### The Starsim library - Example and reference modules that previously lived in the separate `starsim_examples` package are now bundled with Starsim in `starsim.library`, imported as `import starsim.library as ssl`. (or just used as `ss.library`). Like core Starsim, the library exports everything at the top level: - Diseases: `ss.library.Cholera`, `ss.library.Ebola`, `ss.library.HIV`, `ss.library.Measles` - Maternal, neonatal, and child health: `ss.library.FetalHealth`, `ss.library.MaternalInfections`, `ss.library.NeonatalSepsis` - Networks: `ss.library.HouseholdNet`, `ss.library.SpatialNet`, and the theoretical/abstract networks - *Migration*: `ss.FetalHealth` and `ss.HouseholdNet` are no longer available at the top level. Instead of `ss.FetalHealth()` and `ss.HouseholdNet()`, use `ss.library.FetalHealth()` and `ss.library.HouseholdNet()` (or `ssl.FetalHealth()` and `ssl.HouseholdNet()` with `import starsim.library as ssl`). - *Migration*: `ss.MaternalNet` has been removed. It was a thin alias for the prenatal network; replace `ss.MaternalNet(...)` with `ss.PrenatalNet(...)`. - *Migration*: the `gonorrhea` and `syphilis` example diseases have been removed from the package (they are not part of the new library). Full, maintained STI models are available in [STIsim](https://github.com/starsimhub/stisim). Replace `ss.Gonorrhea()` with `import stisim as sti; sti.Gonorrhea()` and the same for syphilis. - Standalone example scripts ([LASER](https://laser.idmod.org/) comparisons, language translations, network embedding) have moved out of the package into `docs/examples`. ### Memory and reproducibility - `ss.shrink()` (previously `ss.utils.shrink()`) is now available at the top level. Calling `sim.run(shrink=True)` (the new default) performs a lightweight shrink that clears the circular `Sim` references that otherwise block garbage collection. A full `sim.shrink()` additionally clears `sim.people`, taking a typical sim from ~MB to ~KB. - Shrinking was refactored to be more robust, including for distributions and mixing pools, with clearer error messages when a module fails to shrink below its expected size. - Distribution run caches are now cleared automatically after each `sim.run()`. - *Regression*: distributions now count the call and jump/reset their random state even when sampling zero agents, so common-random-number (CRN) behavior no longer depends on whether any random numbers were actually drawn. This is more correct, but may produce numerically different results in models where a distribution is sometimes sampled with an empty set of agents. ### Bugfixes and improvements - Whereas before only interventions and analyzers could be provided as pure functions, now any module can; the function becomes the `step()` method, with the sim being the argument to the function. See [Modules.from_func](https://docs.starsim.org/api/modules.html#starsim.modules.Module.from_func) for details. - Fixed the `n_female` result not being calculated. - `dist.set()` now raises a clear error when given a parameter name that is not valid for that distribution, rather than silently ignoring it. - Improved the error message shown when a module overrides misses a required `super()` call. - Fixed `ss.MultiSim` not respecting `n_runs` in some cases. - Fixed `sim.loop.plot()`, and added and tested `sim.loop.plot_step_order()` for visualizing multi-timestep execution order. - Fixed `MultiSim.plot('single_key')` raising `AttributeError: 'Axes' object has no attribute 'flatten'` on a reduced multisim (i.e. after `reduce()`, `mean()`, or `median()`) when plotting a single result key. - `ss.years()` now accepts dates and date strings (and iterables of them), converting them to decimal years (e.g. `ss.years('2020-07-01')`). - Made `ss.parse_age_range()` more robust when parsing `'X to Y'`-style ranges. - Fixed the string representation of an uninitialized `ss.People` object. - Pinned `numpy>2`. - Applied an engineering uplift across the codebase: added and corrected docstrings, normalized line endings, improved error messages, and fixed assorted minor issues. ### Documentation - Added a "getting started" guide and reorganized the API reference. - Clarified the documentation on inclusive vs. exclusive time. - Added new user-guide and tutorial pages, and updated guidance on common modeling gotchas (transmission `beta` as a probability, getting agent UIDs from states, custom-module conventions, etc.). - *GitHub info*: PR [1344](https://github.com/starsimhub/starsim/pull/1344) ## Version 3.3.4 (2026-05-21) - Fixed `ss.CampaignDelivery` failing to initialize on date-based timelines: `init_pre()` now compares against the float `sim.t.yearvec` instead of the date-typed `sim.timevec`, which caused `sc.findnearest` to crash because subtracting `ss.date` objects produces `datedur`. - Fixed `ss.CampaignDelivery` missing a `coverage_dist` placeholder in `__init__`, which caused `AttributeError` on the first delivery. This affected `ss.campaign_screening`, `ss.campaign_triage`, and `ss.campaign_vx`. - Fixed `ss.RoutineDelivery` off-by-one when `dt = 1`: `adj_factor` is now `0` (was `1`) for `dt >= 1`, so the intervention no longer runs for one extra year past `end_year`. The previous behavior also caused an `IndexError` when a per-year `prob` array was supplied. - Aligned `RoutineDelivery.yearvec` with `self.timepoints` by slicing `sim.t.yearvec` directly, ensuring `prob` from `sc.smoothinterp` always matches the timepoint length. - **Regression**: `ss.routine_screening`, `ss.routine_triage`, and `ss.routine_vx` with `dt = 1` will now stop one year earlier than before (at `end_year` rather than `end_year + 1`). - Added test coverage for `RoutineDelivery` and `CampaignDelivery` in `tests/test_interventions.py`. - *GitHub info*: PR [1325](https://github.com/starsimhub/starsim/pull/1325) ## Version 3.3.3 (2026-04-15) - Added `ss.parse_age_range()` and `ss.apply_age_range()` to parse age range specifications like `'[5, 10), [10, 15)'`. - Optimized `ss.RandomNet()` performance. - Fixed `ss.HouseholdNet.add_births()` to assign household IDs exactly once at delivery, rather than using an age-window heuristic tied to `update_freq`. Newborns are now identified by having a parent, age >= 0, and no household ID yet assigned. Moved HouseholdNet's `add_births()` call before the `update_freq` gate so it runs every timestep, ensuring correct behavior with any update frequency or when the network timestep exceeds gestation. - Inspired by [Starsim.jl](http://epirecip.es/Starsim.jl/dev/), added examples of Starsim models translated into pure Python, Numba, Jax, Julia, and Rust in `starsim_examples/translations`. (Thanks to [Simon Frost](https://github.com/sdwfrost) for the Julia conversion.) - Added code coverage, linting, and performance benchmarks to CI/CD. - *GitHub info*: PR [1308](https://github.com/starsimhub/starsim/pull/1308) ## Version 3.3.2 (2026-04-04) - Added a `copy_sim` argument to `ss.MultiSim.run()` to handle large parallel runs of a single sim (e.g. `ss.MultiSim(sim, n_runs=100)`), where multiprocess pickling isn't guaranteed to implicitly copy the sim. - Removed (unused) `_locked` attribute from `ss.TimePar`. - Converted tutorials and user guide from Jupyter notebook (JSON) to Quarto notebook (Markdown) format. - Added GitHub Actions workflows for test coverage, docs build, and linting. - Changed from implicit (`*`) to explicit imports in `__init__.py`, and removed `__all__` from individual modules. - *GitHub info*: PR [1293](https://github.com/starsimhub/starsim/pull/1293) ## Version 3.3.1 (2026-04-02) - Added `uids.concatenate()`, which unifies `uids.concat()` and `uids.cat()` into a single method that works as both an instance method (`x.concatenate(y)`) and an unbound class method (`ss.uids.concatenate(x, y)` or `ss.uids.concatenate([x, y])`). `None` values are silently filtered. - `uids.concat()` and `uids.cat()` are deprecated but provide a `DeprecationWarning` for backwards compatibility, to be removed in a future release. - Added `+` and `+=` operators to `ss.uids` for concatenation (e.g. `a + b` is equivalent to `a.concatenate(b)`). Note that `+` preserves duplicates, unlike `|` which deduplicates. - Comparison operations on `ss.uids` (e.g. `a == b`) now correctly return a plain `np.ndarray` instead of a `uids` instance. - Scalar reductions on `ss.uids` (e.g. `x.max()`, `x.min()`) now return a plain Python `int`, consistent with element indexing (`x[0]`). - Numeric aggregation operations that are not meaningful for UID arrays (`sum()`, `mean()`, `std()`, `var()`, `prod()`, `cumsum()`, `cumprod()`) now raise `TypeError`. - Added `ndim` to `ss.TimePar`, fixing a bug when trying to assign scalar TimePars (e.g. `ss.years(1990)`) to `pandas` dataframes. - *GitHub info*: PR [1279](https://github.com/starsimhub/starsim/pull/1279) ## Version 3.3.0 (2026-03-27) - Made scalar time parameters behave more consistently by removing the ability to index them if they are scalars, and also having `np.iterable()` return `False` for such parameters - **Backwards-compatibility notes:** This change may cause simulation results to be numerically different, as it will cause distributions to use a more efficient sampling method for affected scalar parameters (in such cases, there is likely to be a performance increase) ### Pregnancy loss and neonatal death classification - Added background pregnancy loss via `p_loss` parameter in `Pregnancy`. Per-timestep hazard means early losses are naturally more common. - Prenatal deaths are now classified by gestational age: miscarriage (<20w) vs stillbirth (>=20w), controlled by `loss_threshold` parameter. - Preterm (<37w) and very preterm (<32w) births are now classified and tracked, with configurable thresholds via `preterm_threshold` and `very_preterm_threshold`. - Added passive neonatal death detection: deaths of agents aged 0-28 days are classified as neonatal deaths. - New results: `miscarriages`, `stillbirths`, `nnds`, `n_preterm`, `n_very_preterm`, `preterm_rate`. - New states on newborns: `preterm`, `very_preterm`, `neonatal_death`. - Removed unused `_p_miscarriage` and `_p_stillbirth` placeholder distributions. - *Migration*: `n_pregnancies_this_step` and `n_births_this_step` counters removed; use results directly (e.g. `new_pregnancies`). ### Generic congenital outcome framework - Added `set_congenital()` to the base `Infection` class. Diseases opt in by defining `birth_outcome_keys` and `birth_outcomes` in pars. - Added `step_congenital()` helper to process scheduled congenital events at delivery time. - Added `_assign_congenital_outcomes()` override point for state- or GA-dependent outcome probabilities. - Death outcomes (`miscarriage`, `nnd`, `stillborn`) trigger `request_death`; non-lethal outcomes set a bool state. ### FetalHealth improvements - Simplified `apply_timing_shift`, `apply_growth_restriction`, `reverse_timing_shift`, `reverse_growth_restriction` — removed redundant pregnancy filtering and type coercions. - `n_exposures` state tracks disease exposures during pregnancy (connectors increment directly via `fh.n_exposures[uids] += 1`). - Added `interp_fn` parameter for customizing birth weight interpolation. - Negative growth penalties (growth boost, e.g. GDM macrosomia) are now additive rather than using diminishing returns. ### MNCH examples - Added `starsim_examples/mnch/` with reusable maternal/newborn/child health examples: - `CongenitalDisease`: simple SIR with congenital outcomes via the generic framework. - `NeonatalSepsis`: SIR that infects/kills newborns, useful for testing NND detection. - `fetal_infection`: connector that damages fetal health when mothers are infected. - `treat_pregnant`: intervention that treats infected pregnant women. - *GitHub info*: PR [1269](https://github.com/starsimhub/starsim/pull/1269) ## Version 3.2.2 (2026-03-11) - Added `people.born`, which tracks agents that are alive (`sim.people.alive`) and born (`sim.people.age >= 0`). - Fixed a bug that prevented `ss.Arr` objects from being modified in-place by functions involving other `ss.Arr` objects. - Fixed an initialization bug in `ss.HouseholdNet`. - *GitHub info*: PR [1227](https://github.com/starsimhub/starsim/pull/1227) ## Version 3.2.1 (2026-03-06) - Fixed a bug introduced in v3.2.0 that changed how modules were parsed. Also introduced a `custom` argument to `ss.Sim()`, to specify custom modules. - Moved `HouseholdNet` from `starsim_examples` into `starsim` and merged it with `EvolvingHouseholdNet`. By default, the network is dynamic. To use the static network, use `ss.HouseholdNet(dynamic=False)`. - The calibration database is now created by default in a temporary folder, to avoid collisions with multiple calibrations running simultaneously; to restore previous behavior, set either `study_name` or `db_name`, e.g. `db_name='starsim.db'`. - *GitHub info*: PR [1200](https://github.com/starsimhub/starsim/pull/1200) ## Version 3.2.0 (2026-03-03) ### Sim module changes - Changed `sim.modules` to `sim.custom` for storage of custom modules that do not match standard types (e.g., diseases, interventions). Passing modules in via a `modules` argument is still supported, such modules will automatically be moved to the appropriate attribute upon initialization. - Updated `sim.modules` to now return an iterator over all modules. - Removed `sim.module_list` which can be replaced with `list(sim.modules)` if an exact replacement is required. In many cases, `sim.module_list` was used only to iterate over the modules, in which case it can be directly replaced with `sim.modules`. - Removed `sim.module_dict` which can be replaced with `sim.get_modules(as_dict=True)` if required. `sim.module_dict` was removed as an attribute to help avoid ambiguity as to where modules are stored in the `Sim`. In cases where the `module_dict` was used to retrieve modules by name, `sim.get_module` can be used instead. - Added `sim.get_modules()` to search for modules, and `sim.get_module()` to retrieve exactly one module. Also added `ndict.get()` (e.g. `sim.diseases.get()`) with similar functionality. ### Other changes - Fixed a bug in `ss.Timeline()` that led to array length mismatches for certain combination of `start`, `stop`, and `dt`. - Changed the default start to be the year 2000 regardless of other input (previously, if e.g. `dt='month'`, the start would change to being zero-based); now, only if `start=0` is a timepoint-based (non-calendar-based) timeline assumed. - Fixed a bug introduced in v3.1.0 that prevented `People.filter()` from working. - Provided methods to annualize results for plotting (e.g. `sim.results.sir.prevalence.annualize()`). - Added `sim.people.keys()` as an alias for `sim.people.states.keys()`, and `sim.dists.keys()` as an alias for `sim.dists.dists.keys()`. - Added option for `ss.MultiSim` to be constructed from other MultiSims and from tuples of sims (previously, only lists of sims were supported). - *GitHub info*: PR [1186](https://github.com/starsimhub/starsim/pull/1186) ## Version 3.1.1 (2026-02-18) - Added `sim.set_diagnostics()` (and the associated `ss.Diagnostics()` class), which allows for tracking of every agent in every state and every random number generated by the sim. - Fixed bug with pregnancy burn-in and NumPy 2.4. - Fixed bug with pregnancy results calculation for sims with duration less than 1 year. - *GitHub info*: PR [1145](https://github.com/starsimhub/starsim/pull/1145) ## Version 3.1.0 (2026-02-12) - *Migration information*: See `docs/migration/v3.0_v3.1.md` for a detailed, LLM-friendly migration guide. - Added new functionality to the `Pregnancy` module: - Renamed `make_p_fertility()` -> `make_p_conceive()` - Added variable durations of pregnancies and tracking of pre-term birth outcomes - Added variable breastfeeding durations - Refactored the logic of how pregnancies and births are handled with new helper methods for better extensibility - Added results on age-specific fertility rates and total fertility rates - Previously `People.(module_name).(state_name)` could be used to access module states (e.g., `people.sir.infected`). However, this mechanism fails to pass on property attributes that are dynamically computed (e.g., `SIR.infectious`). Now `People.(module_name)` simply contains a reference to the module, so all attributes can be accessed. - Add support for operating on `ss.BaseArr` with time parameters (e.g., multiplying a state by a duration) - Add support for creating an `ss.dur` from an `ss.BaseArr` - Changed date plotting to convert `ss.date` and `ss.DateArray` values to years internally. This facilitates adding extra data to plots when the data is in years. - Added `ss.parse_age_range` utility function to standardize formats for age ranges in data/input files. - Implemented `__bool__` for `ss.bernoulli` which returns `False` if there is no possibility of returning any `True` values. - Added automatic result creation for `ss.BoolState` instances in `People`, matching the behavior already present in modules. `People` now has `auto_state_list` property and `init_results()` method. - Implemented operators for `ss.BoolArr` and `ss.uids` where the latter is treated as a `BoolArr`. This makes operations like `people.sir.infected & people.female` and `people.sir.infected & people.female.uids` equivalent. - Implemented in-place operators for `ss.BoolArr` and `ss.uids`. The former are true in-place operations that reuse existing memory and preserve references, and thus they can be safely used with `BoolState` instances in modules directly. - Fixed/added operators `+` and `-` for `ss.DateArray` when operating on `ss.date` and `ss.dur` instances - Fixed bug when multiplying an `ss.prob` by an array of durations - Renamed arguments to `MaternalNet.add_pairs()` from `mother_inds` to `mother_uids` and `unborn_inds` to `unborn_uids` to clarify that these inputs should be agent UIDs. - Removed `dur` argument to `MaternalNet.add_pairs()` as the edge lifespan is now managed by the `Pregnancy` module with edges being removed upon delivery. - Removed `Network.prenatal` and `Network.postnatal` attributes as these are now tracked by type (`ss.PrenatalNet` and `ss.PostnatalNet`) - *GitHub info*: PR [1065](https://github.com/starsimhub/starsim/pull/1065) ## Version 3.0.6 (2026-01-28) - Fixed bug in `sim.loop.plot_cpu()` when using pandas v3 - *GitHub info*: PR [1118](https://github.com/starsimhub/starsim/pull/1118) ## Version 3.0.5 (2025-10-22). - Fixed bugs with products being added/stepped multiple times. - Fixed bug that prevented `Result.resample(output_form='series')` from being used. - *GitHub info*: PR [1095](https://github.com/starsimhub/starsim/pull/1095) ## Version 3.0.4 (2025-10-22) - Added additional calibration examples for workflows to re-identify known parameters. - Removed `p_death` from the parameters for the `ss.Deaths` module, as `Deaths.make_p_death()` always overrides this value and is therefore the means by which the death probability should be set or changed. - *GitHub info*: PR [1055](https://github.com/starsimhub/starsim/pull/1055) ## Version 3.0.3 (2025-10-07) - Swapped order of `age` and `female` states in People to allow `female` dist to use `age` state during population initialization. - *GitHub info*: PR [1051](https://github.com/starsimhub/starsim/pull/1051) ## Version 3.0.2 (2025-08-25) - Additional minor updates following the v3 release. - Cleans up some of the logic for converting rates to probabilities in the demographic modules - Adds a `request_removal` method to People, which can be used to remove people from the population for reasons other than death (e.g. migration) - Fixes a bug that prevented `ss.prob` from accepting arrays - Adds a `year` property to Timelines, so `sim.t.now('year')` can be rewritten as `sim.t.year` - *GitHub info*: PR [1032](https://github.com/starsimhub/starsim/pull/1032) ## Version 3.0.1 (2025-08-04) - Minor updates following the v3 release. - The logic for initializing `ss.Timeline` has been changed. Now, `start` and `stop` will not be converted to dates unless at least one argument is a date (e.g. `start=ss.years(2000)` will remain `ss.years()`). `ss.Timeline` is also now much more robust about handling different types of input, and choosing sensible defaults for missing inputs (e.g. with `stop=ss.days(20)`, the new default is `start=ss.days(0)`). - Subtracting dates now yields a `relativedelta`, e.g. `ss.date(2025) - ss.date(2015)` now gives `ss.datedur(years=10)` (previously it converted to days). - `ss.DateArray` now has a `unit` attribute, allowing more reliable conversion to years. - Timepars now have a `disp()` method. - *GitHub info*: PR [1029](https://github.com/starsimhub/starsim/pull/1029) ## Version 3.0.0 (2025-08-02) ### Summary Starsim v3 includes a reimplementation of how time is handled, an extensive new suite of debugging tools, and smaller changes, including an extensive user guide in addition to the tutorials. Please also see `docs/migration_v2v3` for a detailed (and LLM-friendly) migration guide for porting existing Starsim code over to the new version. If a point below says "See the migration guide", that indicates that additional information (and a conversion script where possible) is provided in that guide. Otherwise, it is (generally) a non-breaking change. ### Time Time is now based on precise datetime stamps (specifically, `pd.Timestamp`). In Starsim v3.0, the supported time units are days, weeks, months, and years. Days or years are always handled exactly and weeks and months are defined in terms of them, i.e. a week is exactly 7 days and a month is exactly 1/12th of a year. The following changes have been made to the API: - While durations work similarly as in Starsim v2, rates work differently. The base class, `ss.Rate`, cannot be used directly. Instead, you must use one of the three derived classes. `ss.freq()` is the closest to `ss.rate()` in Starsim v2, and is a simple inverse of `ss.dur()`. `ss.per()` is the equivalent of `ss.rate_prob()` in Starsim v2, but is the primary probability-based rate that should be used (e.g. for beta, birth rates, death rates, etc.). Finally, `ss.prob()` is the equivalent to `ss.time_prob()` in Starsim v2, but whereas `time_prob` was preferred in v2, `per` (equivalent to `rate_prob` in v2) is preferred in v3. - In addition to these base classes, each of them is available for each time unit. For durations, singletons are available (`ss.day`), as well as e.g. `ss.days()`, `ss.years()` etc. For rates, `ss.peryear()` is derived from `ss.per`, while `ss.probperyear()` is the equivalent for `ss.prob` and `ss.freqperyear()` is the equivalent for `ss.freq`. `ss.prob` can also be unitless (`ss.per` and `ss.freq` cannot be). - `ss.beta()` has been removed; use `ss.prob()` instead for a literal equivalent, although in most cases `ss.per()` is preferable, e.g. `ss.peryear()`. - `ss.rate()` has been removed; use `ss.freq()` instead for a literal equivalent, although in most cases `ss.per()` is preferable, e.g. `ss.peryear()`. - `unit` has been removed as an argument; use `dt` instead, e.g. `ss.Sim(dt=1, unit='years')` is now `ss.Sim(dt=ss.year)` (or `ss.Sim(dt='years')` or `ss.Sim(dt=ss.years(1))`). - Although `ss.dur()` still exists in Starsim v3.0, it is preferable to use named classes instead, e.g. `ss.years(3)` instead of `ss.dur(3, 'years')`. - `ss.Time()` is now called `ss.Timeline()` and its internal calculations are handled differently. - `ss.time_ratio()` has been removed; time unit ratio calculations (e.g. months to years) are now handled internally by timepars. - `t.abstvec` has been removed; in most cases, `t.tvec` should be used instead (although `t.yearvec`, `t.datevec`, or `t.timevec` may be preferable in some cases). - Multiplication by `dt` no longer happens automatically; call `to_prob()` to convert from a timepar to a unitless probability (or `to_events()` to convert to a number of events instead). For full details, see the migration guide. ### Distributions - Distributions now have a `scale_type` attribute, which determines how they scale with time: some distributions, like `ss.normal()`, can be scaled either before or after random numbers are drawn; others, like `ss.poisson()`, can only be scaled before. See `ss.scale_types` for details. - Distributions that can be scaled post-draw now take an `unit` argument, e.g. `ss.normal(mean=5, std=2, unit=ss.years(1))`. This is equivalent to `ss.years(ss.normal(mean=5, std=2))` or `ss.normal(mean=ss.years(5), std=ss.years(2))`. Note that not all distributions can be scaled this way (you will get an error if you try to scale a non-scalable distribution.) - Distributions now have a random-number-safe `randround()` method - There are two new distributions, `ss.beta_dist()` and `ss.beta_mean()` (not called `ss.beta()` to distinguish from the beta transmissibility parameter). ### Sim and People changes - Sims now take an optional `modules` argument. These are run first, before anything else in the integration loop. If you want, you can supply everything directly as a module, e.g. `ss.Sim(modules=[ss.Births(), ss.RandomNet(), ss.SIR()])` is equivalent to `ss.Sim(demographics=ss.Births(), networks=ss.RandomNet(), diseases=ss.SIR())`. You can also add your own custom modules, not based on an existing Starsim module type, and specify the order they are called in. - `sim.modules` has been renamed `sim.module_list`. - `ss.People` has a new `filter()` method, which lets you chain operations, e.g.: `ppl = sim.people; f = ppl.filter(ppl.female & (ppl.age>5) & ~ppl.sir.infected)` - `ss.People` has new methods `plot()` (all variables) and `plot_ages()` (age pyramid by sex). ### Module changes #### Base module updates - Module methods have a new decorator, `@ss.required()`, which flag that it is an error for the method not to be called. This is used to prevent the user from accidentally forgetting to call `super().method()`. - Modules can now be used like dictionaries for accessing user-defined states, e.g. `module['my_custom_state']` is an alias for `module.my_custom_state`. - `module.states` has been renamed `module.state_list`. `module.statesdict` has been renamed `module.state_dict`. There is also a `module.auto_state_list` property, referring specifically to `ss.BoolState` attributes. - Built-in modules now have function signatures that look like this example for `ss.Births()`: `def __init__(self, pars=None, rel_death=_, death_rate=_, rate_units=_, **kwargs):`. Although `_` is simply `None`, this notation is short-hand for indicating that (a) the named arguments are the available parameters for the module, (b) their actual values are set by the `define_pars()` method. - Modules now have a much briefer `__repr__`, typically one line. The full module information can still be obtained via `module.disp()`. #### Changes to networks - `key_dict` has been removed from `ss.Network()`; modify the `network.meta` dictionary directly instead, e.g. `ss.DynamicNetwork` has `self.meta.dur = ss_float` in its `__init__()` method, while `ss.SexualNetwork` has `self.meta.acts = ss_int`. - `ss.Network()` now has `plot()` and `to_edgelist()` methods. - `ss.RandomNet()` now only adds "missing" edges, fixing a bug in which the longer the edge duration, the more edges the network had. - There is a new network, `ss.RandomSafe()`, whch is similar to `ss.Random()` but random-number safe (at the cost of being slightly slower). - For `ss.MixingPool`, the argument `contacts` has been renamed `n_contacts`. #### Changes to other modules - There is a new built-in analyzer `ss.dynamics_by_age()`. - There is a new built-in connector `ss.seasonality()`. - `ss.Births()` is now random-number safe. - `ss.Deaths()` now has a default rate of 10 per 1000 people per year (instead of 20). Births is still 20. This means that with `demographics=True`, the population grows at roughly the correct global average rate. - `ss.sir_vaccine()` has been renamed `ss.simple_vx()`. ### Debugging tools Starsim v3 comes with a new set of tools for debugging (in `debugtools.py`): both understanding simulations and what is happening at different points in time, and understanding and profiling code performance to find possible improvements to efficiency. These include: - `sim.profile()`: profiles a run of a sim, showing module by module (and optionally line by line) where most of the time is being spent. See `ss.Profile()` for details. - `ss.Debugger()`: steps through one or more simulations, and raises an exception when a particular condition is met (e.g., if the results from two sims start to diverge). - `ss.check_requires():` use this function to check if a sim contains a required module (e.g., an HIV-syphilis connector would require both HIV and syphilis modules to be present in the sim). - `ss.check_version()`: for checking which version of Starsim is installed. #### Loop debugging tools - `ss.Loop` now has an `insert()` method that lets you manually insert functions at an arbitrary point in the simulation loop. - `ss.Loop` now has a `plot_step_order()` method that lets you validate that the steps are being called in the expected order. #### Profiling tests - In the tests folder, there is a `benchmark_tests.py` script for benchmarking the performance of the tests. Each test is also now timed (via the `@sc.timer` decorator). - There are also various profiling scripts, e.g. `profile_sim.py`. - Baseline and performance benchmark files have been converted from JSON to YAML. #### Mock functions Starsim components are intended to work together as part of an `ss.Sim` object, which handles initialization and coordination across modules, distributions, time parameters, etc. But sometimes, it's useful to build a very simple example to test a component in isolation. Starsim v3 comes with "mock" components, which allow you to work with e.g. a module without incorporating it into a full sim. These have the essential structure of the real thing (e.g., `sim.t.dt`), but without the complexity of the full object. These mock objects are: - `ss.mock_sim()`: generates a mock sim; useful for testing modules - `ss.mock_module()`: generates a mock module; useful for testing distributions - `ss.mock_people()`: generates a mock `ss.People` object; used by `ss.mock_sim()` - `ss.mock_time()`: generates a mock `ss.Timeline` object; used by `ss.mock_sim()` and `ss.mock_module()` Distributions now have a `mock()` method, that allows you to immediately start using them to generate random numbers, e.g. `ss.normal(5,2).mock().rvs(10)`. ### Other changes #### File structure - All diseases except for SIR, SIS, and NCD have been moved to a separate `starsim_examples` folder. This is installed together with Starsim, but must be imported separately, e.g. `import starsim_examples as sse; hiv = sse.HIV()` instead of `import starsim as ss; hiv = ss.HIV()`. See the migration guide for details. - `ss.register_modules()` lets you register external modules as Starsim modules, to allow calling by string; e.g. `ss.register_modules(sse)` (from above) will let you do `ss.Sim(diseases='hiv')`, since `sse.HIV` is registered as a known Starsim module. - Files have been reorganized: - `analyzers.py` and `connectors.py` have been created (split out from `modules.py`); - `calibration.py` and `calib_components.py` have been combined into `calibration.py`; - `disease.py` has been renamed `diseases.py` and `diseases/sir.py` and `diseases/ncd.py` have been incorporated into it; - `timeline.py` has been split out from `time.py`. #### Plotting - Plotting defaults have been updated; you can use these defaults via `with ``ss.style()`. - Result plotting has been improved, in terms of module labels and correct x-axis labels. - `ss.MultiSim()` plotting has been improved to display legends correctly. - Plotting arguments are now handled by `ss.plot_args()`, which will parse different arguments among figure, plot, scatter, and other functions, e.g. `ss.plot_args(dpi=150, linewidth=3)` will set the figure DPI and the plot line width. - Sim results are now automatically skipped during plotting if they were never updated (e.g., `n_deaths` if there were no deaths). #### Array and results updates - Array indexing has been reimplemented, using Numba instead of NumPy for large operations; this should be about 30% faster. An unnecessary array copy operation was also removed, for a further ~50% efficiency gain. (Note that although array indexing is now much faster, it was not typically the slowest step, so "real world" performance gains are closer to 10-20%.) - There is a new class, `ss.IntArr`, although in most cases `ss.FloatArr` is still preferred due to better handling of NaNs. - `ss.State` has been renamed to `ss.BoolState`. See the migration guide for details. - `ss.options._centralized` has been renamed `ss.options.single_rng`. Although there is a very small performance benefit, use of this option is not recommended. - `ss.set_seed()` has been removed; the seed should be set automatically by the distributions. If you want to set the seed for a custom (not random-number-safe) non-distribution random number, call `np.random.seed()` manually. - Distributions and modules now define their own `shrink()` methods (for saving small files). Note that if you define a custom module that stores a lot of data, you may want to define your own `shrink()` method to remove this data. - `ss.Result()` objects now have `disp()` and `to_str()` methods, and they can also be treated as dicts (e.g. `res['low']` instead of `res.low`). - You can now create an `ss.MultiSim` comprised of sims of different lengths; however, the sims will be truncated to be of the same length, and they are _not_ temporally aligned. In general, it is still inadvisable to use `msim.reduce()` with sims of different lengths. #### New options Starsim v3 comes with several new options, set via `ss.options`: - `ss.options.check_method_calls`: whether to check that required module methods are called (default `True`) - `ss.options.install_fonts`: whether to install custom fonts for plotting (default `True`) - `ss.options.numba_indexing`: threshold at which to switch from NumPy to Numba indexing (default 5000 indices) - `ss.options.style`: plotting style to use; options are "starsim", "fancy", "simple", or any Matplotlib style - `ss.options.warn_convert`: whether to warn when automatically converting time parameters `ss.options` also now has a `help` method that will print detailed help on a given option or all options, e.g. `ss.options.help(detailed=True)`. #### Migration summary - An agentic LLM such as Cursor or Claude Code should be able to perform most of the migrations from v2 to v3 automatically. Tell it to read `llms.txt`, then `docs/migration/v2_v3/README.md`. However, there will still be a few things to manually double check, especially around time conversions (such as whether you want `ss.beta()` ported literally to `ss.probperyear()`, or "upgraded" to `ss.peryear()`). - *GitHub info*: PR [927](https://github.com/starsimhub/starsim/pull/927) ## Version 2.3.2 (2025-07-16) - Fix argument passing in `Infection.infect`. This will be the final Starsim v2.x release. - *GitHub info*: PR [1008](https://github.com/starsimhub/starsim/pull/1008) ## Version 2.3.1 (2025-02-25) - Updated `ss.Sim.shrink()` to remove additional objects, resulting in a smaller sim size. - `ss.Calibration.save_csv()` has been replaced by `ss.Calibration.to_df()` (to save to a CSV, use `ss.Calibration.to_df().to_csv()`. - `ss.Result.shape` has been renamed `ss.Result._shape`, so `ss.Result.shape` now correctly returns the actual size of the array. - Results by default convert all result keys to lowercase; use `keep_case=True` to turn off this behavior. - Fixed a bug with an [`ss.date`](`starsim.time.date`) object converting to a `pd.Timestamp` upon copy. - *GitHub info*: PR [865](https://github.com/starsimhub/starsim/pull/865) ## Version 2.3.0 (2025-02-14) - The calibration class has been completely redesigned. Calibration now relies on "components", which capture mismatch with a particular data type (e.g., new infections). The new approach also adds additional statistical rigor for calculating mismatches. - `ss.MixingPool` has been updated to be more modular, and behave more like `ss.Network`; in particular, `compute_transmission()` rather than `step()` is called to determine new infections. - `ss.Result` now has a `summarize_by` argument, which determines how a result should be summarized as a scalar (e.g., mean for a prevalence, sum for a count, last entry for a cumulative count). - Fixed a bug with time parameters incorrectly pulling the parent unit from the Sim, rather than the parent module. - *GitHub info*: PR [831](https://github.com/starsimhub/starsim/pull/831) ## Version 2.2.0 (2024-11-18) - Starsim is now available for R! See for details. - The `Calibration` class has been completely rewritten. See the calibration tutorial for more information. - A negative binomial distribution is now available as `ss.nbinom()`. - `ss.Births()` now uses a binomial draw of births per timestep, rather than the expected value. - Added `ss.load()` and `ss.save()` functions, and removed `ss.Sim.load()`. - *GitHub info*: PR [778](https://github.com/starsimhub/starsim/pull/778) ## Version 2.1.1 (2024-11-08) - Adds improved Jupyter support for plotting (to prevent plots from appearing twice); you can disable this by setting `ss.options.set(jupyter=False)`. - Adds `auto_plot` to `Result` objects, to indicate if it should appear in `sim.plot()` by default. - Adds `copy()` to the Sim and modules. - Networks now store their length on each timestep as a result. - Improves `sim.shrink()`, with typical size reductions of >99%. - Adds additional plotting options `show_module` (include the module name in the plot title), `show_label` (use the simulation label as the figure title), and `show_skipped` (shows results even if `auto_plot=False`). - *GitHub info*: PR [745](https://github.com/starsimhub/starsim/pull/745) ## Version 2.1.0 (2024-11-07) ### Summary - Time in simulations is now handled by an `ss.Time()` class, which unifies how time is represented between the `Sim` and each module. - In addition to networks, there is now a new way of implementing disease transmission via mixing pools. ### Time - Time handling now performed by the `ss.Time()` class. This has inputs similar to before (`start`, `stop`, `unit`, `dt`, with `dur` still available as a sim input). However, instead of the previous `timevec` and `abs_tvec` arrays, there are now multiple ways of representing time (including `datevec` and `yearvec`), regardless of what the inputs were. - Dates are now represented in a native format, `ss.date`, that is based on `pd.Timestamp`. ### Mixing pools - Adds a new approach to disease transmission called mixing pools. A mixing pool is a "mean field" coupling wherein susceptible agents are exposed to the average infectious agent. The user can create a single mixing pool using the `ss.MixingPool` class, or create many pools using `MixingPools`. Such mixing pools could be used to simulate contact matrices, for example as published by Prem et al. - There is a new `ss.Route` class, which is the base class for `ss.Network` and `ss.MixingPool`. ### Other changes - Demographic modules have been updated to fix various bugs around different time units. - The method for hashing distribution trace strings into seeds has changed, meaning that results will be stochastically different compared to Starsim v2.0. - Fixed a bug with how timepars were updated in parameters. - There is a new `ss.Base` class, which both `ss.Sim` and `ss.Module` inherit from. - Results now print as a single line rather than the full array. The latter is available as `result.disp()`. - `sim.to_df()` now works even if different modules have different numbers of timepoints. - The `timepars` module has been renamed to `time`. - In demographics modules, `units` has been renamed `rate_units`. - There are two new options, `ss.options.date_sep` and `ss.options.license`. The former sets the date separator (default `.`, e.g. `2024.04.0.4`), and the latter sets if the license prints when Starsim is imported. - *GitHub info*: PR [724](https://github.com/starsimhub/starsim/pull/724) ## Version 2.0.0 (2024-10-01) ### Summary Version 2.0 contains several major changes. These include: module-specific timesteps and time-aware parameters (including a day/year `unit` flag for modules, and `ss.dur()` and `ss.rate()` classes for parameters), and changes to module types and integration (e.g. renaming `update()` and `apply()` methods to `step()`;). ### Time-aware parameters and modules - Added `ss.dur()`, `ss.rate()`, and `ss.time_prob()` classes, for automatic handling of time units in simulations. There are also convenience classes `ss.days()`, `ss.years()`, `ss.perday()`, `ss.peryear()`, and `ss.beta()` for special cases of these. - `ss.dur()` and `ss.rate()`, along with modules and the sim itself, have a `unit` parameter which can be `'day'`, `'week'`, `'month'`, or `'year'` (default). Modules now also have their own timestep `dt`. Different units and timesteps can be mixed and matched. Time parameters have a `to()` method, e.g. `ss.dur(1, 'year').to('day')` will return `ss.dur(365, unit='day')`. - The `ss.Sim` parameter `n_years` has been renamed `dur`; `sim.yearvec` is now `sim.timevec`, which can have units of days (usually starting at 0), dates (e.g. `'2020-01-01'`), or years (e.g. `2020`). `sim.abs_tvec` is the translation of `sim.timevec` as a numeric array starting at 0, using the sim's units (usually `'day'` or `'year'`). For example, if `sim.timevec` is a list of daily dates from `'2022-01-01'` to `'2022-12-31'`, `sim.abs_tvec` will be `np.arange(365)`. - Each module also has its own `mod.timevec`; this can be different from the sim if it defines its own time unit and/or timestep. `mod.abs_tvec` always starts at 0 and always uses the sim's unit. - There is a new `Loop` class which handles the integration loop. You can view the integration plan via `sim.loop.to_df()` or `sim.loop.plot()`. You can see how long each part of the sim took with `sim.loop.plot_cpu()`. - There are more advanced debugging tools. You can run a single sim timestep with `sim.run_one_step()` (which in turn calls multiple functions), and you can run a single function from the integration loop with `sim.loop.run_one_step()`. ### Module changes - Functionality has been moved from `ss.Plugin` to `ss.Module`, and the former has been removed. - `ss.Connector` functionality has been moved to `ss.Module`. `ss.Module` objects can be placed anywhere in the list of modules (e.g., in demographics, networks, diseases, interventions), depending on when you want them to execute. However, `ss.Connector` objects are applied after `Disease.step_state()` and before `Network.step()`. - Many of the module methods have been renamed; in particular, all modules now have a `step()` method, which replaces `update()` (for demographics and networks), `apply()` (for interventions and analyzers), and `make_new_cases()` (for diseases). For both the sim and modules, `initialize()` has been renamed `init()`. - All modules are treated the same in the integration loop, except for diseases, which have `step_state()` and `step_die()` methods. - The Starsim module `states.py` has been moved to `arrays.py`, and `network.py` has been moved to `networks.py`. ### State and array changes - `ss.Arr`, `ss.TimePar`, and `ss.Result` all inherit from the new class `ss.BaseArr`, which provides functionality similar to a NumPy array, except all values are stored in `arr.values` (like a `pd.Series`). - Whereas before, computations on an `ss.Arr` usually returned a NumPy array, calculations now usually return the same type. To access the NumPy array, use `arr.values`. - There is a new `ss.State` class, which is a subtype of `ss.BoolArr`. Typically, `ss.State` is used for boolean disease states, such as `infected`, `susceptible`, etc., where you want to automatically generate results (e.g. `n_infected`). You can continue using `ss.BoolArr` for other agent attributes that you don't necessarily want to automatically generate results for, e.g. `ever_vaccinated`. ### Results changes - Results are now defined differently. They should be defined in `ss.Module.init_results()`, not `ss.Module.init_pre()`. They now take the module name, number of points, and time vector from the parent module. As a result, they are usually initialized via `ss.Module.define_results(res1, res2)` (as opposed to `mod.results += [res1, res2]` previously). `define_results()` automatically adds these properties from the parent module; they can still be defined explicitly if needed however. - Because results now store their own time information, they can be plotted in a self-contained way. Both `ss.Result` and `ss.Results` objects now have `plot()` and `to_df()` methods. ### Demographics changes - Fixed a bug in how results were defined for `ss.Births` and `ss.Deaths`. - The `ss.Pregnancy` module has been significantly rewritten, including: (1) Agents now have a `parent` which indicates the UID of the parent; (2) Women now track `child_uid`; (3) On neonatal death, the pregnancy state of the mother is corrected; (4) Pregnancy rates now adjusted for infecund rather than pregnant; (4) Pregnancy now has a burn-in, which defaults to `True`; (5) Pregnancy has a `p_neonatal_death` parameter to capture fetal and neonatal death if the mother dies. - Slots now has a minimum, default of 100, to account for small initial population sizes that grow dramatically over time. ### Computational changes - There have been several performance improvements. The default float type is now `np.float32`. Transmission is now handled by a specialized `Infection.compute_transmission()` method. Several additional functions now use Numba, including `fastmath=True`, which leverages Intel's short vector math library. - A new `ss.multi_random()` distribution class has been added, that allows random numbers to be generated by two (or more) agents. It largely replaces `ss.combine_rands()` and is 5-10x faster. - A new `ss.gamma()` distribution has also been added. - Distributions have a new `jump_dt` method that jumps by much more than a single state update. - `ss.parallel()` and `ss.MultiSim.run()` now modify simulations in place by default. Instead of `sims = ss.parallel(sim1, sim2).sims; sims[0].plot()`, you can now simply do `ss.parallel(sim1, sim2); sim1.plot()`. ### Other changes - Data can now be supplied to a simulation; it will be automatically plotted by `sim.plot()`. - `ss.Calibration` has been significantly reworked, and now includes more flexible parameter setting, plus plotting (`calib.plot_sims()` and `calib.plot_trend()`). It also has a `debug` argument (which runs in serial rather than paralell), which can be helpful for troubleshooting issues. - `MultiSim` now has display methods `brief()` (minimal), `show()` (moderate), and `disp` (verbose). - `sim.export_df()` has been renamed `sim.to_df()`. - Most classes now have `to_json()` methods (which can also export to a dict). - Fixed a bug in how the `InfectionLog` is added to disease modules. - `Sim.gitinfo` has been replaced with `Sim.metadata` (which includes git info). - `Infection.validate_beta()` is now applied on every timestep, so changes to beta during the simulation are now honored. - `sim.get_intervention()` and `sim.get_analyzer()` have been removed; use built-in `ndict` operations (e.g., the label) to find the object you're after. - `requires` has been removed from modules, but `ss.check_requires()` is still available if needed. Call it manually from `init_pre()` if desired, e.g. a PMTCT intervention might call `ss.check_requires(self.sim, ['hiv', 'maternalnet'])`. - For networks, `contacts` has been renamed `edges` except in cases where it refers to an *agent's* contacts. For example, `network.contacts` has been renamed `network.edges`, but `ss.find_contacts()` remains the same. - Networks now have a `to_graph()` method that exports to NetworkX. - `ss.diff_sims()` can now handle `MultiSim` objects. - `Sim._orig_pars` has been removed. - `ss.unique()` has been removed. ### Regression information - Note: the list here covers major changes only; in general, Starsim v1.0 scripts will not be compatible with Starsim v2.0. - Results from Starsim v2.0 will be stochastically (but not statistically) different from Starsim v1.0. - All duration and rate parameters should now be wrapped with `ss.dur()` and `ss.rate()`. Events that represent probabilities over time (i.e. hazard rates) can also be wrapped with `ss.time_prob()`, although this is similar to `ss.rate()` unless the value is relatively large. - `ss.Plugin` has been removed. Use `ss.Module` instead. - `init_results()` is now called by `init_pre()`, and does not need to be called explicitly. - `default_pars()` has been renamed `define_pars()`. - `add_states()` has been renamed `define_states()` - `initialize()` has been renamed `init()`. - `Demographics.update()` has been renamed `Demographics.step()`. - `Network.update()` has been renamed `Network.step()`. - `Disease.update_pre()` has been renamed `Disease.step_state()`. - `Disease.make_new_cases()` has been renamed `Disease.step()`. - `Disease.update_death()` has been renamed `Disease.step_die()` (which is now called by `People.step_die()`). - `Infection._set_cases()` has been renamed `Infection.set_outcomes()`. - `Intervention.apply(sim)` has been renamed `Intervention.step()`; ditto for `Analyzer`. - `Module.step()` no longer takes `sim` as an argument (e.g., replace `intervention.apply(sim)` with `intervention.step()`). - All modules now have methods for `start_step()`, `finish_step()`, `init_results()`, and `update_results()`. - `Network.contacts` has been renamed `Network.edges`. - `sim.get_intervention()` and `sim.get_analyzer()` have been removed; simply call directly instead (e.g. replace `sim.get_intervention('vaccine')` with `sim.interventions['vaccine']`). - `requires` is no longer an attribute of modules; call the `ss.check_requires()` function directly if needed. - `People.resolve_deaths()` has been renamed `People.check_deaths()` - `ss.unique()` has been removed. - *GitHub info*: PR [626](https://github.com/starsimhub/starsim/pull/626) ## Version 1.0.3 (2024-09-26) - Fixes a bug in which some intervention parameters (e.g. eligibility) do not get set properly. - *GitHub info*: PR [639](https://github.com/starsimhub/starsim/pull/639) ## Version 1.0.2 (2024-09-25) - Fixes a bug in which random numbers drawn from auto-jumped distributions would overlap with random numbers drawn from subsequent timesteps. - *GitHub info*: PR [639](https://github.com/starsimhub/starsim/pull/639) ## Version 1.0.1 (2024-07-22) - Adds a new distribution, `ss.rand_raw()`, that samples raw integers from the random number bit generator, for use with calculating transmission. This version is roughly 20-30% faster than the previous implementation. - Adds interpolation to age-standardized fertility rate (ASFR) data. - Adds flexibility to ART initiation. - *GitHub info*: PR [593](https://github.com/starsimhub/starsim/pull/593) ## Version 1.0.0 (2024-07-10) - Official release of Starsim! - Adds a `Calibration` class, based on [Optuna](https://optuna.org), to facilitate the calibration of Starsim models. - Adds `mean()`, `median()`, and `plot()` methods to `MultiSim`. - Adds `low` and `high` attributes to `Result` objects. - Adds a `flatten()` method to `Results`, allowing nested `Results` objects to be turned into flat dictionaries. - Removes duplicate UIDs among new infections, and adds a `unique()` method to `ss.uids`. - Fixes a bug that prevented `ss.lognorm_im()` from using callable parameters. - Updates the default `Sim` string representation to be a single line; the more verbose version is available via `sim.disp()`. - *GitHub info*: PR [581](https://github.com/starsimhub/starsim/pull/581) ## Version 0.5.10 (2024-07-03) - Adds two new common-random-number-safe networks. The first is an Erdős-Rényi network that is similar to `RandomNet` but parameterized differently. The second is a 2D spatial network with connectivity between agents within a given radius; these agents can also optionally move. - *GitHub info*: PR [575](https://github.com/starsimhub/starsim/pull/575) ## Version 0.5.9 (2024-06-30) - Added a `ss.histogram()` distribution, which allows generating new random values from an empirical histogram. - When binned age data is provided to specify the initial ages for new agents, the ages are now distributed throughout the year/bin rather than new agents being assigned integer ages - Initial age data is now accepted as a `pd.Series` rather than a `pd.DataFrame` where the index corresponds to the age values, thereby avoiding the need for specific dataframe column names to be used to specify the age and value - *GitHub info*: PR [572](https://github.com/starsimhub/starsim/pull/572) ## Version 0.5.8 (2024-06-30) - Revert to making infection logging disabled by default. However, the infection log will now always be created so disease subclasses can override logging behaviour where required (e.g., to capture additional metadata) - **Backwards-compatibility notes:** Logging has been moved from an argument to `Disease` to `pars`. Existing code such as `Disease(log=True)` should be changed to `Disease(pars={'log':True})`. The 'log' option can be added to the pars passed to any subclass e.g., `ss.HIV(pars={...,log=True})`. - *GitHub info*: PR [573](https://github.com/starsimhub/starsim/pull/573) ## Version 0.5.7 (2024-06-27) - Implemented a new `ss.combine_rands()` function based on a bitwise-XOR, since the previous modulo-based approach could introduce correlations between pairs of agents. - *GitHub info*: PR [546](https://github.com/starsimhub/starsim/pull/546) ## Version 0.5.6 (2024-06-22) - `ss.Infection.make_new_cases()` now returns the index of the network associated with each transmission event - If a `People` object is provided to the `Arr` constructor, the arrays will be pre-initialized to index the current UIDs in the `People` object. This enables construction of temporary `Arr` instances that can be used to perform intermediate calculations (e.g., inside `Intervention.apply()` or within a module update step) - Deprecated `Arr(raw=...)` argument to simplify initialization, as in practice the `raw` variable is not directly set, and this update also introduces a new pathway for initializating the raw attribute - `ss.uids.to_numpy()` now returns a view rather than a copy - `ss.bernoulli.filter()` now supports `ss.BoolArr` as an input, where the filtering will operate on the `uids` returned by `ss.BoolArr.uids` - `ss.uids()` supports construction from `set` objects (via `np.fromiter()`) - *GitHub info*: PR [565](https://github.com/starsimhub/starsim/pull/555) ## Version 0.5.5 (2024-06-19) - Added labels to `Result` and state (`Arr`) objects. - Added Numba decorator to `find_contacts` to significantly increase performance. - Fixed bug when comparing `uids` and `BoolArr` objects. - *GitHub info*: PR [562](https://github.com/starsimhub/starsim/pull/555) ## Version 0.5.4 (2024-06-18) - Adjusted `RandomNet` to avoid connections to unborn agents and use random rounding for half edges - Adds `get_analyzers` and `get_analyzer` - Refactor how data is pre-processed for births/pregnancy/death rates, giving about a 10% decrease in run time for the STIsim HIV model - `BoolArr.uids` is automatically called when doing set operations on `uids` with a `BoolArr` - *GitHub info*: PR [555](https://github.com/starsimhub/starsim/pull/555) ## Version 0.5.3 (2024-06-10) - `ss.uids` class implements set operators to facilitate combining or otherwise operating on collections of UIDs - `FloatArr.isnan` and `FloatArr.notnan` return `BoolArr` instances rather than UIDs (so as to facilitate logical operations with other `BoolArr` instances, and to align more closely with np.isnan) - `Arr.true()` and `Arr.false()` are supported for all `Arr` subclasses - `BoolArr.isnan` and `Boolarr.notnan` are also implemented (although since `BoolArr` cannot store NaN values, these always return `False` and `True`, respectively) - *GitHub info*: PR [544](https://github.com/starsimhub/starsim/pull/544) ## Version 0.5.2 (2024-06-04) - Renames `network.contacts` to `network.edges`. - For modules (including diseases, networks, etc.), renames `initialize()` to `init_pre()` and `init_vals()` to `init_post()`. - Renames `ss.delta()` to `ss.constant()`. - Allows `Arr` objects to be indexed by integer (which are assumed to be UIDs). - Fixes bug when using callable parameters with `ss.lognorm_ex()` and `ss.lognorm_im()`. - Fixes bug when initializing `ss.StaticNet()`. - Updates default birth rate from 0 to 30 (so `demographics=True` is meaningful). - Adds `min_age` and `max_age` parameters to the `Pregnancy` module (with defaults 15 and 50 years). - Adds an option for the `sir_vaccine` to be all-or-nothing instead of leaky. - Updates baseline test from HIV to SIR + SIS. - Fixes issue with infection log not being populated. - *GitHub info*: PR [527](https://github.com/starsimhub/starsim/pull/527) ## Version 0.5.1 (2024-05-15) - Separates maternal transmission into prenatal and postnatal modules. - *GitHub info*: PR [509](https://github.com/starsimhub/starsim/pull/509) ## Version 0.5.0 (2024-05-14) ### Summary All inputs to the sim and modules now use a `ss.Pars()` class, which handles updating and validation. It is now not necessary to ever use `pars=` (although you still can if you want), so what was previously: `sim = ss.Sim(pars=dict(diseases='sir', networks='random'))` is now just: `sim = ss.Sim(diseases='sir', networks='random')` Updates happen recursively, so distributions etc. can be flexibly updated. This has significantly changed how modules are initialized; what was previously: def __init__(self, pars=None, **kwargs): pars = ss.omergeleft(pars, dur_inf = 6, init_prev = 0.01, p_death = 0.01, beta = 0.5, ) par_dists = ss.omergeleft(par_dists, dur_inf = ss.lognorm_ex, init_prev = ss.bernoulli, p_death = ss.bernoulli, ) super().__init__(pars=pars, par_dists=par_dists, *args, **kwargs) is now: def __init__(self, pars=None, **kwargs): super().__init__() self.default_pars( beta = 0.5, init_prev = ss.bernoulli(0.01), dur_inf = ss.lognorm_ex(6), p_death = ss.bernoulli(0.01), ) self.update_pars(pars, **kwargs) ### Parameter changes - Added a `ss.Pars` class (and a `ss.SimPars` subclass) that handles parameter creation, updates, and validation. - Initialization has been moved from `sim.py` to `parameters.py`; `ss.Sim.convert_plugins()` has been replaced by `ss.SimPars.convert_modules()`. - The key method is `ss.Pars.update()`, which performs all necessary validation on the parameters being updated. ### Initialization changes - Previously, the people were initialized first, then the states were initialized and the values populated, then the modules were initialized, and finally the distributions are initialized. This led to circular logic with the states being initialized based on uninitialized distributions. Now, states and modules are *linked* to the `People` and `Sim` objects, but further initialization is not done at this step. This ensures all distributions are created but not yet used. Next, distributions are initialized. Finally, the initial values are populated, and everything is initialized. - New methods supporting these changes include `ss.link_dists()`, `dist.link_sim()`, `dist.link_module()`, `sim.init_vals()`, `people.init_vals()`, `module.init_vals()`, ### Module changes - Whereas modules previously initialized a dict of parameters and then called `super().__init__(pars, **kwargs)`, they now call `super().__init__()` first, then `self.default_pars(par1=x, par2=y)`, then finally `self.update_pars(pars, **kwargs)`. - What was previously e.g. `ss.Module(pars=dict(par=x))` is now `ss.Module(par=x)`. - `par_dists` has been removed; instead, distributions are specified in the default parameters, and are updated via the `Pars` object. - Modules now contain a link back to the `Sim` object. This means that all methods that used to have `sim` as an argument now do not, e.g. `self.update()` instead of `self.update(sim)`. - `ss.module_map()` maps different module types to their location in the sim. - `ss.find_modules()` finds all available modules (including subclasses) in Starsim. - Removed `ss.dictmerge()` and `ss.dictmergeleft` (now handled by `ss.Pars.update()`). - Removed `ss.get_subclasses()` and `ss.all_subclasses()` (now handled by `ss.find_modules()`). - Modules can no longer be initialized with a `name` key; it must be `type` (e.g. `dict(type='sir')` rather than `dict(name='sir')`. - Added `to_json()` and `plot()` methods to `Module`. - Removed `connectors.py`; connectors still exist but as an empty subclass of `Module`. ### People and network changes - `BasePeople` has been removed and merged with `People`. - Time parameters (`ti`, `dt`, etc.) have been removed from `People`. Use `sim.ti`, `sim.dt` etc. instead. One consequence of this is that `people.request_death()` now requires a `sim` argument. Another is that network methods (e.g. `add_pairs()`) now take `sim` arguments instead of `people` arguments. - `SexualNetwork` is now a subclass of `DynamicNetwork`. - Removed `ss.Networks` (now just an `ss.ndict`). - Network connectors have been removed. - `Person` has been implemented as a slice of `sim.people[i]`. - There is a new parameter `use_aging`; this defaults to `True` if demographic modules are supplied, and `False` otherwise. ### Other changes - Boolean arrays have new methods `true()`, `false()`, and `split()`, which return the UIDs for the `True` values (alias to `arr.uids`), `False` values, and both sets of values, respectively. `ss.bernoulli.split()` has been added as an alias of `ss.bernoulli.filter(both=True)`. - All inputs to a sim are now copied by default. To disable, use `ss.Sim(..., copy_inputs=False)`. - There is a new `Plugin` class, which contains shared logic for Interventions and Analyzers. It has a `from_func()`, which will generate an intervention/analyzer from a function. - Diseases no longer have a default value of `beta=1` assigned; beta must be defined explicitly if being used. - Individual diseases can now be plotted via either e.g. `sim.plot('hiv')` or `sim.diseases.hiv.plot()`. - Distributions can be created from dicts via `ss.make_dist()`. - A new function `ss.check_sims_match()` will check if the results of two or more simulations match. - `ndict` values can be accessed through a call; e.g. `sim.diseases()` is equivalent to `sim.diseases.values()`. - Merged `test_dcp.py` and `test_base.py` into `test_other.py`. - Renamed `test_simple.py` to `test_sim.py`. - Renamed `test_dists.py` to `test_randomness.py`. - *GitHub info*: PR [488](https://github.com/starsimhub/starsim/pull/488) ## Version 0.4.0 (2024-04-24) - Replace `UIDArray`, `ArrayView`, and `State` with `Arr`, which has different subclasses for different data types (e.g. `FloatArr`, `BoolArr`, and `IndexArr`). States are usually represented by `BoolArr` (e.g. `sir.infected`), while other agent properties are represented by `FloatArr` (e.g. `sir.rel_trans`). - Arrays that had previously been represented using an integer data type (e.g. `sir.ti_infected`) are now also `FloatArr`, to allow the use of `np.nan`. Integer arrays are supported via `IndexArr`, but these are only intended for use for slots and UIDs. - `Arr` objects automatically skip over dead (or otherwise removed) agents; the "active" UIDs are stored in `sim.people.auids`, which is updated when agents are born or die. This array is linked to each `Arr`, so that e.g. `sim.people.age.mean()` will only calculate the mean over alive agents. To access the underlying Numpy array, use `sim.people.age.raw`. - `FloatArr` has `isnan`, `notnan`, and `notnanvals` properties. `BoolArr` has logical operations defined. For example, `~people.female` works, but `~people.ti_dead` does not; `people.ti_dead.notnan` works, but `people.female.notnan` does not. - UIDs used to be NumPy integer arrays; they are now `ss.uids` objects (which is a class, but is lowercase for consistency with `np.array()`, which it is functionally similar to). Indexing a state by an integer array rather than `ss.uids()` now raises an exception, due to the ambiguity involved. To index the underlying array with an integer array, use `Arr.raw[int_arr]`; to index only the active/alive agents, use `Arr.values[int_arr]`. - Dead agents are no longer removed, so `uid` always corresponds to the position in the array. This means that no remapping is necessary, which has a significant performance benefit (roughly 2x faster for large numbers of agents). - Renamed `omerge` to `dictmerge` and `omergeleft` to `dictmergeleft`. - *GitHub info*: PR [456](https://github.com/starsimhub/starsim/pull/456) ## Version 0.3.4 (2024-04-18) - Default duration of edges in `ss.RandomNet` changed from 1 to 0; this does not matter if `dt=1`, but does matter with smaller `dt` values. - Removed `ss.HPVNet`. - `new_deaths` now counted for cholera. - Crude birth and death rates now take `dt` into account. - The ability to use a centralized random number generator has been restored via `ss.options(_centralized=True)`; this option not advised, but can be used for testing. - *GitHub info*: PR [473](https://github.com/starsimhub/starsim/pull/473) ## Version 0.3.3 (2024-04-16) - Changed Ebola model transmission logic. - Fixed bug with module names not being preserved with multiple initialization. - *GitHub info*: PR [463](https://github.com/starsimhub/starsim/pull/463) ## Version 0.3.2 (2024-04-08) - Change to syphilis model to permit latent transmission. - *GitHub info*: PR [450](https://github.com/starsimhub/starsim/pull/450) ## Version 0.3.1 (2024-03-31) - Added SIS model. - Fixes distribution initialization. - Allows interventions and analyzers to be functions. - Tidies up tests. - Performance improvements in `UIDArray` (~3x faster for large numbers of agents). - *GitHub info*: PR [428](https://github.com/amath-idm/stisim/pull/428) ## Version 0.3.0 (2024-03-30) ### New RNGs & distributions - Replaces `ss.SingleRNG()`, `ss.MultiRNG()`, `ss.ScipyDistribution()`, and `ss.ScipyHistogram()` with a single `ss.Dist()` class. The `starsim.random` and `starsim.distributions` submodules have been removed, and `starsim.dists` has been added. - The `ss.Dist` class uses `np.random.default_rng()` rather than `scipy.stats` by default, although a `scipy.stats` distribution can be supplied as an alternative. This is up to 4x faster (including, critically, for Bernoulli distributions). - Also removes `ss.options.multirng` (the new version is equivalent to it being always on). - Removes duplicate logic for transmission (`make_new_cases()`) - Adds new custom distributions such as `ss.choice()` and `ss.delta()`. - These distributions can be called directly, e.g. `dist = ss.weibull(c=2); dist(5)` will return 5 random variates from a Weibull distribution. - Instead of being manually initialized based on the name, the `Sim` object is parsed and all distributions will be initialized with a unique identifier based on their place in the object (e.g. `sim.diseases.sir.pars.dur_inf`), which is used to set their unique seed. ### Other changes - This PR also fixes bugs with lognormal parameters, and makes it clear whether the parameters are for the *implicit* normal distribution (`ss.lognorm_im()`, the NumPy/SciPy default, equivalent to `ss.lognorm_mean()` previously) or the "explicit" lognormal distribution (`ss.lognorm_ex()`, equivalent to `ss.lognorm()` previously). - Renames `ss.dx`, `ss.tx`, `ss.vx` to`ss.Dx`, `ss.Tx`, `ss.Vx`. - Removed `set_numba_seed()` as a duplicate of `set_seed()`. - *GitHub info*: PR [392](https://github.com/amath-idm/stisim/pull/392) ## Version 0.2.10 (2024-03-18) - SIR duration of infection now accounts for dt - Reworked sir\_vaccine to modify rel\_sus instead of moving agents from susceptible to recovered. - n\_years no longer necessarily an integer - *GitHub info*: PR [389](https://github.com/amath-idm/stisim/pull/389) ## Version 0.2.9 (2024-03-18) - Renames and extends the multirng option in settings, now called 'rng', which set how random numbers are handled in Starsim with three options: - "centralized" uses the centralized numpy random number generator for all distributions. - "single" uses a separate (SingleRNG) random number generator for each distribution. - "multi" uses a separate (MultiRNG) random number generator for each distribution. - *GitHub info*: PR [349](https://github.com/amath-idm/stisim/pull/349) ## Version 0.2.8 (2024-03-13) - Add `ss.demo()` to quickly create a default simulation. - *GitHub info*: PR [380](https://github.com/amath-idm/stisim/pull/380) ## Version 0.2.7 (2024-03-09) - Update `StaticNet` with defaults and correct argument passing - *GitHub info*: PR [339](https://github.com/amath-idm/stisim/pull/339) ## Version 0.2.6 (2024-02-29) - Make random number streams independent for SIR - *GitHub info*: PR [307](https://github.com/amath-idm/stisim/pull/307) ## Version 0.2.5 (2024-02-29) - Improve logic for making new cases with multi-RNG - *GitHub info*: PR [337](https://github.com/amath-idm/stisim/pull/337) ## Version 0.2.4 (2024-02-27) - Improve `sim.summarize()` - Improve `sim.plot()` - Improve SIR model defaults - *GitHub info*: PR [320](https://github.com/amath-idm/stisim/pull/320) ## Version 0.2.3 (2024-02-26) - Removes `STI` class - Changes default death rate from units of per person to per thousand people - Allows `ss.Sim(demographics=True)` to enable births and deaths - Fix pickling of `State` objects - Rename `networks.py` to `network.py`, and fix HIV mortality - *GitHub info*: PRs [305](https://github.com/amath-idm/stisim/pull/305), [308](https://github.com/amath-idm/stisim/pull/308), [317](https://github.com/amath-idm/stisim/pull/317) ## Version 0.2.2 (2024-02-26) - Add the `Samples` class - *GitHub info*: PR [311](https://github.com/amath-idm/stisim/pull/311) ## Version 0.2.1 (2024-02-22) - Only remove dead agents on certain timesteps - *GitHub info*: PR [294](https://github.com/amath-idm/stisim/pull/294) ## Version 0.2.0 (2024-02-15) - Code reorganization, including making `networks.py` and `disease.py` to the top level - Networks moved from `People` to `Sim` - Various classes renamed (e.g. `FusedArray` to `UIDArray`, `STI` to `Infection`) - Better type checking - Added `MultiSim` - Added cholera, measles, and Ebola - Added vaccination - More flexible inputs - *GitHub info*: PR [235](https://github.com/amath-idm/stisim/pull/235) ## Version 0.1.8 (2024-01-30) - Transmission based on number of contacts - *GitHub info*: PR [220](https://github.com/amath-idm/stisim/pull/220) ## Version 0.1.7 (2024-01-27) - Performance enhancement for disease transmission, leading to a 10% decrease in runtime. - *GitHub info*: PR [217](https://github.com/amath-idm/stisim/pull/217) ## Version 0.1.6 (2024-01-23) - Adds template interventions and products for diagnostics and treatment - Adds syphilis screening & treatment interventions - *GitHub info*: PR [210](https://github.com/amath-idm/stisim/pull/210) ## Version 0.1.5 (2024-01-23) - Renamed `stisim` to `starsim`. - *GitHub info*: PR [200](https://github.com/amath-idm/stisim/pull/200) ## Version 0.1.4 (2024-01-23) - Adds a syphilis module - *GitHub info*: PR [206](https://github.com/amath-idm/stisim/pull/206) ## Version 0.1.3 (2024-01-22) - Read in age distributions for people initializations - *GitHub info*: PR [205](https://github.com/amath-idm/stisim/pull/205) ## Version 0.1.2 (2024-01-19) - Functionality for converting birth & fertility data to a callable parameter within SciPy distributions - *GitHub info*: PR [203](https://github.com/amath-idm/stisim/pull/203) ## Version 0.1.1 (2024-01-12) - Improving performance of MultiRNG - Now factoring the timestep, `dt`, into transmission calculations - *GitHub info*: PRs [204](https://github.com/amath-idm/stisim/pull/204) ## Version 0.1.0 (2023-12-10) - Allows SciPy distributions to be used as parameters - Optionally use multiple random number streams and other tricks to maintain coherence between simulations - Adding functionality to convert death rate data to a callable parameter within a SciPy distribution - *GitHub info*: PRs [170](https://github.com/amath-idm/stisim/pull/170) and [202](https://github.com/amath-idm/stisim/pull/202) ## Version 0.0.8 (2023-10-04) - Enable removing people from simulations following death - *GitHub info*: PR [121](https://github.com/amath-idm/stisim/pull/121) ## Version 0.0.7 (2023-09-08) - Refactor distributions to use new Distribution class - *GitHub info*: PR [112](https://github.com/amath-idm/stisim/pull/112) ## Version 0.0.6 (2023-08-30) - Changes agent IDs from index-based to UID-based - Allows states to store their own data and live within modules - *GitHub info*: PR [88](https://github.com/amath-idm/stisim/pull/88) ## Version 0.0.5 (2023-08-29) - Refactor file structure - *GitHub info*: PRs [77](https://github.com/amath-idm/stisim/pull/77) and [86](https://github.com/amath-idm/stisim/pull/86) ## Version 0.0.2 (2023-06-29) - Adds in basic Starsim functionality - *GitHub info*: PR [17](https://github.com/amath-idm/stisim/pull/17) ## Version 0.0.1 (2023-06-22) - Initial version.