---
title: "The calibrate / simulate workflow in covsim"
author: "Njål Foldnes"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{The calibrate / simulate workflow in covsim}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

## Motivation

Since covsim 1.2.0, each of the three algorithms (`IG`, `PLSIM`, `VITA`) is
split into two steps:

1. **Calibration** — fits the model parameters that achieve the target
   covariance (and the target marginal moments where applicable). This step
   is the expensive one. It only needs to be run once for a given target.
2. **Simulation** — draws data from the calibrated model. Fast. Can be
   repeated as often as needed, returning a list of independent samples.

The original `rIG()`, `rPLSIM()` and `vita()` functions are unchanged and
still work; they are now thin wrappers that calibrate and simulate in one
call. New code that needs many simulated datasets from the same target
should call the calibrator once and reuse the calibration object.

## The general pattern

```{r eval=FALSE}
library(covsim)

# 1. calibrate once
cal <- calibrate_ig(sigma.target, skewness, excesskurtosis)
#   ... or calibrate_plsim(...)
#   ... or calibrate_vm(...)
#   ... or calibrate_vita(margins, sigma.target)

# 2. simulate one or more datasets
draws <- simulate(cal, nsim = 100, N = 1000)   # list of 100 data matrices
```

`simulate()` here is the standard `stats::simulate` generic; covsim
registers S3 methods for it on the four calibration object classes
(`covsim_calib_ig`, `covsim_calib_plsim`, `covsim_calib_vm`,
`covsim_calib_vita`).

## IG (Independent Generator)

```{r}
library(covsim)
set.seed(1)
d <- 4
sigma.target <- cov(MASS::mvrnorm(50, mu = rep(0, d), Sigma = diag(d)))

cal_ig <- calibrate_ig(sigma.target,
                       skewness        = rep(1, d),
                       excesskurtosis  = rep(3, d))
class(cal_ig)

# draw five independent datasets from the same calibration
draws <- simulate(cal_ig, nsim = 5, N = 10000)
length(draws)
dim(draws[[1]])
round(cov(draws[[1]]) - sigma.target, 3)
```

The first call to `calibrate_ig` does the work; subsequent `simulate()`
calls are nearly free.

## PLSIM (Piecewise Linear)

PLSIM's pairwise calibration solves one intermediate correlation per
variable pair. Since covsim 1.2.0 each pairwise covariance is computed
exactly from Price's theorem, so calibration takes well under a second
even for a dozen variables, and the calibration can be reused for any number
of replications.

```{r}
set.seed(1)
sigma.target <- cov(MASS::mvrnorm(50, mu = rep(0, 4), Sigma = diag(4)))
cal_pl <- calibrate_plsim(sigma.target,
                          skewness       = rep(1, 4),
                          excesskurtosis = rep(3, 4),
                          verbose        = FALSE)

# Re-use the calibration for many replications
replications <- simulate(cal_pl, nsim = 50, N = 500)
length(replications)
```

## VM (Vale-Maurelli)

The Vale-Maurelli method transforms correlated normals with Fleishman
polynomials. Like IG and PLSIM, only skewness and excess kurtosis are
specified.

```{r}
set.seed(1)
sigma.target <- cov(MASS::mvrnorm(50, mu = rep(0, 4), Sigma = diag(4)))
cal_vm <- calibrate_vm(sigma.target,
                       skewness       = rep(1, 4),
                       excesskurtosis = rep(3, 4))
cal_vm$coef      # Fleishman coefficients, one row per variable
draws <- simulate(cal_vm, nsim = 5, N = 10000)
round(cov(draws[[1]]) - sigma.target, 3)
```

## From calibration to a Monte Carlo study

`simulate_estimate()` combines simulation and estimation. Here the
rejection rate of the ML chi-square test is compared under IG and VM data
with the same covariance matrix, skewness and kurtosis:

```{r}
sigma <- matrix(0.5, 4, 4); diag(sigma) <- 1
dimnames(sigma) <- list(paste0("x", 1:4), paste0("x", 1:4))
dists <- list(IG = calibrate_ig(sigma, rep(2, 4), rep(7, 4)),
              VM = calibrate_vm(sigma, rep(2, 4), rep(7, 4)))
res <- simulate_estimate(dists, model = "F =~ x1 + x2 + x3 + x4",
                         n = 200, reps = 50, seed = 1,
                         extractor = function(fit) lavaan::fitMeasures(fit, "pvalue"))
aggregate(pvalue ~ dist, res, function(p) mean(p < 0.05))
```

Each replication uses its own random number stream, so `cores = 4` gives
the same result as `cores = 1`. Pass `fit = "magmaan"` to estimate with
magmaan instead of lavaan, or any `function(data, model)`.
`vignette("simulation-study", package = "covsim")` shows a complete study,
with and without magmaan.

## VITA (Vine-To-Anything)

`calibrate_vita()` calibrates a regular vine to match the target
covariance under user-specified marginals. The resulting calibration
object embeds the underlying `vine_dist`, so it can be passed to
`simulate()` (recommended) or to `rvinecopulib::rvine()` directly.

```{r eval=FALSE}
set.seed(1)
d <- 5
sigma.target <- cov(MASS::mvrnorm(50, mu = rep(0, d), Sigma = diag(d)))
margins <- lapply(sqrt(diag(sigma.target)),
                  function(s) list(distr = "norm", sd = s))

cal_vita <- calibrate_vita(margins, sigma.target,
                           Nmax    = 1e5,
                           cores   = parallel::detectCores(),
                           verbose = FALSE)

draws <- simulate(cal_vita, nsim = 10, N = 1000)
```

`cores` is passed to `rvinecopulib`, which draws the calibration samples
in parallel threads. The calibration algorithm is the same as in covsim
1.1.0.

## When to use the wrappers vs the new API

Use the new API when:

- You need many replications from the same target (simulation studies).
- You want to inspect the calibrated model before simulating.
- You want to call `simulate()` in pipelines (it is the standard R generic).

Use the original `rIG()` / `rPLSIM()` / `vita()` wrappers when:

- You only need a one-shot draw.
- You're maintaining old code; both still work and produce the same
  output shape as in covsim 1.1.0.
