---
title: "Saving and Reloading Fits"
output:
  rmarkdown::html_vignette:
    toc: true
vignette: >
  %\VignetteIndexEntry{Saving and Reloading Fits}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  message = FALSE,
  warning = FALSE
)
```

```{r load-package}
library(mixeff)
```

A mixed-model fit often outlives the R session that produced it.
You fit a model today, save it with the analysis, hand the script to
a collaborator, and reopen the fit six months later for a contrast,
a revision, or a referee response.

`mixeff` stores the fitted values, the random-effects design, the
convergence record, and the inference labels inside the R object —
so each of those tasks works after `readRDS()` without recomputing
the fit, and without depending on the original Rust handle.

## Fit a model

```{r clinic-data, include = FALSE}
set.seed(42)
n_clinic <- 12L
n_per_clinic <- 6L
clinic <- factor(rep(seq_len(n_clinic), each = n_per_clinic))
week <- rep(0:(n_per_clinic - 1L), n_clinic)
treatment <- factor(
  rep(rep(c("usual", "coached"), each = n_per_clinic), length.out = length(week)),
  levels = c("usual", "coached")
)
clinic_shift <- rnorm(n_clinic, sd = 0.45)
score <- 7.2 - 0.28 * week - 0.55 * (treatment == "coached") +
  clinic_shift[as.integer(clinic)] + rnorm(length(week), sd = 0.35)
clinic_visits <- data.frame(score, week, treatment, clinic)
```

```{r fit-model}
fit <- lmm(
  score ~ week + treatment + (1 | clinic),
  clinic_visits,
  control = mm_control(verbose = -1)
)
```

Before saving, the ordinary extractors work as expected.

```{r before-save}
fixef(fit)
reporting_table(fit, "fixed_effects")
```

## Round trip through RDS

```{r rds-roundtrip}
path <- tempfile(fileext = ".rds")
saveRDS(fit, path)

restored <- readRDS(path)
restored <- revive(restored)
```

The restored object still answers the same fitted-model questions.

```{r after-restore}
fixef(restored)
head(predict(restored))
reporting_table(restored, "fixed_effects")
```

```{r restore-checks, include = FALSE}
stopifnot(identical(fixef(restored), fixef(fit)))
stopifnot(length(predict(restored)) == nrow(clinic_visits))
stopifnot(identical(reporting_table(restored, "fixed_effects"),
                    reporting_table(fit, "fixed_effects")))
```

## Rebuild design matrices when needed

Design extractors can be rebuilt from the stored formula and model frame.

```{r design-extractors}
X <- model.matrix(restored, type = "fixed")
Z <- model.matrix(restored, type = "random")

dim(X)
dim(Z)
class(Z)
```

`getME()` provides a small familiar subset for code that expects lme4-style
names.

```{r getme}
getME(restored, c("theta", "beta", "cnms"))
```

```{r design-checks, include = FALSE}
stopifnot(nrow(X) == nrow(clinic_visits))
stopifnot(nrow(Z) == nrow(clinic_visits))
stopifnot(inherits(Z, "dgCMatrix"))
```

## What stays explicit?

Quantities that the Rust inference contract cannot certify are marked rather
than fabricated. For full-rank fits, `vcov()` returns the model-based
fixed-effect covariance from the upstream `fixed_effect_covariance_matrix`
payload. For rank-deficient or otherwise uncertified fits, the matrix carries
an `mm_unavailable_reason` attribute and the values are `NA`.

```{r covariance-status}
V <- vcov(restored)
attr(V, "mm_unavailable_reason")
V
```

Conditional variances for random effects also survive the round trip. With
`condVar = TRUE`, each grouping table carries a finite `postVar` array.

```{r ranef-condvar}
re <- ranef(restored, condVar = TRUE)
attr(re, "mm_unavailable_reason")
dim(attr(re$clinic, "postVar"))
```

```{r unavailable-checks, include = FALSE}
stopifnot(is.null(attr(V, "mm_unavailable_reason")) ||
          is.character(attr(V, "mm_unavailable_reason")))
stopifnot(is.null(attr(re, "mm_unavailable_reason")))
stopifnot(is.array(attr(re$clinic, "postVar")))
stopifnot(all(is.finite(attr(re$clinic, "postVar"))))
```

## What should you report?

Use `reporting_table()` for durable tables and `summary()` for console output.
Both continue to work after a save/load cycle.

```{r final-reporting}
coef_table <- summary(restored, method = "auto")$coefficients
knitr::kable(coef_table, digits = 4)
reporting_table(restored, "overview")
```
