How to handle divergent chains in Stan mixture models

Hello, I want to use a mixture model to decide whether my data is better explained by two components or by a single component.

For example, let me take (simulated) data to be the following. The model behaves well (Reff close to 1; Neff comparable with iter) when the two components in the simulated data are equal in proportion. So, I will take proportion to be 1:2. In this case, the chains seem to be forming two clusters, with the two clusters having widely different lp__. If I understand correctly, this means the chains with lower lp__ are finding a posterior estimate with a much lower posterior density than the other chains.

However, how should I proceed after this? Should I discard the chains with lower mean lp__? With lower HDI of lp__? From some places (including Claude and ChatGPT), I read that I should proceed by computing Rhat for individual chains. However, Rhat seems to be computed across multiple chains rather than individual; so I’m missing something. But let’s say I do compute it (see below), I see that all Rhat are close to 1 (and even below 1?).

Thanks a lot in advance!

To elaborate:

my.data_ <- c(rbinom(40, size=8, prob=rbeta(40, 51, 951)),
              rbinom(80, size=8, prob=rbeta(80, 951, 51)));

My model is:

data{
  int<lower=0> num_participants;
  int<lower=1> num_components;
  array[num_participants] int<lower=0> x;
}
transformed data{
  int N = num_participants;
  int K = num_components; // number of groups
}
parameters{
  simplex[K] w;       // mixing proportions
  ordered[K] mode_raw;   // locations of mixture components
  vector<lower=0>[K] c;  // scales or concentrations of mixture components
}
transformed parameters{
  vector<lower=0, upper=1>[K] mode;
  mode = inv_logit(mode_raw);
  vector<lower=1>[K] a, b;
  for(i in 1:K){
    a[i] = mode[i] * c[i] + 1;
    b[i] = (1 - mode[i]) * c[i] + 1;
  }
}
model{
  vector[K] log_w = log(w);  // cache log calculation
  mode_raw ~ normal(0,10);
  c ~ lognormal(0, 2);
  for (n in 1:N) {
    vector[K] lps = log_w;
    for (k in 1:K){
      lps[k] += beta_binomial_lpmf(x[n] | 8, a[k], b[k]);
    }
    target += log_sum_exp(lps);
  }
}
generated quantities{
  // The two groups unmixed
  vector[K] x_group;
  for (k in 1:K)
    x_group[k] = beta_rng(a[k], b[k]);
}

And I fit it:

my.model <- rstan::stan(model_code = my.mixture_model,
                        data = list(num_participants = length(my.data_),
                                    x = my.data_,
                                    num_components = 2),
                        iter = 10000,
                        cores = 4,
                        chains = 10,
                        refresh = 0,
                        save_warmup = TRUE);

The diagnostics suggest that the chains are diverging:

my.model

Inference for Stan model: anon_model.
10 chains, each with iter=10000; warmup=5000; thin=1; 
post-warmup draws per chain=5000, total post-warmup draws=50000.

               mean se_mean    sd    2.5%     25%     50%     75%   97.5% n_eff  Rhat
w[1]           0.37    0.03  0.09    0.26    0.31    0.35    0.40    0.59     7  1.98
w[2]           0.63    0.03  0.09    0.41    0.60    0.65    0.69    0.74     7  1.98
mode_raw[1]   -6.70    3.13  8.98  -26.52  -11.53   -6.19   -3.02   10.17     8  1.68
mode_raw[2]    8.87    0.76  5.76    3.09    4.00    7.09   12.20   23.02    58  1.07
c[1]          24.83    6.79 59.10    6.44   10.21   13.91   22.82  106.93    76  1.06
c[2]          24.98    5.57 25.92    0.01   13.89   19.68   30.00   77.47    22  1.22
mode[1]        0.21    0.18  0.39    0.00    0.00    0.00    0.05    1.00     5 25.23
mode[2]        0.99    0.00  0.01    0.96    0.98    1.00    1.00    1.00    68  1.10
a[1]          13.24    9.89 57.51    1.00    1.00    1.02    2.41   94.38    34  1.11
a[2]          25.47    5.47 24.85    1.01   14.86   20.63   30.54   75.37    21  1.23
b[1]          13.60    2.62 14.90    1.00    8.28   11.90   15.96   44.09    32  1.11
b[2]           1.51    0.09  1.15    1.00    1.00    1.01    1.52    4.19   173  1.08
x_group[1]     0.26    0.16  0.36    0.00    0.03    0.08    0.21    0.99     5  5.52
x_group[2]     0.86    0.08  0.22    0.13    0.89    0.95    0.98    1.00     8  1.67
lp__        -194.04    6.39 14.37 -224.64 -189.76 -187.03 -185.99 -184.89     5  9.57

However, when I look at individual chains, I see them forming two clusters:

plot_chain_params <- function(model, param){

    params <- as.array(model, pars=param);
    if (length(dim(params)) > 2){
        dim(params) <- c(dim(params)[1] * dim(params)[3], dim(params)[2])
    }
    dimnames(params) <- list(iteration = NULL,
                             chain = paste0("chain_", 1:ncol(params)))

    df <- as.data.frame.table(params, responseName = "param") |>
        rename(chain = chain)
    print(str(df))

    ggplot(df, aes(x = param, fill=chain)) +
        geom_density() +
        labs(x = param, y = "Chain") +
        theme_minimal() +
        theme(legend.position = "none") +
        facet_wrap(facets="chain", ncol=1)
}

plot_chain_params(my.model, "x_group[2]")

## ggsave(here("plots", "stanforums_x_group_2.png"))

Their lp__:

Computing Rhat for individual chains (does it make sense?)

for(i in 1:10){
    print(rstan::Rhat(as.array(my.model, pars="x_group[2]")[,i,1]))
}
[1] 1.000232
[1] 1.003585
[1] 1.002601
[1] 1.000395
[1] 1.001119
[1] 0.9998907
[1] 1.000066
[1] 1.018789
[1] 1.000938
[1] 1.000387

Assuming it makes sense, one explanation I see for my data is that the model is explaining the smaller component as noise. Both ess_tail as well as ess_bulk are also comparable across all the 10 chains.

One thing I see is that the between-chain divergences are much unlikely if I fix the modes to the values obtained by running kmeans over the data. (Though, merely passing them through init is not enough as the models can drift.) Post that, I can run the model with 1 vs 2 components and compare them using loo or something.