Skip to contents

CausalStress separates execution, optional persistence, typed scoring, and audit. A board is required only when results should be stored; without it, runners simply return their result objects.

library(CausalStress)
library(dplyr)
library(pins)

board_path <- tempfile("causalstress-history-")
dir.create(board_path)
board <- board_folder(board_path)

Run and persist one seed

cs_run_single() returns typed outputs and scores, compatibility ATT/QST projections, optional bootstrap draws, metadata, and provenance. Bootstrap draws are present only when the estimator/runner produced them.

one_run <- cs_run_single(
  dgp_id = "synth_baseline",
  estimator_id = "lm_att",
  n = 200,
  seed = 123,
  board = board
)

names(one_run)
[1] "outputs"    "scores"     "att"        "qst"        "boot_draws"
[6] "meta"       "provenance"
cs_collect_scores(one_run) |>
  select(estimand_target_id, metric_id, score_status, estimate, truth, error)
# A tibble: 1 × 6
  estimand_target_id metric_id   score_status estimate truth   error
  <chr>              <chr>       <chr>           <dbl> <dbl>   <dbl>
1 att                point_error scored           1.07  1.07 0.00681
cs_tidy(one_run) |>
  select(dgp_id, estimator_id, seed, est_att, att_error)
# A tibble: 1 × 5
  dgp_id         estimator_id  seed est_att att_error
  <chr>          <chr>        <int>   <dbl>     <dbl>
1 synth_baseline lm_att         123    1.07   0.00681

Only the completed result is persisted. The board does not make an in-progress estimator call recoverable.

Scale to an ordinary grid

This small grid uses the finite-moment baseline DGP, so the displayed mean ATT error summaries have their ordinary interpretation. Heavy-tail estimand-boundary runs should be executed and reported separately, not placed in a mean-based ATT RMSE, coverage, or ranking shootout.

runs <- cs_run_grid(
  dgp_ids = "synth_baseline",
  estimator_ids = c("lm_att", "ipw_att"),
  n = 200,
  seeds = 1:3,
  board = board,
  show_progress = FALSE
)
Running batch: synth_baseline x lm_att
Running batch: synth_baseline x ipw_att
scorecard <- cs_summarise_runs(runs)
scorecard |>
  select(
    dgp_id, estimator_id, n_runs,
    mean_error, mean_abs_error, sd_error
  )
# A tibble: 2 × 6
  dgp_id         estimator_id n_runs mean_error mean_abs_error sd_error
  <chr>          <chr>         <int>      <dbl>          <dbl>    <dbl>
1 synth_baseline ipw_att           3    -0.0318          0.137    0.202
2 synth_baseline lm_att            3    -0.0327          0.101    0.149

mean_error is signed mean deviation, mean_abs_error is mean absolute deviation, and sd_error is the standard deviation across the supplied runs. None of those columns is RMSE.

Resume matching completed work

Resume is explicit and strict. skip_existing = TRUE requires current schema-4 metadata and an exact configuration fingerprint before reading an existing pin. Here all requested records already exist, so the estimators are not rerun.

resumed <- cs_run_grid(
  dgp_ids = "synth_baseline",
  estimator_ids = c("lm_att", "ipw_att"),
  n = 200,
  seeds = 1:3,
  board = board,
  skip_existing = TRUE,
  show_progress = FALSE
)
Running batch: synth_baseline x lm_att
Running batch: synth_baseline x ipw_att
resumed |>
  count(estimator_id)
# A tibble: 2 × 2
  estimator_id     n
  <chr>        <int>
1 ipw_att          3
2 lm_att           3

Filesystem reads and validation still take time. A mismatch or historical schema fails closed; use force = TRUE only when recomputation and replacement are intentional.

Audit and reload persisted evidence

cs_audit() inventories pins and their recorded identities. It does not turn timestamps or runtime logs into scientific identity fields.

history <- cs_audit(board)
history |>
  select(
    dgp_id, estimator_id, estimator_version,
    seed, fit_fingerprints, score_fingerprints, pin_name
  ) |>
  head()
# A tibble: 6 × 7
  dgp_id         estimator_id estimator_version  seed fit_fingerprints
  <chr>          <chr>        <chr>             <int> <list>
1 synth_baseline ipw_att      0.2.1                 1 <chr [1]>
2 synth_baseline ipw_att      0.2.1                 2 <chr [1]>
3 synth_baseline ipw_att      0.2.1                 3 <chr [1]>
4 synth_baseline lm_att       0.2.1                 1 <chr [1]>
5 synth_baseline lm_att       0.2.1               123 <chr [1]>
6 synth_baseline lm_att       0.2.1                 2 <chr [1]>
# ℹ 2 more variables: score_fingerprints <list>, pin_name <chr>

A pins board remains an ordinary persistence boundary, so a selected artifact can be loaded with pins and then inspected through CausalStress accessors.

target <- history |>
  filter(
    dgp_id == "synth_baseline",
    estimator_id == "lm_att",
    seed == 1
  ) |>
  slice(1)

stored_result <- pin_read(board, target$pin_name)

cs_meta_flatten(stored_result) |>
  select(dgp_id, estimator_id, seed, fit_fingerprint)
# A tibble: 1 × 4
  dgp_id         estimator_id  seed fit_fingerprint
  <chr>          <chr>        <int> <chr>
1 synth_baseline lm_att           1 ce6d27d319adb1e646f5d1e1892188bd5a78eef5e96…
cs_provenance(stored_result)[c("run_time_dgp", "run_time_est", "run_time_total")]
$run_time_dgp
[1] 0.003341913

$run_time_est
[1] 0.001786947

$run_time_total
[1] 0.02736688

For experimental parallel execution, workers stage completed RDS artifacts and the controlling process gathers them through a single-writer path. That is a different contract from serial board writes and does not make a whole campaign transactional.