--- title: "Checkpoint and resume long fits" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Checkpoint and resume long fits} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") set.seed(20260716) ``` ```{r cran-gate, include = FALSE} # The model fits below are evaluated when the article is built locally, in CI # and for the pkgdown site (all of which set NOT_CRAN). CRAN's check farm gives # the whole check a ten-minute budget, which these fits do not fit inside, so # there the code is shown without being run. EVAL_FITS <- identical(Sys.getenv("NOT_CRAN"), "true") knitr::opts_chunk$set(eval = EVAL_FITS) ``` ```{r load, message = FALSE} library(tulpa) ``` ## Why checkpoint A nested-Laplace fit is a loop over independent hyperparameter grid cells; a multi-chain NUTS fit is a loop over independent chains. Both can run for a long time, and a killed or rebooted run should not have to start over. tulpa writes each finished unit to a content-addressed append log: on resume it reloads the completed units and runs only the rest, and a run interrupted mid-write is detected and re-run rather than trusted. ## Turn it on Every nested-Laplace fitter takes `control$checkpoint = list(path =, resume =)`. `path` is the checkpoint file; `resume = TRUE` reloads any completed cells from it, `resume = FALSE` (the default) starts fresh and overwrites. ```{r fit} # A small nested-Laplace fit over an ICAR field on a chain graph. S <- 30L W <- matrix(0, S, S) for (i in 1:(S - 1)) W[i, i + 1] <- W[i + 1, i] <- 1 df <- data.frame(region = factor(seq_len(S))) df$x <- as.integer(df$region) / 10 + rnorm(S, 0, 0.3) df$y <- rbinom(S, 20, plogis(-0.4 + 0.5 * df$x)) ckpt <- tempfile(fileext = ".ckpt") fit <- tulpa(y ~ x + spatial(region), data = df, family = "binomial", n_trials = rep(20L, S), spatial = spatial_car(W, level = "obs"), mode = "laplace", control = list(checkpoint = list(path = ckpt, resume = FALSE))) coef(fit) ``` The grid cells are now on disk: ```{r size} file.exists(ckpt) && file.info(ckpt)$size > 0 ``` ## Resume A second call with the same data, settings, and grid plus `resume = TRUE` reloads every completed cell instead of re-solving it -- so this call returns essentially instantly and to the same result. ```{r resume} fit2 <- tulpa(y ~ x + spatial(region), data = df, family = "binomial", n_trials = rep(20L, S), spatial = spatial_car(W, level = "obs"), mode = "laplace", control = list(checkpoint = list(path = ckpt, resume = TRUE))) all.equal(coef(fit), coef(fit2)) ``` ```{r cleanup, include = FALSE} unlink(ckpt) ``` ## What the fingerprint protects The checkpoint header carries a fingerprint of the data, settings, and grid. Resuming onto a file written for a *different* fit errors rather than silently mixing results, and a torn final record (a run killed mid-write) is truncated and re-run. A multi-chain NUTS resume is bit-for-bit identical to the uninterrupted run, because a chain is deterministic in its seed and data.