---
title: GLMM Fitting and Model Comparison
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{GLMM Fitting and Model Comparison}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
has_lme4 <- requireNamespace("lme4", quietly = TRUE)
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  message = FALSE,
  warning = FALSE,
  eval = has_lme4
)
```

```{r load-package, eval = TRUE}
library(mixeff)
```

```{r lme4-missing, eval = !has_lme4, echo = FALSE, results = "asis"}
cat("The executable cbpp examples in this vignette require the `lme4` package.\n")
```

This vignette shows the current GLMM contract in `mixeff`. The default path
is `glmm(..., method = "pirls_profiled")`, which delegates to the upstream
profiled PIRLS GLMM fitter. `method = "joint_laplace"` requests the labelled
joint route (`fast = FALSE`, `nAGQ = 1`) and is available in the dependency-light
vendored build through the native optimizer.

The example uses the `lme4::cbpp` data. `glmm()` does not yet expose binomial
case weights, so the grouped counts are expanded to Bernoulli rows before
fitting.

```{r cbpp-data}
env <- new.env(parent = emptyenv())
utils::data("cbpp", package = "lme4", envir = env)
cbpp <- get("cbpp", envir = env, inherits = FALSE)

expand_cbpp <- function(data) {
  rows <- lapply(seq_len(nrow(data)), function(i) {
    successes <- data$incidence[[i]]
    failures <- data$size[[i]] - successes
    data.frame(
      herd = data$herd[[i]],
      period = data$period[[i]],
      y = c(rep.int(1L, successes), rep.int(0L, failures))
    )
  })
  droplevels(do.call(rbind, rows))
}

cbpp_binary <- expand_cbpp(cbpp)
c(n_grouped_rows = nrow(cbpp), n_binary_rows = nrow(cbpp_binary))
```

## Family and Audit

The statistical intent is a binomial-logit GLMM with a herd random intercept.
Before fitting, the same formula can be compiled and explained so the fixed and
random-effect structure is visible before numerical optimization starts.

```{r audit-first}
cbpp_family <- binomial(link = "logit")
cbpp_formula <- y ~ period + (1 | herd)

cbpp_spec <- compile_model(cbpp_formula, cbpp_binary)
explain_model(cbpp_spec)
```

## Fit

```{r fit-laplace}
glmm_fit <- glmm(
  cbpp_formula,
  cbpp_binary,
  family = cbpp_family,
  method = "pirls_profiled",
  nAGQ = 1L,
  control = mm_control(verbose = -1)
)

glmm_fit
fixef(glmm_fit)
VarCorr(glmm_fit)
```

The summary prints fitted quantities and deliberately avoids coefficient tests
unless the inference contract certifies them.

```{r summary}
summary(glmm_fit)
```

Core extractors are available from the durable R object.

```{r extractors}
head(fitted(glmm_fit))
head(residuals(glmm_fit))
head(ranef(glmm_fit)[[1L]])
c(
  logLik = as.numeric(logLik(glmm_fit)),
  deviance = deviance(glmm_fit),
  AIC = AIC(glmm_fit),
  BIC = BIC(glmm_fit)
)
```

## Quadrature Sensitivity

`nAGQ` is part of the fit request and is recorded on the object. Values above
one are a profiled-path sensitivity check; the labelled joint-Laplace path is
restricted to `nAGQ = 1`.

```{r agq-sensitivity}
glmm_fit_agq3 <- glmm(
  cbpp_formula,
  cbpp_binary,
  family = cbpp_family,
  method = "pirls_profiled",
  nAGQ = 3L,
  control = mm_control(verbose = -1)
)

data.frame(
  nAGQ = c(glmm_fit$nAGQ, glmm_fit_agq3$nAGQ),
  logLik = c(as.numeric(logLik(glmm_fit)),
             as.numeric(logLik(glmm_fit_agq3))),
  AIC = c(AIC(glmm_fit), AIC(glmm_fit_agq3)),
  check.names = FALSE
)
```

## Current Boundaries

In-sample `fitted()` and `residuals()` are available, and
`summary(fit, tests = "coefficients")` returns a Wald-z fixed-effect
table built from the upstream PIRLS/Laplace working-Hessian covariance
payload (reliability label: `moderate`). GLMM `predict()` (population and
conditional, on the link or response scale), `contrast()`, `drop1()`, and
multi-model `anova()` are available as R-side computations on top of that
certified covariance. `confint()` (Wald) requires a joint-Laplace fit: the
fast-PIRLS/profiled covariance is a working-Hessian payload and the engine
does not certify Wald intervals for it. `simulate()` and `refit()` for GLMMs
are not yet implemented and are exposed as ordinary R conditions rather than
silently filled in. The joint-Laplace fit route is available and labelled
separately from the profiled fast-PIRLS default.

```{r boundaries}
glmm_boundary <- function(expr) {
  cnd <- tryCatch({
    force(expr)
    NULL
  }, error = function(cnd) cnd)
  if (is.null(cnd)) {
    return(data.frame(status = "available", class = NA_character_,
                      message = "", check.names = FALSE))
  }
  data.frame(
    status = "unavailable",
    class = class(cnd)[[1L]],
    message = conditionMessage(cnd),
    check.names = FALSE
  )
}

rbind(
  predict = glmm_boundary(predict(glmm_fit)),
  confint = glmm_boundary(confint(glmm_fit)),
  coefficient_tests = glmm_boundary(summary(glmm_fit, tests = "coefficients")),
  simulate = glmm_boundary(stats::simulate(glmm_fit, nsim = 1L)),
  refit = glmm_boundary(refit(glmm_fit, fitted(glmm_fit))),
  joint_laplace = glmm_boundary(glmm(
    cbpp_formula,
    cbpp_binary,
    family = cbpp_family,
    method = "joint_laplace",
    control = mm_control(verbose = -1)
  ))
)
```

```{r checks, include = FALSE}
stopifnot(inherits(glmm_fit, "mm_glmm"))
stopifnot(identical(glmm_fit$nAGQ, 1L))
stopifnot(identical(glmm_fit_agq3$nAGQ, 3L))
stopifnot(nobs(glmm_fit) == nrow(cbpp_binary))
stopifnot(all(is.finite(fixef(glmm_fit))))
stopifnot(is.finite(as.numeric(logLik(glmm_fit))))
stopifnot(is.finite(AIC(glmm_fit)))
stopifnot(inherits(summary(glmm_fit), "summary.mm_glmm"))
stopifnot(length(fitted(glmm_fit)) == nrow(cbpp_binary))
stopifnot(length(residuals(glmm_fit)) == nrow(cbpp_binary))

boundaries <- rbind(
  predict = glmm_boundary(predict(glmm_fit)),
  confint = glmm_boundary(confint(glmm_fit)),
  coefficient_tests = glmm_boundary(summary(glmm_fit, tests = "coefficients")),
  simulate = glmm_boundary(stats::simulate(glmm_fit, nsim = 1L)),
  joint_laplace = glmm_boundary(glmm(
    cbpp_formula,
    cbpp_binary,
    family = cbpp_family,
    method = "joint_laplace",
    control = mm_control(verbose = -1)
  ))
)
# predict / coefficient tests are available for PIRLS-profiled fits;
# confint requires joint-Laplace (PIRLS working-Hessian not certified for Wald);
# simulate remains unavailable; joint-Laplace route is available.
stopifnot(identical(boundaries["predict", "status"], "available"))
stopifnot(identical(boundaries["confint", "status"], "unavailable"))
stopifnot(identical(boundaries["coefficient_tests", "status"], "available"))
stopifnot(identical(boundaries["simulate", "status"], "unavailable"))
stopifnot(identical(boundaries["joint_laplace", "status"], "available"))
```
