---
title: "Posterior diagnostics and multiple chains"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Posterior diagnostics and multiple chains}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

After fitting `bpgmm`, posterior samples can be inspected through chain-level
summaries, trace plots, and co-clustering probabilities. These diagnostics
complement the model-selection example.

The chains below are deliberately short so that the vignette builds quickly.
Applied analyses should use longer chains and examine stability across
independent runs.

## Simulate a diagnostic example

```{r}
library(bpgmm)

set.seed(2029)
X <- cbind(
  matrix(rnorm(10, mean = -2.0, sd = 0.25), nrow = 2),
  matrix(rnorm(10, mean =  0.0, sd = 0.25), nrow = 2),
  matrix(rnorm(10, mean =  2.0, sd = 0.25), nrow = 2)
)
known_labels <- rep(1:3, each = 5)
```

```{r, fig.width = 5.5, fig.height = 4, fig.alt = "Scatter plot of a compact three-cluster diagnostic data set."}
cluster_cols <- c("#0072B2", "#D55E00", "#009E73", "#CC79A7")
plot(
  X[1, ], X[2, ],
  col = cluster_cols[known_labels],
  pch = 19,
  xlab = "Variable 1",
  ylab = "Variable 2",
  main = "Diagnostic example",
  asp = 1
)
```

## Run independent chains

`pgmm_rjmcmc_chains()` runs independent chains. The vignette uses `cores = 1`
for CRAN portability, but users can increase `cores` locally.

```{r}
fits <- pgmm_rjmcmc_chains(
  X = X,
  m_init = 3,
  m_range = c(1, 4),
  q_new = 1,
  burn = 1,
  niter = 4,
  constraint = "UUU",
  m_step = 1,
  v_step = 1,
  chains = 2,
  cores = 1,
  seed = 2029,
  verbose = FALSE
)

length(fits)
attr(fits, "chain_seeds")
```

## Summarize each chain

For chain \(c\), simple empirical summaries are

\[
\widehat{p}_c(m = r \mid X)
  = \frac{1}{S_c}\sum_{s=1}^{S_c} I(m_c^{(s)} = r),
\qquad
\widehat{p}_c(v = a \mid X)
  = \frac{1}{S_c}\sum_{s=1}^{S_c} I(v_c^{(s)} = a).
\]

Large disagreements across chains are a warning sign, especially when the
chains use different random seeds.

```{r}
chain_summaries <- lapply(fits, summarize_pgmm_rjmcmc, true_cluster = known_labels)

data.frame(
  chain = names(chain_summaries),
  ari = vapply(chain_summaries, function(x) x$ari, numeric(1)),
  modal_clusters = vapply(chain_summaries, function(x) {
    as.integer(names(which.max(x$n_clusters)))
  }, integer(1))
)
```

## Trace cluster counts and covariance models

The posterior samples store the active cluster indicators and covariance
constraint at each saved iteration. These traces are first diagnostics.

```{r}
cluster_count_trace <- function(fit) {
  vapply(fit$active_cluster_samples, sum, numeric(1))
}

constraint_trace <- function(fit) {
  vapply(fit$constraint_samples, constraint_to_model, character(1))
}

cluster_traces <- lapply(fits, cluster_count_trace)
constraint_traces <- lapply(fits, constraint_trace)

cluster_traces
constraint_traces
```

```{r, fig.width = 7, fig.height = 4, fig.alt = "Trace plot of sampled cluster counts across two short chains."}
old_par <- par(mar = c(4, 4, 3, 1))
plot(
  cluster_traces[[1]],
  type = "b",
  pch = 19,
  ylim = range(unlist(cluster_traces)),
  col = "#0072B2",
  xlab = "Saved iteration",
  ylab = "Active clusters",
  main = "Cluster-count trace"
)
lines(cluster_traces[[2]], type = "b", pch = 19, col = "#D55E00")
legend("topright", legend = names(fits), col = c("#0072B2", "#D55E00"), lty = 1, pch = 19, bty = "n")
par(old_par)
```

## Posterior co-clustering matrix

A co-clustering matrix estimates how often two observations are assigned to the
same cluster across posterior samples. It reports pairwise uncertainty rather
than only a single modal allocation.

\[
C_{ij} =
\Pr(z_i = z_j \mid X)
\approx
\frac{1}{S}\sum_{s=1}^{S}
I\{z_i^{(s)} = z_j^{(s)}\}.
\]

```{r}
co_clustering_matrix <- function(fit) {
  n <- length(fit$allocation_samples[[1]])
  out <- matrix(0, n, n)

  for (allocation in fit$allocation_samples) {
    out <- out + outer(allocation, allocation, "==")
  }

  out / length(fit$allocation_samples)
}

co_mat <- co_clustering_matrix(fits[[1]])
round(co_mat[1:6, 1:6], 2)
```

```{r, fig.width = 5.5, fig.height = 5, fig.alt = "Heatmap of posterior co-clustering probabilities."}
image(
  seq_len(nrow(co_mat)),
  seq_len(ncol(co_mat)),
  co_mat[nrow(co_mat):1, ],
  col = hcl.colors(20, "YlGnBu", rev = TRUE),
  xlab = "Observation",
  ylab = "Observation",
  main = "Posterior co-clustering"
)
```

## What to look for

Warning signs include:

- cluster-count traces that stay at the boundary of `m_range`;
- very different posterior summaries across independent chains;
- covariance-model traces that never move when `v_step = 1`;
- co-clustering matrices with no clear block structure;
- sensitivity to scaling, `q_new`, or `m_range`.

These diagnostics do not replace a long MCMC analysis, but they give a concise
description of the posterior output.
