calibration
HPVsim calibration — ss.Calibration subclass with a weighted-gof eval.
Provides
- hpv.Calibration: ss.Calibration subclass that takes
data(a standardized wide DataFrame, a dict of pre-scoped DataFrames, or a list of CSV paths / long-format DataFrames — all normalized viahpv.data.loaders.load_calib_data) and computes a single weighted mismatch usingcompute_gof. - compute_gof: goodness-of-fit between actual and predicted arrays.
- build_sim: default build_fn that routes flat dotted-key calib_pars to sim.pars, sim.diseases[
].pars, or the CrossImmunity connector.
Standardized data DataFrame: index=‘t’ (float years), columns dot-scoped: - all_hpv.<name> scalar-per-year pooled target; looked up on sim.results.all_hpv (HPVTotal) at eval time (e.g. all_hpv.asr_cancer_incidence). - all_hpv.<name>.<bin> age-stratified pooled target; looked up on the auto-attached all_hpv_by_age by_age analyzer (e.g. all_hpv.cancers.0-15). - by_genotype.<name>.<g> per-genotype distribution target; computed at eval time via hpv.results_by_genotype.
Classes
| Name | Description |
|---|---|
| Calibration | HPVsim calibration. Delegates to ss.Calibration with HPV-aware defaults. |
Calibration
calibration.Calibration(
sim,
calib_pars,
*,
data=None,
weights=None,
gof_kwargs=None,
build_fn=None,
eval_fn=None,
eval_kw=None,
**kwargs,
)HPVsim calibration. Delegates to ss.Calibration with HPV-aware defaults.
Three entry points to specify the fit target:
datafiles=['data/foo_cancer_cases.csv', ...]: list of long-format CSV paths (columnsyear,name,[age,sex,genotype,]value). The loader dispatches on stratification columns: files with anagecolumn go to a sharedby_ageanalyzer named'calib_by_age'(attached tosim.pars.analyzersif not already there);by_ageresult keys then live under the scoped data key'calib_by_age.<name>'. Ages and years are derived from the data itself. Genotype-stratified files (noagecolumn) are reserved for'calib_by_genotype.<name>'but not yet wired up (NotImplementedError).data={'calib_by_age.cancers': df, ...}: pre-built dict of ‘t’-indexed DataFrames. Keys are'<analyzer_name>.<result_key>'(scoped) or a bare result key that unambiguously matches a singleby_ageanalyzer on the sim. The default eval_fn extracts each, aligns on(index, columns), and sumscompute_gofacross all cells, scaled by per-keyweights.components=[...]or a customeval_fn: standard ss.Calibration paths, unchanged.
calib_pars is a nested dict grouped by scope, with list leaves [best, low, high, step] (step optional). sc.flattendict collapses the scope tree to ss.Calibration’s flat dotted-key form::
calib_pars = dict(
beta=[0.2, 0.1, 0.34, 0.02],
network=dict(m_partners_casual=[0.5, 0.1, 0.9, 0.05]),
hi5=dict(cin_fn=dict(k=[0.15, 0.1, 0.25, 0.02])),
)
Default build_fn is hpv.calibration.build_sim (route_pars); it accepts the flattened dotted keys and routes them.
Methods
| Name | Description |
|---|---|
| remove_db | Skip cleanup when self.run_args.storage isn’t a string URL |
| shrink | Return a lightweight sc.objdict with the top-n_results |
| worker | Run a single worker. |
remove_db
calibration.Calibration.remove_db()Skip cleanup when self.run_args.storage isn’t a string URL (parent’s 'sqlite' in storage check errors on a Storage object). Each hpv.Calibration gets its own tempdir per instance, so there’s no cross-run pollution to worry about.
shrink
calibration.Calibration.shrink(n_results=100)Return a lightweight sc.objdict with the top-n_results trials (by mismatch) + the metadata needed to replot / rebuild sims.
Use for committing calibration results to source control: a full 1000-5000-trial calib.obj is many MB (Optuna study, tempdir storage refs, per-trial state); the shrunken version drops all of that while remaining a drop-in for hpv.plot_calibration and utils.run_top_n.
Preserved attributes (accessed by plotting/rebuild code paths): - df : top-N rows of the trial DataFrame, sorted by mismatch ascending. - best_pars : the best-fit par dict. - eval_kw : the eval kwargs (holds data for plotting). - calib_pars : the Optuna spec dict. - build_fn : how to reconstruct a sim from a par set. - build_kw : build_fn kwargs (defaults to {}). - sim : base sim template.
worker
calibration.Calibration.worker()Run a single worker.
Mirrors stisim.Calibration.worker: wraps study.optimize in a try/except so a single worker’s transient Optuna storage failure does not propagate through Optuna’s own error handler and trip its assert False, 'Should not reach.', taking the whole run down. Upstream ss.Calibration.worker calls study.optimize bare.
Functions
| Name | Description |
|---|---|
| compute_gof | Goodness-of-fit between two arrays — normalized absolute error by default. |
| default_eval_fn | Weighted sum of compute_gof across each column of data. |
| make_calib_sims | Rerun the top-n trials from a hpv.Calibration in parallel. |
compute_gof
calibration.compute_gof(
actual,
predicted,
normalize=True,
use_frac=False,
use_squared=False,
as_scalar='none',
eps=1e-09,
skestimator=None,
estimator=None,
**kwargs,
)Goodness-of-fit between two arrays — normalized absolute error by default.
Mean squared error is normalize=False, use_squared=True, as_scalar='mean'.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| actual | array of observed (data) points. | required | |
| predicted | array of model points; same shape as actual. |
required | |
| normalize | if True, divide errors by max(\|actual\|). |
True |
|
| use_frac | if True, divide each error by max(actual, predicted) + eps instead of normalizing by the global max. |
False |
|
| use_squared | if True, square the per-point errors. | False |
|
| as_scalar | collapse to a scalar via 'sum' / 'mean' / 'median'; 'none' returns the per-point array. |
'none' |
|
| eps | small constant guarding the use_frac denominator. |
1e-09 |
|
| skestimator | scikit-learn metric name (e.g. 'mean_squared_error'). |
None |
|
| estimator | user-supplied callable (actual, predicted, **kwargs). |
None |
|
| kwargs | forwarded to the scikit-learn / custom estimator. | {} |
default_eval_fn
calibration.default_eval_fn(sim, data, weights=None, gof_kwargs=None)Weighted sum of compute_gof across each column of data.
data is a wide DataFrame (index=‘t’, dot-scoped columns). For each column, sim-side values are extracted via _extract_columns, NaN cells in data are skipped, and per-column mismatches are weighted by weights.get(column_name, 1.0) before summing.
make_calib_sims
calibration.make_calib_sims(
calib,
n=50,
sim_kwargs=None,
analyzers=None,
extract_fn=None,
n_workers=None,
)Rerun the top-n trials from a hpv.Calibration in parallel.
A hpv.Calibration stores per-trial mismatch + eval-column values, not per-trial sim results. To inspect any other result (e.g. the asr_cancer_incidence trajectory, or a custom by_age analyzer output), rerun the top-n best-fit trials with this helper and read sim.results / sim.analyzers from the returned sims.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| calib | hpv.Calibration (or the shrunk sc.objdict from calib.shrink() — both expose df, calib_pars, build_fn, build_kw, sim). |
required | |
| n | int | number of best-mismatch trials to rerun; clamped to len(calib.df). |
50 |
| sim_kwargs | dict | overrides applied to sim.pars before build_fn (e.g. dict(stop=2045) to project past the calibration window). |
None |
| analyzers | zero-arg callable returning a fresh list of Analyzer instances (recommended so each subprocess gets its own state), OR a list of instances (deep-copied per worker). Appended to any analyzers the Calibration already attached to the base sim. | None |
|
| extract_fn | callable | if provided, worker returns extract_fn(sim) instead of the full sim. Strongly recommended — see the note below. |
None |
| n_workers | int | defaults to min(n, cpu_count()). |
None |
Returns
| Name | Type | Description |
|---|---|---|
list of length min(n, len(calib.df)): run sims if extract_fn |
||
| is None, else the extract_fn outputs. |
Note
Full sims are memory-heavy (each holds people arrays, edge tables, results, analyzers). For n=50 this can be many GB in the parent process after sc.parallelize pickles them back. Standard practice: pass an extract_fn that returns a small dict / DataFrame / array (use ss.Result.to_df or ss.Result.annualize for time-series results), then commit that extracted output rather than the raw sims.