This page is for people who build or support models in Atomica: a framework workbook of compartments, parameters, and a transition matrix, a databook of values by population and year, a program book of spending, unit costs, and effects, and calibration, scenarios, and budget optimization. It shows an Atomica model next to the same model in Starsim, how their results agree, how long each takes, and what works and what does not yet, including what fails. Starsim is a Python library: it reads Atomica’s workbooks, but it has no spreadsheet interface or user interface of its own for analysts who do not program.
The same model in both
Atomica’s SIR demo, in its framework workbook (shown here as CSV, one sheet at a time) and its databook. The compartments:
The parameters, with the force of infection as a function of them:
Code Name,Display Name,Format,Databook Page,Targetable,Databook Order,Default Value,Function
transpercontact,Transmission probability per contact,Probability,parameters,y,,0.0008,
contacts,Number of contacts annually,,parameters,y,,80,
recrate,Average duration of infections (years),Duration,parameters,y,,0.5,
infdeath,Death rate for infected people,Rate,parameters,y,,0.016,
susdeath,Death rate for susceptible people,Rate,parameters,y,,0.008,
foi,Force of infection,Rate,,,,,(1 - (1-ch_prev*transpercontact)**floor(contacts)*(1-ch_prev*transpercontact*(contacts-floor(contacts))))*(1-susdeath)
The framework also has eight characteristics (such as prevalence, ch_prev = inf/ch_all) and a cascade, and the databook gives the values (a transmission probability of 0.008, 80 contacts a year, infections lasting 5 years, and so on) and the initial numbers. Atomica takes one Euler step a year here.
In Starsim, the compartments are states, each parameter is an ss.par, and each cell of the transition matrix is a transition. solver=ss.solvers.ode(integrator='euler', substeps=1) steps the model as Atomica does:
import starsim as sstranspercontact = ss.par(0.008, doc='Transmission probability per contact')contacts = ss.par(80, doc='Number of contacts annually')susdeath = ss.par(ss.peryear(0.008), doc='Death rate for susceptible people')infdeath = ss.par(ss.peryear(0.016), doc='Death rate for infected people, in addition')sus, inf, rec, dead = ss.states(4)prev = inf.n / (sus + inf + rec).nfoi = (1- (1- prev * transpercontact)**contacts) * (1-0.008)sir = ss.Disease(infection= sus >> inf @ foi,recovery= inf >> rec @ ss.years(5),death= sus + inf + rec >> dead @ susdeath,inf_death= inf >> dead @ infdeath,init_n=dict(sus=700, inf=285.71429, rec=14.28571),default_state='sus',)sim = ss.Sim(sir, total_pop=1000, start=2000, stop=2023, solver=ss.solvers.ode(integrator='euler', substeps=1), verbose=0) # Atomica takes one Euler step a yearsim.run()sim.plot()
The HIV demo (a cascade by sex, 7 programs, 3 budgets), written by hand
Identical: a largest difference of 2 in 1014, in every compartment and every level of the cascade of both sexes, without programs and with the default, doubled, and zero budgets
Atomica’s library workbooks, imported with ss.import_from()
sir, hiv_dyn, sir_vaccine, and udt agree with Atomica 1.32.4’s own runs to within 1e-9 (checked by the switching demo). The review that the demo is built from found 9 of Atomica’s 16 library demos imported and agreed to 1e-12 or better. tb fails today (below)
Timings
Lines are lines of model code (not counting blank lines, comments, imports, or plotting, and counting a line over 100 characters twice), against the rows of Atomica’s spreadsheets plus its Python; times are for one run, the first in a new process and a rerun of the same model. Both were measured as described on the comparison page. Atomica 1.32.4’s times were recorded on the same machine, for Project.run_sim() with the spreadsheets already loaded.
Model
Lines: Atomica
Lines: Starsim
One run: Atomica
One run: Starsim
The SIR demo
62 (59 spreadsheet rows, 3 lines of Python)
16
first 0.12 s, rerun 0.0023 s
first 0.011 s, rerun 0.00023 s
Pass
The HIV demo
217 (188 spreadsheet rows, 29 lines of Python)
64
first 0.14 s, rerun 0.004 s
first 0.036 s, rerun 0.00045 s
Pass
Budget optimization of the HIV demo over its 7 programs
1.3 s
2.3 s
A spreadsheet row that a program officer fills in is not the same as a line of Python, so the line counts compare unlike things. Starsim’s Numba kernels are compiled once after installing, which takes a couple of minutes (starsim init compile); its Rust kernels need no such step.
What works and what does not yet
These are from Starsim’s switching demo for Atomica users (tests/switching/test_atomica.py in this repo), which checks each task against Atomica’s numbers, and from the review it was built from. “Fails” means the demo’s check does not pass today.
Task
Today
Import a framework and databook from Atomica’s library (sir, hiv_dyn, sir_vaccine, udt)
Works: agreement to within 1e-9; the timed vaccine duration of sir_vaccine is a fixed dwell time
Import tb, Atomica’s flagship TB demo
Fails: it imports, but lists 81 parts as not translated (junctions, references to flows such as life_expectancy*:ddis, SRC_POP_AVG(), and interactions), and the run stops when a compartment stops being a number
Calibrate an imported workbook to its databook’s data
Works: bounds on two parameters (sim.new(pars=dict(transpercontact=ss.par(0.008, bounds=[0.0008, 0.024]), ...))) and sim.fit(data=...); the mean error is 0.01% of the data, against 4.5% for Atomica’s own calibration of the same demo
Import a program book and optimize the budget: reallocate the HIV demo’s $164 million across its seven programs to maximize the number virally suppressed in 2030
Works: ss.import_from(..., progbook='hiv_dyn_progbook.xlsx', programs_start=2020) gives Atomica’s baseline exactly, and sim.optimize(ss.fitting.maximize('all_vs', at=2030), total=..., start=2020) finds 1,066,910 suppressed, against 1,067,056 for Atomica’s optimizer (from 374,830 at the current allocation), in 2.3 s against 1.3 s
The least money that reaches a target, with limits by program (Atomica’s money minimization)
Works: sim.optimize(ss.fitting.maximize('all_vs', at=2030), start=2020, target=1e6, bounds=dict(...)); on the HIV demo, a million virally suppressed in 2030 needs $133 million, in 5.5 s
Programs declared for a model written by hand (spending to coverage to effect)
Not yet: the importer writes programs as expressions; there is no ss.program()
A databook generated from the model and read back
Not yet
Costs of deliveries, by population
Works: products have cost=, and deliveries report delivered, coverage, and cost
The same model with chance, for a small province
Works: solver='binomial'
A user interface for analysts who do not program
Not planned
Bringing your model in
ss.import_from('framework.xlsx', format='atomica', databook='databook.xlsx') reads an Atomica framework and databook, and progbook= adds a program book. Compartments become states, characteristics become results, and each parameter on the transition matrix a transition; parameters with functions become expressions of the counts. Atomica moves value × dt of a compartment each step for a rate or probability, dt/value for a duration, and value × dt people for a number, which is the Euler step, so the sim is stepped as Atomica steps it. Several populations become a stratification by population, and a program book’s programs become parameters of spending, whose coverage changes the parameters they affect. Junctions, transfers, and interactions are not translated; what is not translated is listed (sim.imported.incomplete), and sim.imported.prompt is a prompt for an AI assistant with Starsim’s convert-model skill to finish the job. The import is Starsim code, kept as sim.imported.code, to save and change as a Python file.
See Import/export for importing in general: checking an import, keeping it as code, and finishing one that is incomplete.
Further reading
Atomica: documentation: frameworks, databooks, program books, calibration, and optimization