medsim 0.5.0 — chunked-run integrity
New features
-
Pre-integration review hardening (7 confirmed findings). The collapse
audit excludes discrete contract fields BY NAME (collapse_exclude,
defaultconverged/branch_switch) instead of by observed distinctness --
a TOTALLY collapsed estimate column (1-2 distinct values) is no longer
invisible to the very check built for it. An all-failed run now fires an
all_failedviolation instead of combining clean. Mixed legacy+v2 chunk
directories are order-independent (attributes stripped + one warning; the
audit never mis-stops on legacy ids).erroris a documented RESERVED
method-field name -- a method returning it stops immediately instead of
havingconvergedsilently forced to 0. Pilot checks:ncompared
numerically (200L vs 200 no longer false-positives) and NA-vs-value
asymmetries against the pilot are mismatches. All-provenance-less chunk
sets warn about the skipped SHA assertion. The combinerbindis
schema-harmonized (ragged chunk columns cannot crash it), and the submit
template createslogs/up front. -
Gate C: chunk provenance + single-SHA assertion (#34). Every chunk file
now carries a provenance header (R version, medsim + key dependency
versions, hostname, code SHA, sec/rep, UTC timestamp). The SHA
auto-detects viagit rev-parse HEADin the running script's directory
(explicitcode_sha =overrides;pkg:medsim-<version>tag outside git).
medsim_combine_chunks()asserts one SHA across all chunks -- catching a
mid-run code edit + partial resubmit that would silently mix two code
states. Provenance-less legacy chunks warn, never stop. -
Gate D: pilot-subset positive control (#34). Because seeds depend only
on(scenario, replication), a full run's reps1..B_pilotare
draw-identical to an archived pilot at the samen.
medsim_combine_chunks(pilot_reference =, pilot_tol = 1e-9)asserts
identity first (sample size + scenario fingerprints -- a stale pilot
fails aspilot_config_differs, never masquerading as a seeding
regression), then compares estimate columns only (neverelapsed) within
tolerance -- a free regression check that harness, environment, and seeding
are unchanged since the pilot passed. -
Gate B: hardened SLURM submit template (#34, fixes #37).
medsim_write_submit_script()now emits a fail-loud script:#!/bin/bash -l
(on Hoppermoduleis defined only in login shells -- the old plain
#!/bin/bashtemplate failed there with "module: command not found"),
set -eo pipefail, a hard-failingmodule load(never|| true), a
command -v Rscriptpre-check,#SBATCH --requeue, andRscriptas the
final command so its exit code is the task's exit code. Optional
config$array_throttle = Kemits--array=1-N%K. There is deliberately no
shell-side output-file gate -- completeness is audited at combine time by
Gate A. Theinst/hopper-tests/submit_chunk.shexemplar is updated to the
same pattern, and thechunk_%04d.rdsnaming convention is centralized in
an internal helper shared by writer and combiner. -
Gate A: combine-step seed-provenance audit (#34, SPEC-medsim-chunked-run-gates).
medsim_combine_chunks()now audits the combined grid and the new
medsim_audit_results()runs the same audit standalone: per-scenario
replicationcontiguity (gapless, duplicate-free1..max, equal across
scenarios -- self-validating, no external count needed; optionalnsim =
pin), the collapse signature on every continuous estimate column
(n_distinct > collapse_threshold * n_ok; the 0.3.1 seed collapse produced
~17 distinct outcomes in 1000 while every chunk exited 0), all-failed cells
reported ascell_failed, and cross-scenario seed collisions in
.medsim_det_seed()'s hash space. Violations route through one
on_violation = c("stop", "warn", "ignore")control; the default"stop"
signals amedsim_combine_violationcondition that carries the combined
results (tryCatch(..., medsim_combine_violation = function(e) e$results)
recovers an hours-long run's good cells). Breaking: the old
expected_chunkswarn-and-combine default is now a violation under the same
control -- for a deliberate partial combine (interim look at a running
array), passon_violation = "warn". Legacy (pre-schema-v2) frames skip the
id-based audits with a warning, never an error.
Bug fixes
- Chunked runs:
replicationis now the GLOBAL rep id (schema v2; #36,
SPEC-medsim-chunked-run-gates P1). Previously each SLURM chunk emitted
chunk-local ids (1..chunk_size), so a combined 4-chunk/nsim-20 run carried
5 distinctreplicationvalues each appearing 4 times, rows were not
uniquely identifiable, andmedsim_analyze()reported the chunk size as
n_replications. Standalone (non-chunked) runs are unchanged. Result frames
now carrymedsim_schema = 2Land amedsim_meta_colsprovenance attribute
recording which columns the runner wrote (consumed bymedsim_analyze();
legacy frames fall back to the old name list). medsim_combine_chunks()rebuilds$summaryand$config(P2).
It previously returned chunk 1's slice statistics and chunk-sized
config$n_replicationsas if they described the combined run.- One failed replication no longer crashes the run (P3). Failure rows and
success rows had different columns, sorbinderrored
("names do not match previous names") -- in chunk mode converting one
transient rep failure into a missing chunk file. All rows now share one
schema: anerrorcolumn on every row (NAon success),NAestimates and
converged = 0on failure rows. - Logical method-contract fields are no longer dropped (P3).
branch_switch/convergedreturned as logicals (including logicalNA)
were silently excluded from$results, breaking
medsim_summarize_branch_switch(). - Chunk mode no longer writes intermediate CSVs (#38, P3). Concurrent
array tasks sharing anoutput_diroverwrote each other's fixed-name
results_scenario_*.csv/all_results.csv(last writer wins), leaving
partial data that looked complete. The chunk.rdsis the artifact;
standalone runs still write their CSVs.
Quality fixes (2026-07 review round)
medsim_scenario_pmed()now computes the ground-truth P_med with the exact
closed form `Phi((beta_ay + alpha_axbeta_my) / sqrt(2(beta_my^2*sigma_m^2- sigma_y^2)))
instead of an unseeded 4x50k Monte-Carlo potential-outcome draw at construction time. The truth is now deterministic, exact, and leaves the RNG state untouched; then_poargument (and then_poparameter of the internal.medsim_pmed_truth()`) has been removed.
- sigma_y^2)))
medsim_validate_scenario()now validates generator output for all
estimand kinds: interval/numeric (and other non-core) kinds previously got
zero column validation; they now require a non-empty data.frame (at least
1 row and 1 column). X/M/Y columns are still not required for these kinds --
their column contract remains method-defined..medsim_analyze_coverage_interval()now emits acoverage_mcsecolumn
(sqrt(p*(1-p)/n_valid), mirroring the point path) in both the overall and
by-scenario coverage frames, somedsim_table_coverage()renders a real
MCSE for interval-kind runs instead of anNAplaceholder. The table
builder is also hardened to render body rows even when optional columns
(coverage_mcse,n_valid,n_failed) are absent.medsim_config(seed_stream=)is deprecated: the value was stored and
documented but has never been consumed by any medsim function. Passing a
non-NULLvalue now emits a deprecation warning pointing at
medsim_run_parallel(seed=); the value is still stored for
back-compatibility.- The
medsim_cache_load()documentation example now unwraps the
fingerprinted ground-truth cache formatlist(truth=, fingerprint=)written
bymedsim_run()instead of treating the wrapper as the truth value.
- Internal refactor:
medsim_scenario_sobol()andmedsim_scenario_gauge()
now share one linear-Gaussian-with-interaction data generator
(.medsim_lingauss_dgp()) and one corner-mean truth helper
(.medsim_corner_means()) instead of duplicating them verbatim. Scenario
draws and closed-form truths are bit-identical to before. - Internal refactor: the delta-method product SE is now a single helper
(.medsim_se_prod()) used by the MC-CI and IPW adapters, and
medsim_method_bounds()/medsim_method_pmed_mbco()fit their mediation
regressions through the generalized.medsim_md_fit_ab()(named-coefficient
lookup, replacing fragile positionalcoef()[k]indexing; one edge-path
change: degenerate/collinear fits now error loudly instead of a silent
se = 0.1fallback). Estimates, SEs,
CIs, and p-values are bit-identical to before. - Performance:
medsim_method_mbco_mi()computes the three MBCO
log-likelihoods once per imputation (.medsim_mbco_lls()) and derives both
the D4 LRT statistic and the union-null branch indicator from that triple --
about 6 instead of 10lm()fits per imputation, with bit-identical
statistics, p-values, andbranch_switchvalues. - Internal rename:
.gen_complete_med()is now.medsim_gen_complete_med(),
matching the package's.medsim_*internal naming convention.
Known caveat: Gate B's submit template is validated by a stubbed exit-code harness + the field-tested exemplar pattern; a live Hopper Tier-B array run (inst/hopper-tests/run-all.sh) remains the on-cluster validation path.