## ----setup, include=FALSE-----------------------------------------------------
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(vbpm)

## ----sweep--------------------------------------------------------------------
sim <- sim_fa(N = 500, K = 3, ipf = 6, lam = .7, lac = .3, rseed = 11)
Y <- sim$dat
groups <- rep(1:3, each = 6)

Q0 <- matrix(-1L, ncol(Y), 2)
for (k in 1:2) Q0[which(groups == k)[1:2], k] <- 1L
Q0[1:8, ]

p <- pefa(Q0, Y, Kmin = 2, Kmax = 7, verbose = FALSE)
p

## ----object-------------------------------------------------------------------
names(p)
class(p)
names(p$settings)

## ----matrices-----------------------------------------------------------------
names(p$loadings)
names(p$pips)
round(p$loadings[["3"]], 2)
round(p$pips[["3"]][1:6, ], 2)

## ----sweep-table--------------------------------------------------------------
p$sweep

## ----transitions--------------------------------------------------------------
p$transitions

## ----persistence--------------------------------------------------------------
names(p$persistence)
round(p$persistence$phi, 3)
round(p$persistence$rmsd, 3)
p$persistence$collision

## ----k2-----------------------------------------------------------------------
round(p$loadings[["2"]][c(1, 7, 8, 13, 17, 18), ], 2)

## ----ssl----------------------------------------------------------------------
ssl(p)

## ----k7-----------------------------------------------------------------------
round(p$loadings[["7"]][13:18, 3:5], 2)

## ----reading------------------------------------------------------------------
profile_specs <- data.frame(
  profile = c(".85/r1", ".80/r2", ".70/r3"),
  phi_cut = c(.85, .80, .70), r = 1:3,
  practical_default = c(FALSE, TRUE, FALSE)
)
profile_sources <- 2:4

read_sweep <- function(x, phi_cut, r, sources) {
  K <- as.integer(x$sweep$K)
  converged <- x$sweep$converged

  read_count <- function(score) {
    usable <- length(K) >= 2L && all(is.finite(K)) && all(diff(K) == 1L) &&
      length(score) == length(K) && all(is.finite(score)) &&
      is.logical(converged) && all(converged %in% TRUE)
    if (!usable) return(list(usable = FALSE, Khat = NA_integer_))
    gain <- diff(score)
    gain_max <- max(gain)
    if (!(gain_max > 0)) return(list(usable = TRUE, Khat = NA_integer_))
    peak <- max(which(gain == gain_max))
    hit <- which(seq_along(gain) > peak & gain < .20 * gain_max)
    list(usable = TRUE,
         Khat = if (length(hit)) K[hit[1L]] else NA_integer_)
  }

  ELBO <- read_count(x$sweep$ELBO)
  BIC <- read_count(-x$sweep$BIC)
  C20 <- sort(unique(c(ELBO$Khat, BIC$Khat)[
    is.finite(c(ELBO$Khat, BIC$Khat))]))

  phi <- x$persistence$phi
  collision <- x$persistence$collision
  conv <- setNames(converged, K)
  state <- vapply(sources, function(k) {
    targets <- k + seq_len(r)
    edge <- vapply(targets, function(target) {
      from <- as.character(k); to <- as.character(target)
      if (!from %in% rownames(phi) || !to %in% colnames(phi) ||
          !isTRUE(conv[[from]]) || !isTRUE(conv[[to]]) ||
          is.na(collision[from, to])) return(NA_integer_)
      if (isTRUE(collision[from, to])) return(0L)
      value <- phi[from, to]
      if (!is.finite(value)) NA_integer_ else as.integer(value >= phi_cut)
    }, integer(1))
    if (any(edge %in% 0L)) 0L else
      if (length(edge) && all(edge %in% 1L)) 1L else NA_integer_
  }, integer(1))

  persistent <- which(state == 1L)
  if (!length(state)) {
    resolution <- "unresolved"
    Kp <- NA_integer_
  } else if (!length(persistent)) {
    resolution <- if (all(state %in% 0L)) "nonpersistent" else "unresolved"
    Kp <- NA_integer_
  } else {
    top <- max(persistent)
    higher <- if (top < length(state)) state[(top + 1L):length(state)] else 0L
    resolution <- if (all(higher %in% 0L)) "persistent" else "unresolved"
    Kp <- if (resolution == "persistent") sources[top] else NA_integer_
  }

  hit_ELBO <- is.finite(Kp) && is.finite(ELBO$Khat) && ELBO$Khat == Kp
  hit_BIC <- is.finite(Kp) && is.finite(BIC$Khat) && BIC$Khat == Kp
  support <- if (hit_ELBO && hit_BIC) "both" else if (hit_ELBO) "ELBO only" else
    if (hit_BIC) "BIC only" else "none"
  any_count_usable <- ELBO$usable || BIC$usable
  layer <- if (resolution == "persistent" && Kp %in% C20) "L1" else
    if (resolution == "persistent" && any_count_usable) "L2" else
    if (resolution == "nonpersistent") "L3" else
      "unclassified"
  gap <- if (resolution == "persistent" && length(C20))
    min(abs(C20 - Kp)) else NA_real_

  list(Khat_ELBO = ELBO$Khat, Khat_BIC = BIC$Khat, C20 = C20,
       count_usable = c(ELBO = ELBO$usable, BIC = BIC$usable),
       support = support, Kp = Kp, source_state = setNames(state, sources),
       resolution = resolution, layer = layer, g_CP = gap)
}

profile_row <- function(x, spec) {
  z <- read_sweep(x, spec$phi_cut, spec$r, profile_sources)
  data.frame(profile = spec$profile, practical_default = spec$practical_default,
             Khat_ELBO = z$Khat_ELBO, Khat_BIC = z$Khat_BIC,
             C20 = if (length(z$C20)) paste0("{", paste(z$C20,
               collapse = ","), "}") else "{}",
             support = z$support, Kp = z$Kp, layer = z$layer,
             g_CP = z$g_CP, row.names = NULL)
}

profile_results <- do.call(rbind, lapply(seq_len(nrow(profile_specs)),
  function(i) profile_row(p, profile_specs[i, ])))
profile_results

## A declared source at the upper edge cannot persist without its endpoint.
short_window <- read_sweep(p, phi_cut = .85, r = 1L, sources = 7L)
data.frame(profile = ".85/r1", declared_source = 7L,
           required_endpoint = 8L, resolution = short_window$resolution,
           layer = short_window$layer)

## ----summary------------------------------------------------------------------
summary(p)

## ----plots, fig.width=7, fig.height=4.5---------------------------------------
plot(p, type = "objective", criterion = "ELBO")
plot(p, type = "gain", criterion = "BIC", pct = TRUE)
plot(p, type = "fit")

