---
title: "Running Chains in Parallel"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Running Chains in Parallel}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.5,
  warning = FALSE,
  message = FALSE
)
```

## Overview

`exnexSurv` can run multiple independent MCMC chains and, when requested, distribute them across R-level parallel workers.
The C++ sampler still handles one chain per call; the R bridge coordinates the chain loop and combines the post-warmup draws.

Use:

- `chains` for the total number of independent chains,
- `parallel_chains` for how many chains to run concurrently.

`parallel_chains` must be less than or equal to `chains`.

## Simulate data

```{r}
library(exnexSurv)
library(survival)

simulate_surv_data <- function(
  theta,
  sigma2,
  beta = NULL,
  n_per_group = 40,
  censor_min = 4,
  censor_max = 12,
  seed = NULL
) {
  if (!is.null(seed)) {
    set.seed(seed)
  }

  groups <- rep(seq_along(theta), each = n_per_group)
  age_std <- rnorm(length(groups))
  mean_log_time <- rep(theta, each = n_per_group)

  if (!is.null(beta)) {
    mean_log_time <- mean_log_time + beta * age_std
  }

  log_time <- rnorm(length(groups), mean = mean_log_time, sd = sqrt(sigma2))
  true_time <- exp(log_time)
  censor_time <- runif(length(groups), min = censor_min, max = censor_max)

  data.frame(
    time = pmin(true_time, censor_time),
    event = as.integer(true_time <= censor_time),
    group = factor(groups),
    age_std = age_std
  )
}

sim_data <- simulate_surv_data(
  theta = c(1.0, 1.6, 2.1),
  sigma2 = 0.25,
  beta = -0.30,
  seed = 9201
)
```

## Fit two chains in parallel

```{r}
fit_parallel <- exnex_surv(
  Surv(time, event) ~ group + age_std,
  data = sim_data,
  iter = 60,
  warmup = 20,
  chains = 2,
  parallel_chains = 2,
  seed = 9201
)

print(fit_parallel, show_trace = FALSE)
```

The fitted object stores the combined post-warmup draws from both chains.
If you used `iter = 60` and `warmup = 20` with `chains = 2`, the posterior sample table contains `80` rows in total, or `40` rows per chain.

## Fit more chains than workers

```{r}
fit_four_chains <- exnex_surv(
  Surv(time, event) ~ group + age_std,
  data = sim_data,
  iter = 60,
  warmup = 20,
  chains = 4,
  parallel_chains = 2,
  seed = 9201
)

print(fit_four_chains, show_trace = FALSE)
```

This runs four independent chains while scheduling only two workers at a time.

## Traceplots with all chains

```{r}
plot(
  fit_parallel,
  parameters = c("theta_1", "theta_2", "theta_3", "beta_1", "sigma2"),
  ask = FALSE
)
```

Each parameter panel shows all chains together, which makes it easier to compare mixing and overlap across chains.

## Compare to sequential execution

```{r}
fit_sequential <- exnex_surv(
  Surv(time, event) ~ group + age_std,
  data = sim_data,
  iter = 60,
  warmup = 20,
  chains = 2,
  parallel_chains = 1,
  seed = 9201
)

identical(fit_parallel$draws, fit_sequential$draws)
```

Changing `parallel_chains` affects only how the chains are scheduled, not the model itself.

## Practical notes

If you want to run more chains than you want to evaluate concurrently, set `parallel_chains` smaller than `chains`.
That is useful when the sampler is expensive or when you want to avoid using all available cores at once.
