R/fitcensBayes.R

Defines functions .cens_predict .cens_plot .cens_print fitcensBayes .cens_summarize .cens_exact .cens_mcmc .cens_truncated .cens_slice .cens_geometry .cens_transform .cens_start .cens_prior .cens_rician_survival .cens_survival .cens_density .cens_log_i0_scaled .cens_logneglog1m .cens_logadd .cens_log1mexp_exp .cens_data .cens_models .cens_control

Documented in fitcensBayes

# Separate censored-data interface; sampling does not call the complete-data fit.
# Censoring is independent/non-informative; status 0 means T > x (strictly,
# including for discrete distributions). Priors are those of the complete model.

.cens_control <- function(control) {
  defaults <- list(rhat_threshold = 1.01, ess_threshold = 400,
    slice_width = 1, slice_steps = 40L, max_shrink = 1000L,
    init_jitter = 2, max_init_tries = 100L, optimize_start = TRUE,
    warn_convergence = TRUE, store_callables = TRUE,
    entropy_tol = 1e-10, entropy_exact_limit = 2000L)
  if (!is.list(control) || (length(control) &&
      (is.null(names(control)) || any(!nzchar(names(control))) ||
       anyDuplicated(names(control))))) stop("'control' must be a uniquely named list.")
  bad <- setdiff(names(control), names(defaults))
  if (length(bad)) stop("Unknown control option: ", paste(bad, collapse = ", "))
  defaults[names(control)] <- control
  for (nm in c("rhat_threshold", "ess_threshold", "slice_width", "init_jitter", "entropy_tol")) {
    z <- defaults[[nm]]
    if (!is.numeric(z) || length(z) != 1L || !is.finite(z) || z <= 0)
      stop("control$", nm, " must be a positive finite scalar.")
  }
  if (defaults$rhat_threshold < 1 || defaults$entropy_tol >= 0.1)
    stop("Invalid R-hat threshold or entropy tolerance.")
  for (nm in c("slice_steps", "max_shrink", "max_init_tries", "entropy_exact_limit"))
    defaults[[nm]] <- .fdb_scalar_count(defaults[[nm]], nm)
  for (nm in c("optimize_start", "warn_convergence", "store_callables"))
    if (!is.logical(defaults[[nm]]) || length(defaults[[nm]]) != 1L || is.na(defaults[[nm]]))
      stop("control$", nm, " must be TRUE or FALSE.")
  defaults
}

.cens_models <- function() {
  z <- fitdistrBayes_routes()
  names(z)[names(z) == "engine"] <- "complete_data_engine"
  names(z)[names(z) == "posterior_condition"] <- "exact_subset_sufficient_condition"
  z$exact_subset_sufficient_condition <- paste0(z$exact_subset_sufficient_condition,
    "; apply n to the exact-event subset, not to total/imputed n")
  ix <- z$model == "t" & z$prior == "independence-jeffreys"
  z$exact_subset_sufficient_condition[ix] <- "at least 2 pairwise distinct exact events; ties are not certified"
  z$censored_methods <- "direct, augmentation"
  z$censoring <- "right; status=1 event, status=0 means T>x"
  z$prior_origin <- "complete-data sampling model"
  z
}

.cens_data <- function(x, status, model, na.action) {
  if (!is.numeric(x) || is.complex(x) || !is.null(dim(x)) || !length(x))
    stop("'x' must be a nonempty numeric vector.")
  if (!(is.numeric(status) || is.logical(status)) || is.complex(status) || !is.null(dim(status)) ||
      length(status) != length(x)) stop("'status' must have the same length as x and contain 0/1.")
  missing <- is.na(x) | is.na(status)
  if (any(missing) && na.action == "fail") stop("Missing x/status values; use na.action='omit' to omit pairs.")
  omitted <- which(missing)
  x <- x[!missing]; status <- status[!missing]
  if (!length(x) || any(!is.finite(x)) || any(!is.finite(status)) ||
      any(!status %in% c(0, 1))) stop("Data must be finite, with status exactly 0 or 1.")
  discrete <- model %in% c("geometric", "Poisson", "negative binomial")
  positive <- model %in% c("gamma", "weibull", "lognormal", "chi-squared", "frechet",
                          "lomax", "nakagami", "rician", "weighted lindley",
                          "exponential-logarithmic", "exponential")
  if (discrete && (any(x < 0) || any(x != floor(x))))
    stop("Discrete observations/censoring limits must be nonnegative integers; status=0 means T>x.")
  if (positive && any(x < 0)) stop("This model requires nonnegative observed times.")
  if (positive && model != "exponential" && any(x[status == 1] <= 0))
    stop("Exact observations must be strictly positive for this model.")
  if (model == "beta" && (any(x < 0 | x >= 1) || any(x[status == 1] <= 0)))
    stop("Beta events must lie in (0,1), and censoring limits in [0,1).")
  list(x = x, status = as.integer(status), omitted = omitted)
}

# log(1-exp(-exp(a))), without underflow in the outer log.
.cens_log1mexp_exp <- function(a) {
  out <- a
  middle <- is.finite(a) & a > -35 & a < 7
  out[middle] <- log(-expm1(-exp(a[middle])))
  out[a >= 7] <- 0
  out
}
.cens_logadd <- function(a, b) {
  z <- pmax(a, b)
  ans <- z + log1p(exp(-abs(a - b)))
  ans[is.infinite(z) & z < 0] <- -Inf
  ans
}
# log(-log(1-exp(a))), a<=0; needed for EL survival and inverse tails.
.cens_logneglog1m <- function(a) {
  out <- a
  use <- a > -35
  out[use] <- log(-log(-expm1(a[use])))
  out
}

.cens_log_i0_scaled <- function(log_argument) {
  out <- rep(NA_real_, length(log_argument))
  ordinary <- !is.na(log_argument) & log_argument <= log(1000)
  out[ordinary] <- log(besselI(exp(log_argument[ordinary]), 0, expon.scaled = TRUE))
  large <- !is.na(log_argument) & log_argument > log(1000)
  r <- exp(-log_argument[large])
  # DLMF 10.40.1, nu=0: positive asymptotic coefficients. At x>1000
  # the omitted sixth term is below double-precision relative accuracy.
  correction <- r/8 + 9*r^2/128 + 225*r^3/3072 +
    11025*r^4/98304 + 893025*r^5/3932160
  out[large] <- -0.5 * (log(2*pi) + log_argument[large]) + log1p(correction)
  out
}

.cens_density <- function(model, x, th, fixed) {
  switch(model,
    beta = stats::dbeta(x, th["shape1"], th["shape2"], log = TRUE),
    cauchy = stats::dcauchy(x, th["location"], th["scale"], log = TRUE),
    `chi-squared` = stats::dchisq(x, th["df"], log = TRUE),
    exponential = stats::dexp(x, th["rate"], log = TRUE),
    gamma = stats::dgamma(x, th["shape"], rate = th["rate"], log = TRUE),
    geometric = stats::dgeom(x, th["prob"], log = TRUE),
    lognormal = stats::dlnorm(x, th["meanlog"], th["sdlog"], log = TRUE),
    logistic = stats::dlogis(x, th["location"], th["scale"], log = TRUE),
    `negative binomial` = stats::dnbinom(x, size = fixed$size, mu = th["mu"], log = TRUE),
    normal = stats::dnorm(x, th["mean"], th["sd"], log = TRUE),
    Poisson = stats::dpois(x, th["lambda"], log = TRUE),
    t = stats::dt((x - th["location"]) / th["scale"],
                 if (is.null(fixed$df)) th["df"] else fixed$df, log = TRUE) - log(th["scale"]),
    weibull = stats::dweibull(x, th["shape"], th["scale"], log = TRUE),
    frechet = {
      a <- th["shape"]; b <- th["scale"]
      log(a) + log(b) - (a + 1) * log(x) - exp(log(b) - a * log(x))
    },
    gumbel = {
      z <- (x - th["location"]) / th["scale"]
      -log(th["scale"]) - z - exp(-z)
    },
    lomax = log(th["shape"]) - log(th["scale"]) -
      (th["shape"] + 1) * .fdb_softplus(log(x) - log(th["scale"])),
    nakagami = stats::dgamma(x^2, th["shape"], rate = th["shape"] / th["spread"],
                            log = TRUE) + log(2) + log(x),
    `exponential-logarithmic` = {
      lp <- log(th["theta"]); lq <- log(-expm1(lp))
      log(th["rate"]) + lq - log(-lp) - th["rate"] * x -
        log(-expm1(lq - th["rate"] * x))
    },
    rician = {
      s <- th["scale"]; a <- th["noncentrality"]
      log(x) - 2 * log(s) + .cens_log_i0_scaled(log(a) + log(x) - 2*log(s)) -
        0.5 * ((x - a) / s)^2
    },
    `weighted lindley` = {
      a <- th["phi"]; b <- th["lambda"]
      (a + 1) * log(b) - .fdb_logsumexp(log(c(a, b))) - lgamma(a) +
        (a - 1) * log(x) + log1p(x) - b * x
    })
}

.cens_survival <- function(model, x, th, fixed) {
  # All calculations use the upper tail directly, not 1 - p(...).
  ans <- switch(model,
    beta = stats::pbeta(x, th["shape1"], th["shape2"], lower.tail = FALSE, log.p = TRUE),
    cauchy = stats::pcauchy(x, th["location"], th["scale"], lower.tail = FALSE, log.p = TRUE),
    `chi-squared` = stats::pchisq(x, th["df"], lower.tail = FALSE, log.p = TRUE),
    exponential = stats::pexp(x, th["rate"], lower.tail = FALSE, log.p = TRUE),
    gamma = stats::pgamma(x, th["shape"], rate = th["rate"], lower.tail = FALSE, log.p = TRUE),
    geometric = stats::pgeom(x, th["prob"], lower.tail = FALSE, log.p = TRUE),
    lognormal = stats::plnorm(x, th["meanlog"], th["sdlog"], lower.tail = FALSE, log.p = TRUE),
    logistic = stats::plogis(x, th["location"], th["scale"], lower.tail = FALSE, log.p = TRUE),
    `negative binomial` = stats::pnbinom(x, size = fixed$size, mu = th["mu"], lower.tail = FALSE, log.p = TRUE),
    normal = stats::pnorm(x, th["mean"], th["sd"], lower.tail = FALSE, log.p = TRUE),
    Poisson = stats::ppois(x, th["lambda"], lower.tail = FALSE, log.p = TRUE),
    t = stats::pt((x - th["location"]) / th["scale"],
                 if (is.null(fixed$df)) th["df"] else fixed$df, lower.tail = FALSE, log.p = TRUE),
    weibull = stats::pweibull(x, th["shape"], th["scale"], lower.tail = FALSE, log.p = TRUE),
    frechet = .cens_log1mexp_exp(log(th["scale"]) - th["shape"] * log(pmax(x, 0))),
    gumbel = .cens_log1mexp_exp(-(x - th["location"]) / th["scale"]),
    lomax = -th["shape"] * .fdb_softplus(log(pmax(x, 0)) - log(th["scale"])),
    nakagami = stats::pgamma(pmax(x, 0)^2, th["shape"], rate = th["shape"] / th["spread"],
                            lower.tail = FALSE, log.p = TRUE),
    `exponential-logarithmic` = {
      lp <- log(th["theta"])
      .cens_logneglog1m(log(-expm1(lp)) - th["rate"] * pmax(x, 0)) - log(-lp)
    },
    rician = .cens_rician_survival(x, th),
    `weighted lindley` = {
      la <- log(th["phi"]); lb <- log(th["lambda"])
      denominator <- .fdb_logsumexp(c(la, lb))
      .cens_logadd(lb - denominator + stats::pgamma(x, th["phi"], rate = th["lambda"],
                       lower.tail = FALSE, log.p = TRUE),
                   la - denominator + stats::pgamma(x, th["phi"] + 1, rate = th["lambda"],
                       lower.tail = FALSE, log.p = TRUE))
    })
  # The boundary is exact, including for mixture survival formulas whose
  # floating-point weights otherwise need not sum to exactly one.
  if (model %in% c("beta", "chi-squared", "exponential", "gamma", "lognormal",
      "weibull", "frechet", "lomax", "nakagami", "exponential-logarithmic",
      "rician", "weighted lindley")) ans[x <= 0] <- 0
  if (any(is.nan(ans)) || any(ans > 1e-12)) return(rep(NaN, length(x)))
  pmin(ans, 0)
}

.cens_rician_survival <- function(x, th) {
  # R's upper noncentral chi-square tail may lose precision for large ncp.
  # In those cases integrate the scaled Rician density on standardized y.
  rho <- th["noncentrality"] / th["scale"]
  q <- pmax(x, 0) / th["scale"]
  if (!is.finite(rho) || any(!is.finite(q)))
    stop("Rician standardized parameters exceeded numerical range.")
  answer <- if (rho^2 <= 1e5) suppressWarnings(stats::pchisq(q^2, df = 2, ncp = rho^2,
                                         lower.tail = FALSE, log.p = TRUE)) else rep(NA_real_, length(q))
  fallback <- which((rho^2 >= 80 & answer < log(1e-8)) | !is.finite(answer))
  for (i in fallback) {
    if (q[i] <= 0) { answer[i] <- 0; next }
    lower_z <- q[i] - rho
    logd <- function(z) {
      y <- rho + z
      ans <- rep(-Inf, length(z)); valid <- y > 0 & is.finite(y)
      ans[valid] <- log(y[valid]) + .cens_log_i0_scaled(log(rho) + log(y[valid])) - z[valid]^2 / 2
      ans
    }
    offset <- logd(max(lower_z, 0))
    f <- function(z) exp(logd(z) - offset)
    integral <- function(a,b) stats::integrate(f, a, b, rel.tol = 1e-8, subdivisions = 300L)$value
    # Center around the noncentrality so a narrow peak is not missed when
    # rho is large; split the lower half at -8 rather than discarding it.
    val <- tryCatch({
      if (lower_z >= 0) {
        h <- 1 / max(1, lower_z)
        stats::integrate(function(t) f(lower_z + h*t), 0, Inf,
                         rel.tol = 1e-8, subdivisions = 300L)$value * h
      } else {
        integral(0, Inf) + integral(max(lower_z, -8), 0) +
          if (lower_z < -8) integral(lower_z, -8) else 0
      }
    }, error = function(e) NA_real_)
    if (!is.finite(val) || val <= 0)
      stop("Rician survival could not be evaluated accurately at this parameter state.")
    answer[i] <- offset + log(val)
  }
  answer[x <= 0] <- 0
  answer
}

.cens_prior <- function(model, prior, th, fixed, control, rician_logq) {
  if (model %in% c("normal", "lognormal", "logistic", "gumbel", "cauchy") ||
      (model == "t" && !is.null(fixed$df))) {
    scale_name <- switch(model, normal = "sd", lognormal = "sdlog", "scale")
    return(-(if (prior == "jeffreys") 2 else 1) * log(th[scale_name]))
  }
  switch(model,
    beta = {
      a <- th["shape1"]; b <- th["shape2"]; ab <- trigamma(a + b)
      det <- (trigamma(a) - ab) * (trigamma(b) - ab) - ab^2
      if (!is.finite(det) || det <= 0) -Inf else 0.5 * log(det)
    },
    `chi-squared` = 0.5 * log(trigamma(th["df"] / 2)),
    exponential = (if (prior == "mdi") 1 else -1) * log(th["rate"]),
    gamma = {
      a <- th["shape"]
      h <- switch(prior,
        jeffreys = 0.5 * log(.fdb_gamma_joint_term(a)),
        `first-rule` = -log(a),
        `reference-shape` = 0.5 * (log(.fdb_gamma_joint_term(a)) - log(a)),
        `reference-rate` = 0.5 * log(trigamma(a)))
      h - log(th["rate"])
    },
    geometric = {
      p <- th["prob"]
      if (prior == "mdi") log(p) + (1 - p) / p * log1p(-p) else -log(p) - 0.5 * log1p(-p)
    },
    `negative binomial` = {
      if (prior == "mdi") -.fdb_nbinom_entropy(fixed$size, th["mu"], control) else
        -0.5 * (log(th["mu"]) + log(fixed$size + th["mu"]))
    },
    Poisson = if (prior == "mdi") -.fdb_poisson_entropy(th["lambda"], control) else -0.5 * log(th["lambda"]),
    t = -log(th["scale"]) + 0.5 * (log(th["df"]) - log(th["df"] + 3) + log(.fdb_t_B(th["df"]))),
    weibull = -log(th["scale"]) - if (prior == "reference") log(th["shape"]) else 0,
    frechet = -sum(log(th[c("shape", "scale")])),
    lomax = -log(th["scale"]) - 0.5 * log(th["shape"]) - log1p(th["shape"]) - 0.5 * log(th["shape"] + 2),
    nakagami = 0.5 * (log(.fdb_gamma_joint_term(th["shape"])) -
      if (prior == "reference") log(th["shape"]) else 0) - log(th["spread"]),
    `exponential-logarithmic` = {
      lp <- log(th["theta"]); terms <- .fdb_el_prior_terms(lp)
      lr <- log(th["rate"])
      switch(prior,
        jeffreys = -lr + 0.5 * terms["log_zeta"],
        mdi = lr + 0.5 * (log(-expm1(lp)) - lp - log(-lp)) + exp(terms["log_dilog"]) / lp,
        reference =, `reference-theta` = -lr + 0.5 * (terms["log_zeta"] + log(-lp) - terms["log_dilog"]),
        `reference-rate` = -lr + 0.5 * terms["log_information_theta"])
    },
    rician = 0.5 * rician_logq(2 * (log(th["noncentrality"]) - log(th["scale"]))) - 2 * log(th["scale"]),
    `weighted lindley` = .fdb_weighted_lindley_log_prior(th["lambda"], th["phi"], prior))
}

.cens_start <- function(built, model, exact, start) {
  env <- environment(built$loglik)
  if (exists("sv", env, inherits = FALSE)) {
    initial <- get("sv", env)
  } else {
    initial <- switch(model,
      exponential = c(rate = 1 / mean(exact)),
      geometric = c(prob = (length(exact) + 0.5) / (length(exact) + sum(exact) + 1)),
      Poisson = c(lambda = (sum(exact) + 0.5) / length(exact)),
      `negative binomial` = c(mu = mean(exact) + 0.5 / length(exact)),
      normal = c(mean = mean(exact), sd = sqrt(mean((exact - mean(exact))^2))),
      lognormal = c(meanlog = mean(log(exact)), sdlog = sqrt(mean((log(exact) - mean(log(exact)))^2))))
  }
  initial <- initial[built$parameters]
  if (!is.null(start)) {
    z <- if (is.list(start)) unlist(start, use.names = TRUE) else start
    if (!is.numeric(z) || !length(z) || any(!is.finite(z))) stop("'start' must contain finite numeric values.")
    if (is.null(names(z))) {
      if (length(z) != length(initial)) stop("Unnamed 'start' has incorrect length.")
      names(z) <- names(initial)
    }
    if (any(!nzchar(names(z))) || anyDuplicated(names(z)) || any(!names(z) %in% names(initial)))
      stop("Invalid or duplicated starting parameter names.")
    initial[names(z)] <- z
  }
  real <- names(initial) %in% c("location", "mean", "meanlog")
  probs <- names(initial) %in% c("prob", "theta")
  if (any(!is.finite(initial)) || any(initial[!real] <= 0) || any(initial[probs] >= 1))
    stop("Starting values lie outside parameter support.")
  initial
}

.cens_transform <- function(model, initial) {
  pars <- names(initial)
  real <- pars %in% c("location", "mean", "meanlog")
  prob <- pars %in% c("theta", "prob")
  loc <- which(real)
  loc_center <- if (length(loc)) initial[loc] else numeric()
  scale_name <- intersect(c("scale", "sd", "sdlog"), pars)
  loc_scale <- if (length(loc)) initial[scale_name] else 1
  to <- function(th) {
    u <- numeric(length(th))
    u[!real & !prob] <- log(th[!real & !prob])
    u[real] <- (th[real] - loc_center) / loc_scale
    u[prob] <- stats::qlogis(th[prob])
    if (model == "gamma") u <- c(log(th["shape"] / th["rate"]), log(th["shape"]))
    if (model == "beta") u <- c(log(th["shape1"] / th["shape2"]), log(sum(th)))
    if (model == "lomax") u <- c(log(th["shape"]), log(th["scale"] / th["shape"]))
    if (model == "weighted lindley") {
      a <- th["phi"]; b <- th["lambda"]
      u <- c(log(a * (b + a + 1) / (b * (b + a))), log(a))
      if (!is.finite(u[1L])) u[1L] <- log(a) +
        .fdb_logsumexp(c(log(b), log(a), 0)) - log(b) -
        .fdb_logsumexp(c(log(b), log(a)))
    }
    if (model == "gamma" && !is.finite(u[1L])) u[1L] <- log(th["shape"]) - log(th["rate"])
    if (model == "beta") {
      if (!is.finite(u[1L])) u[1L] <- log(th["shape1"]) - log(th["shape2"])
      if (!is.finite(u[2L])) u[2L] <- .fdb_logsumexp(log(th))
    }
    if (model == "lomax" && !is.finite(u[2L])) u[2L] <- log(th["scale"]) - log(th["shape"])
    unname(u)
  }
  from <- function(u) {
    th <- exp(u)
    th[real] <- loc_center + loc_scale * u[real]
    th[prob] <- stats::plogis(u[prob])
    names(th) <- pars
    jac <- sum(u[!real & !prob]) +
      sum(stats::plogis(u[prob], log.p = TRUE) + stats::plogis(u[prob], lower.tail = FALSE, log.p = TRUE)) +
      length(loc) * log(loc_scale)
    if (model == "gamma") {
      th <- c(shape = exp(u[2]), rate = exp(u[2] - u[1])); jac <- 2 * u[2] - u[1]
    }
    if (model == "beta") {
      p <- stats::plogis(u[1]); th <- c(shape1 = p * exp(u[2]), shape2 = (1 - p) * exp(u[2]))
      if (p == 0 || p == 1 || any(!is.finite(th))) {
        th <- c(shape1 = exp(stats::plogis(u[1], log.p = TRUE) + u[2]),
                shape2 = exp(stats::plogis(u[1], lower.tail = FALSE, log.p = TRUE) + u[2]))
      }
      jac <- sum(log(th))
    }
    if (model == "lomax") {
      th <- c(shape = exp(u[1]), scale = exp(u[1] + u[2])); jac <- 2 * u[1] + u[2]
    }
    if (model == "weighted lindley") {
      phi <- exp(u[2]); mu <- exp(u[1]); lambda <- .fdb_weighted_lindley_lambda_from_mean(mu, phi)
      th <- c(lambda = lambda, phi = phi)
      ld <- .fdb_logsumexp(log(c(lambda, phi)))
      lA <- .fdb_logsumexp(c(2 * ld, log(2) + log(lambda), log(phi)))
      jac <- u[1] + u[2] - (log(phi) + lA - 2 * log(lambda) - 2 * ld)
    }
    list(theta = th, jacobian = jac)
  }
  list(to = to, from = from)
}

.cens_geometry <- function(target, init, optimize_start) {
  center <- init; p <- length(init); ok <- FALSE
  objective <- function(u) { z <- target(u); if (is.finite(z)) -z else 1e100 }
  if (!is.finite(target(init))) stop("The initial censored log posterior is not finite; supply 'start'.")
  if (optimize_start) {
    method <- if (p == 1L) "BFGS" else "Nelder-Mead"
    opt <- tryCatch(stats::optim(init, objective, method = method,
                     control = list(maxit = 1500, reltol = 1e-9)), error = function(e) NULL)
    if (!is.null(opt) && is.finite(target(opt$par)) && objective(opt$par) <= objective(init)) {
      center <- opt$par; ok <- opt$convergence == 0
    }
  }
  h <- tryCatch(stats::optimHess(center, objective), error = function(e) NULL)
  B <- diag(1, p)
  if (!is.null(h) && all(is.finite(h))) {
    es <- eigen((h + t(h)) / 2, symmetric = TRUE)
    if (all(es$values > 0)) {
      # Bounds affect proposal geometry only; parameter space is NOT truncated.
      B <- es$vectors %*% diag(pmin(10, pmax(1e-4, 1 / sqrt(es$values))), p)
    }
  }
  list(center = center, basis = B, optimizer_converged = ok)
}

.cens_slice <- function(current, target, width, steps, max_shrink) {
  level <- target(current) - stats::rexp(1)
  if (!is.finite(level)) stop("Non-finite current state in slice sampling.")
  left <- current - width * stats::runif(1); right <- left + width
  j <- floor(stats::runif(1, 0, steps)); k <- steps - 1L - j
  while (j > 0 && target(left) > level) { left <- left - width; j <- j - 1L }
  while (k > 0 && target(right) > level) { right <- right + width; k <- k - 1L }
  for (attempt in seq_len(max_shrink)) {
    proposal <- stats::runif(1, left, right)
    lp <- target(proposal)
    if (is.finite(lp) && lp >= level) return(proposal)
    if (proposal < current) left <- proposal else right <- proposal
    if (left == right) break
  }
  stop("Slice interval exhausted numerical resolution; no invalid draw was returned.")
}

.cens_truncated <- function(model, lower, th, fixed) {
  if (!length(lower)) return(numeric())
  lp <- .cens_survival(model, lower, th, fixed) - stats::rexp(length(lower))
  if (any(!is.finite(lp))) stop("Truncated sampling exceeded the representable tail range.")
  z <- switch(model,
    beta = stats::qbeta(lp, th["shape1"], th["shape2"], lower.tail = FALSE, log.p = TRUE),
    cauchy = stats::qcauchy(lp, th["location"], th["scale"], lower.tail = FALSE, log.p = TRUE),
    `chi-squared` = stats::qchisq(lp, th["df"], lower.tail = FALSE, log.p = TRUE),
    exponential = stats::qexp(lp, th["rate"], lower.tail = FALSE, log.p = TRUE),
    gamma = stats::qgamma(lp, th["shape"], rate = th["rate"], lower.tail = FALSE, log.p = TRUE),
    geometric = stats::qgeom(lp, th["prob"], lower.tail = FALSE, log.p = TRUE),
    lognormal = stats::qlnorm(lp, th["meanlog"], th["sdlog"], lower.tail = FALSE, log.p = TRUE),
    logistic = stats::qlogis(lp, th["location"], th["scale"], lower.tail = FALSE, log.p = TRUE),
    `negative binomial` = stats::qnbinom(lp, size = fixed$size, mu = th["mu"], lower.tail = FALSE, log.p = TRUE),
    normal = stats::qnorm(lp, th["mean"], th["sd"], lower.tail = FALSE, log.p = TRUE),
    Poisson = stats::qpois(lp, th["lambda"], lower.tail = FALSE, log.p = TRUE),
    t = th["location"] + th["scale"] * stats::qt(lp,
            if (is.null(fixed$df)) th["df"] else fixed$df, lower.tail = FALSE, log.p = TRUE),
    weibull = stats::qweibull(lp, th["shape"], th["scale"], lower.tail = FALSE, log.p = TRUE),
    frechet = exp((log(th["scale"]) - .cens_logneglog1m(lp)) / th["shape"]),
    gumbel = th["location"] - th["scale"] * .cens_logneglog1m(lp),
    lomax = th["scale"] * expm1(-lp / th["shape"]),
    nakagami = sqrt(stats::qgamma(lp, th["shape"], rate = th["shape"] / th["spread"],
                                  lower.tail = FALSE, log.p = TRUE)),
    `exponential-logarithmic` = {
      ltheta <- log(th["theta"])
      (log(-expm1(ltheta)) - .cens_log1mexp_exp(lp + log(-ltheta))) / th["rate"]
    },
    rician = {
      # Fast vectorized inversion in the moderate-ncp regime, checked against
      # our survival. Only problematic quantiles need a scalar root search.
      rho2 <- (th["noncentrality"] / th["scale"])^2
      ans <- if (is.finite(rho2) && rho2 < 80) suppressWarnings(th["scale"] *
        sqrt(stats::qchisq(lp, df = 2, ncp = rho2, lower.tail = FALSE, log.p = TRUE))) else rep(NA_real_, length(lp))
      error <- rep(Inf, length(lp))
      good <- is.finite(ans) & ans > lower
      if (any(good)) error[good] <- abs(.cens_survival(model, ans[good], th, fixed) - lp[good])
      bad <- which(!is.finite(error) | error > 1e-7)
      for (i in bad) {
        hi <- max(lower[i] + th["scale"], th["noncentrality"] + 8 * th["scale"])
        fun <- function(z) .cens_survival(model, z, th, fixed) - lp[i]
        for (j in seq_len(100L)) {
          if (fun(hi) <= 0) break
          hi <- 2 * hi + th["scale"]
        }
        ans[i] <- stats::uniroot(fun, c(lower[i], hi), tol = max(1e-12 * th["scale"], .Machine$double.eps))$root
      }
      ans
    },
    `weighted lindley` = {
      a <- th["phi"]; b <- th["lambda"]
      l1 <- log(b) + stats::pgamma(lower, a, rate = b, lower.tail = FALSE, log.p = TRUE)
      l2 <- log(a) + stats::pgamma(lower, a + 1, rate = b, lower.tail = FALSE, log.p = TRUE)
      component <- stats::runif(length(lower)) > exp(l1 - .cens_logadd(l1, l2))
      shape <- a + component
      ltail <- stats::pgamma(lower, shape, rate = b, lower.tail = FALSE, log.p = TRUE)
      stats::qgamma(ltail - stats::rexp(length(lower)), shape, rate = b, lower.tail = FALSE, log.p = TRUE)
    })
  if (any(!is.finite(z)) || any(z <= lower))
    stop("Truncated draws were not representable strictly above their censoring limits; no clipping was applied.")
  unname(z)
}

.cens_mcmc <- function(model, prior, x, status, fixed, initial, iter, warmup,
                       thin, chains, control, method, logprior) {
  transform <- .cens_transform(model, initial)
  event <- status == 1L; cens <- !event
  state <- new.env(parent = emptyenv()); state$data <- x
  evaluate <- function(u, augmented = FALSE) {
    if (any(!is.finite(u))) return(-Inf)
    tr <- suppressWarnings(transform$from(u)); th <- tr$theta
    if (any(!is.finite(th)) || any(th[!names(th) %in% c("location", "mean", "meanlog")] <= 0) ||
        any(th[names(th) %in% c("theta", "prob")] >= 1) || !is.finite(tr$jacobian)) return(-Inf)
    priorval <- suppressWarnings(logprior(th))
    if (length(priorval) != 1L || !is.finite(priorval)) return(-Inf)
    if (augmented) {
      ll <- suppressWarnings(.cens_density(model, state$data, th, fixed))
    } else {
      ll <- c(suppressWarnings(.cens_density(model, x[event], th, fixed)),
              suppressWarnings(.cens_survival(model, x[cens], th, fixed)))
    }
    ans <- sum(ll) + priorval + tr$jacobian
    if (is.finite(ans)) unname(ans) else -Inf
  }
  geometry <- .cens_geometry(evaluate, transform$to(initial),
                            control$optimize_start && is.null(attr(initial, "supplied")))
  target <- function(z) evaluate(as.numeric(geometry$center + geometry$basis %*% z),
                                  augmented = method == "augmentation")
  out <- vector("list", chains); p <- length(initial)
  starts <- matrix(NA_real_, chains, p, dimnames = list(NULL, names(initial)))
  for (ch in seq_len(chains)) {
    z <- rep(0, p)
    for (attempt in seq_len(control$max_init_tries)) {
      candidate <- stats::rnorm(p, sd = control$init_jitter)
      if (is.finite(evaluate(as.numeric(geometry$center + geometry$basis %*% candidate)))) {
        z <- candidate; break
      }
    }
    theta <- transform$from(as.numeric(geometry$center + geometry$basis %*% z))$theta
    starts[ch, ] <- theta
    saved <- matrix(NA_real_, floor((iter - warmup) / thin), p,
                    dimnames = list(NULL, names(initial)))
    pos <- 0L
    for (i in seq_len(iter)) {
      if (method == "augmentation") {
        theta <- transform$from(as.numeric(geometry$center + geometry$basis %*% z))$theta
        state$data[cens] <- .cens_truncated(model, x[cens], theta, fixed)
        # Exact blocked Normal-inverse-Gamma conditional, not a restarted fit.
        if (model %in% c("normal", "lognormal")) {
          y <- if (model == "normal") state$data else log(state$data)
          n <- length(y); ybar <- mean(y); q <- sum((y - ybar)^2)
          a <- if (prior == "jeffreys") 2 else 1
          s2 <- 1 / .fdb_rgamma_positive(1, (n + a - 2) / 2, rate = q / 2)
          theta <- stats::setNames(c(stats::rnorm(1, ybar, sqrt(s2 / n)), sqrt(s2)), names(initial))
          z <- as.numeric(solve(geometry$basis, transform$to(theta) - geometry$center))
        } else {
          for (j in sample.int(p)) {
            one <- function(v) { proposed <- z; proposed[j] <- v; target(proposed) }
            z[j] <- .cens_slice(z[j], one, control$slice_width,
                                control$slice_steps, control$max_shrink)
          }
        }
      } else {
        for (j in sample.int(p)) {
          one <- function(v) { proposed <- z; proposed[j] <- v; target(proposed) }
          z[j] <- .cens_slice(z[j], one, control$slice_width,
                              control$slice_steps, control$max_shrink)
        }
      }
      if (i > warmup && (i - warmup) %% thin == 0L) {
        pos <- pos + 1L
        saved[pos, ] <- transform$from(as.numeric(geometry$center + geometry$basis %*% z))$theta
      }
    }
    out[[ch]] <- saved
  }
  list(chains = out, independent = FALSE,
       engine = if (method == "augmentation") {
         if (model %in% c("normal", "lognormal")) "data augmentation + exact Normal-inverse-Gamma blocks" else
           "data augmentation + preconditioned slice-within-Gibbs"
       } else "observed-likelihood preconditioned slice sampling",
       initialization = list(source = if (is.null(attr(initial, "supplied"))) "automatic" else "user-supplied center",
         method = "complete-event classical start; observed-posterior geometry; dispersed chains",
         classical = initial, center = transform$from(geometry$center)$theta,
         chain_starts = starts, optimizer_converged = geometry$optimizer_converged))
}

.cens_exact <- function(model, prior, x, status, initial, iter, warmup, thin, chains, control) {
  m <- sum(status); A <- sum(x); nc <- sum(status == 0L)
  if (model %in% c("exponential", "geometric") && !is.finite(A))
    stop("The exact censored posterior sufficient statistic exceeded numerical range.")
  nsave <- floor((iter - warmup) / thin)
  if (model == "exponential") {
    out <- lapply(seq_len(chains), function(ch) matrix(.fdb_rgamma_positive(nsave,
      m + if (prior == "mdi") 2 else 0, rate = A), ncol = 1, dimnames = list(NULL, "rate")))
    label <- "exact censored Gamma posterior"
  } else if (model == "geometric") {
    out <- lapply(seq_len(chains), function(ch) matrix(stats::rbeta(nsave, m, A + nc + 0.5),
      ncol = 1, dimnames = list(NULL, "prob")))
    label <- "exact censored Beta posterior"
  } else {
    lx <- log(x); event_lx <- lx[status == 1]
    power <- m - if (prior == "jeffreys") 1 else 2
    target <- function(u) {
      k <- exp(u)
      if (!is.finite(k) || k <= 0) return(-Inf)
      ans <- power * u + (k - 1) * sum(event_lx) - m * .fdb_logsumexp(k * lx) + u
      if (is.finite(ans)) ans else -Inf
    }
    raw <- .fdb_slice_chains(target, log(initial["shape"]), iter, warmup, thin, chains,
      .fdb_merge_control(list(slice_width = control$slice_width, slice_steps = control$slice_steps,
                              init_jitter = control$init_jitter)))
    out <- lapply(raw, function(u) {
      k <- exp(u)
      logA <- vapply(k, function(v) .fdb_logsumexp(v * lx), numeric(1))
      sc <- .fdb_exp_positive((logA - log(.fdb_rgamma_positive(length(k), m))) / k)
      cbind(shape = k, scale = sc)
    })
    label <- "censored marginal slice + exact conditional Weibull scale"
  }
  list(chains = out, independent = model != "weibull", engine = label,
       initialization = list(source = if (model == "weibull") "classical event-subset start" else "not applicable",
                             method = label, center = initial))
}

.cens_summarize <- function(chains, independent) {
  out <- .fdb_summarize(chains, independent)
  if (!independent) for (j in seq_len(nrow(out))) {
    split <- .fdb_split_matrix(chains, j)
    scale <- .fdb_stable_sd(as.numeric(split))
    chain_moves <- vapply(chains, function(z) length(unique(z[, j])) > 1L, logical(1))
    if (!all(chain_moves) || !is.finite(scale) || scale <= 0) {
      out$rhat[j] <- Inf
      out$ess_mean[j] <- out$ess_bulk[j] <- out$ess_tail[j] <- 0
      out$mcse_mean[j] <- NA_real_
    } else {
      # Scaling protects mean ESS/MCSE from absolute variance tolerances when
      # users change units (rank-based R-hat and bulk/tail ESS are invariant).
      standardized <- (split - mean(split)) / scale
      out$ess_mean[j] <- .fdb_ess_matrix(standardized)
      out$mcse_mean[j] <- out$sd[j] / sqrt(out$ess_mean[j])
    }
  }
  out
}

fitcensBayes <- function(x, status, distr, prior = NULL, start = NULL, fixed = NULL,
                        iter = 4000L, warmup = floor(iter / 2), thin = 1L,
                        chains = 4L, seed = NULL, method = c("auto", "direct", "augmentation"),
                        na.action = c("fail", "omit"), control = list(), ...,
                        criteria = FALSE) {
  call <- match.call()
  if (length(list(...))) stop("Unused arguments: ", paste(names(list(...)), collapse = ", "))
  if (!is.character(distr) || length(distr) != 1L || is.na(distr))
    stop("This separate extension accepts the 20 registered distribution names, not custom densities yet.")
  model <- .fdb_model_name(distr)
  if (!is.character(prior) || length(prior) != 1L || is.na(prior) || !nzchar(prior))
    stop("Specify one registered prior name.")
  prior_key <- .fdb_prior_name(prior, model)
  method <- match.arg(method); na.action <- match.arg(na.action)
  control <- .cens_control(control)
  if (!identical(criteria, FALSE)) .fdb_criteria_preflight(criteria, control)
  iter <- .fdb_scalar_count(iter, "iter", lower = 20L)
  warmup <- .fdb_scalar_count(warmup, "warmup", lower = 0L)
  if (warmup >= iter) stop("'warmup' must be smaller than 'iter'.")
  thin <- .fdb_scalar_count(thin, "thin")
  chains <- .fdb_scalar_count(chains, "chains", lower = 2L)
  nsave <- floor((iter - warmup) / thin)
  if (nsave < 10L) stop("Keep at least 10 draws per chain after warmup/thinning.")
  if (!is.null(seed) && (!is.numeric(seed) || length(seed) != 1L || !is.finite(seed) ||
      seed < 0 || seed > .Machine$integer.max || seed != floor(seed))) stop("Invalid seed.")
  dat <- .cens_data(x, status, model, na.action)
  x <- dat$x; status <- dat$status; exact <- x[status == 1L]
  m <- length(exact); nc <- sum(status == 0L)
  # Check priors/fixed parameters before the event-subset propriety guard.
  # The upstream validator supplies detailed errors for aliases and fixed df.
  if (!m && !(model == "exponential" && prior_key == "mdi" && sum(x) > 0))
    stop("Posterior propriety is not certified with zero events for this route; no sampling was performed.")
  guard_data <- if (!m || (model == "exponential" && sum(exact) == 0 && sum(x) > 0)) 1 else exact
  built <- tryCatch(.fdb_build_builtin(guard_data, model, prior_key, fixed, start),
    error = function(e) stop("Censored-model validation failed. The complete-event sufficient criterion or model/prior restriction was not met: ",
                            conditionMessage(e), ". This does not by itself establish impropriety of every censored case.", call. = FALSE))
  fixed <- built$fixed
  if (model == "exponential" && (!is.finite(sum(x)) || sum(x) <= 0))
    stop("Exponential posterior requires a finite positive total observed time.")
  initial <- .cens_start(built, model, guard_data, start)
  if (!is.null(start)) attr(initial, "supplied") <- TRUE
  rq <- if (model == "rician") .fdb_rician_logq_factory() else NULL
  logprior <- function(th) .cens_prior(model, prior_key, th, fixed, control, rq)
  exact_route <- model %in% c("exponential", "weibull") ||
    (model == "geometric" && prior_key != "mdi")
  selected <- if (method == "auto") "direct" else method
  sampled <- .fdb_with_seed(seed, {
    if (nc == 0L && method == "auto") {
      up_control <- control[intersect(names(control), names(.fdb_merge_control(list())))]
      built$sampler(iter, warmup, thin, chains, .fdb_merge_control(up_control))
    } else if (exact_route && selected == "direct") {
      .cens_exact(model, prior_key, x, status, initial, iter, warmup, thin, chains, control)
    } else {
      .cens_mcmc(model, prior_key, x, status, fixed, initial, iter, warmup, thin,
                 chains, control, selected, logprior)
    }
  })
  chain_list <- sampled$chains
  if (length(chain_list) != chains || any(vapply(chain_list, nrow, integer(1)) != nsave) ||
      any(vapply(chain_list, function(z) any(!is.finite(z)), logical(1)))) stop("Invalid posterior chains; fit not returned.")
  positive_parameters <- setdiff(built$parameters, c("location", "mean", "meanlog"))
  probability_parameters <- intersect(built$parameters, c("theta", "prob"))
  if (any(vapply(chain_list, function(z)
    any(z[, positive_parameters, drop=FALSE] <= 0) ||
    any(z[, probability_parameters, drop=FALSE] >= 1), logical(1))))
    stop("Posterior draws reached a nonrepresentable parameter boundary; no clipping was applied.")
  posterior <- .cens_summarize(chain_list, independent = sampled$independent)
  moments <- .fdb_moment_status(model, prior_key, built$parameters, length(guard_data), fixed, guard_data)
  if (nc) {
    # Dominance proves existence, never nonexistence, of moments. Unproved
    # censored moments are NA, not asserted infinite from complete-data tests.
    for (col in c("mean_exists", "variance_exists")) moments[[col]][!moments[[col]] %in% TRUE] <- NA
    moments$note <- "Finite moments transferred from exact-event subset when TRUE; NA means not certified under censoring."
    if (model == "exponential") {
      moments$mean_exists <- moments$variance_exists <- TRUE
      moments$note <- "Exact Gamma posterior: all positive rate moments are finite."
    }
    if (model == "weibull") {
      idx <- moments$parameter == "scale"
      moments[idx, c("mean_exists", "variance_exists")] <- FALSE
      moments$note[idx] <- "Under the censored Weibull conditional Gamma form, positive scale moments diverge as shape approaches zero."
    }
  }
  posterior$mean_exists <- moments$mean_exists
  posterior$variance_exists <- moments$variance_exists
  posterior$moment_note <- moments$note
  posterior$mean[!posterior$mean_exists %in% TRUE] <- NA_real_
  posterior$sd[!posterior$variance_exists %in% TRUE] <- NA_real_
  posterior$mcse_mean[!(posterior$mean_exists %in% TRUE & posterior$variance_exists %in% TRUE)] <- NA_real_
  good <- is.finite(posterior$rhat) & posterior$rhat <= control$rhat_threshold &
    is.finite(posterior$ess_bulk) & posterior$ess_bulk >= control$ess_threshold &
    is.finite(posterior$ess_tail) & posterior$ess_tail >= control$ess_threshold
  passed <- sampled$independent || all(good)
  message <- if (sampled$independent) "Independent exact simulation; MCMC convergence is not applicable." else
    if (passed) "Rank-normalized split/folded R-hat and bulk/tail ESS targets met; this is not a proof of convergence." else
      paste0("Diagnostic targets not met for: ", paste(posterior$parameter[!good], collapse = ", "), ". Increase iter and inspect chains.")
  propriety <- if (model == "exponential") "proper: exact censored Gamma posterior" else
    if (!nc) built$propriety else paste0("proper by exact-event subset dominance; m=", m, "; ", built$propriety)
  answer <- list(call = call,
    model = list(name = model, parameters = built$parameters, fixed = fixed, n = length(x)),
    prior = list(input = prior, key = prior_key, label = built$prior_label, kernel = built$prior_kernel,
                 origin = "complete-data sampling model", posterior_propriety = propriety),
    censoring = list(type = "right", events = m, censored = nc, fraction = nc / length(x),
                     assumption = "independent/non-informative; status=0 means T>x"),
    initialization = sampled$initialization,
    engine = list(algorithm = sampled$engine, requested = method, iterations = iter, warmup = warmup,
                  thin = thin, chains = chains, saved_per_chain = nsave, seed = seed),
    estimates = stats::setNames(posterior$median, posterior$parameter), summary = posterior,
    moment_status = moments,
    diagnostics = list(converged = passed, exact_or_independent = sampled$independent,
      max_rhat = max(posterior$rhat), min_ess_bulk = min(posterior$ess_bulk), min_ess_tail = min(posterior$ess_tail),
      rhat_threshold = control$rhat_threshold, ess_threshold = control$ess_threshold, messages = message),
    chains = chain_list, draws = .fdb_long_draws(chain_list), data = x, status = status,
    omitted = dat$omitted, control = control)
  if (control$store_callables) {
    answer$.loglik <- function(th) {
      out <- numeric(length(x)); event <- status == 1
      out[event] <- .cens_density(model, x[event], th, fixed)
      out[!event] <- .cens_survival(model, x[!event], th, fixed)
      out
    }
    answer$.rng <- built$rng
    answer$.survival <- function(times, th) .cens_survival(model, times, th, fixed)
    answer$.truncated <- function(lower, th) .cens_truncated(model, lower, th, fixed)
  }
  class(answer) <- "fitcensBayes"
  if (!identical(criteria, FALSE)) {
    answer$criteria <- .fdb_criteria_after_fit(answer, criteria)
  }
  if (!passed && control$warn_convergence) warning(message, call. = FALSE)
  answer
}

.cens_print <- function(x, digits = 4L, ...) {
  cat("\nObjective Bayesian fit with right censoring\n")
  cat("Model:", x$model$name, " | Prior:", x$prior$label, "\n")
  cat("Events:", x$censoring$events, " | Censored:", x$censoring$censored,
      sprintf(" (%.1f%%)\n", 100 * x$censoring$fraction))
  cat("Algorithm:", x$engine$algorithm, "\n")
  cat("Diagnostic:", if (x$diagnostics$converged) "targets met" else "attention required", "\n\n")
  print(x$summary[, c("parameter", "mean", "sd", "q2.5", "median", "q97.5", "rhat", "ess_bulk", "ess_tail")],
        row.names = FALSE, digits = digits)
  if (anyNA(x$summary$mean) || anyNA(x$summary$sd))
    cat("\nNA moments are infinite or not certified. coef() returns posterior medians.\n")
  invisible(x)
}

.cens_plot <- function(x, type, pars, ...) {
  if (!is.character(pars) || !length(pars) || any(!pars %in% x$model$parameters))
    stop("'pars' must name fitted parameters.")
  if (type == "pairs") {
    if (length(pars) < 2L) stop("Pairs plots require at least two parameters.")
    graphics::pairs(x$draws[, pars, drop = FALSE], ...)
    return(invisible(x))
  }
  old <- graphics::par(no.readonly = TRUE)
  on.exit(graphics::par(old))
  count <- length(pars) * if (type == "acf") length(x$chains) else 1L
  graphics::par(mfrow = grDevices::n2mfrow(count), mar = c(4.2, 5, 2.5, 1.2))
  for (p in pars) {
    if (type == "trace") {
      series <- lapply(x$chains, function(z) z[, p])
      args <- list(x = seq_along(series[[1]]), y = series[[1]], type = "l", col = 1,
                   ylim = range(unlist(series)), xlab = "Saved iteration", ylab = p,
                   main = paste("Trace:", p))
      do.call(graphics::plot, utils::modifyList(args, list(...)))
      for (ch in seq.int(2L, length(series))) graphics::lines(series[[ch]], col = ch)
    } else if (type == "density") {
      dens <- lapply(x$chains, function(z) stats::density(z[, p]))
      args <- list(x = dens[[1]], col = 1, xlab = p, main = paste("Posterior:", p),
                   xlim = range(vapply(dens, function(z) range(z$x), numeric(2))),
                   ylim = range(vapply(dens, function(z) range(z$y), numeric(2))))
      do.call(graphics::plot, utils::modifyList(args, list(...)))
      for (ch in seq.int(2L, length(dens))) graphics::lines(dens[[ch]], col = ch)
    } else {
      for (ch in seq_along(x$chains)) {
        args <- list(x = x$chains[[ch]][, p], main = sprintf("ACF: %s, chain %d", p, ch))
        do.call(stats::acf, utils::modifyList(args, list(...)))
      }
    }
  }
  invisible(x)
}

.cens_predict <- function(object, type, times, draws, size, seed) {
  draws <- .fdb_scalar_count(draws, "draws"); size <- .fdb_scalar_count(size, "size")
  if (is.null(object$.rng)) stop("Prediction callbacks were not stored.")
  if (type == "survival" && (!is.numeric(times) || !length(times) || any(!is.finite(times))))
    stop("Supply finite numeric 'times' for survival prediction.")
  if (type == "impute" && !any(object$status == 0)) stop("There are no censored observations to impute.")
  if (type != "survival" && !is.null(times)) stop("'times' is only used for type='survival'.")
  .fdb_with_seed(seed, {
    idx <- sample.int(nrow(object$draws), draws, replace = draws > nrow(object$draws))
    cols <- switch(type, response = size, survival = length(times), impute = sum(object$status == 0))
    ans <- matrix(NA_real_, draws, cols)
    for (i in seq_len(draws)) {
      th <- unlist(object$draws[idx[i], object$model$parameters, drop = FALSE], use.names = TRUE)
      ans[i, ] <- switch(type, response = object$.rng(th, size),
                        survival = exp(object$.survival(times, th)),
                        impute = object$.truncated(object$data[object$status == 0], th))
    }
    if (any(!is.finite(ans))) stop("Prediction exceeded numerical range; no clipping was applied.")
    colnames(ans) <- switch(type,
      response = paste0("replicate[", seq_len(cols), "]"),
      survival = paste0("S(", times, ")"),
      impute = paste0("observation[", which(object$status == 0), "]"))
    ans
  })
}

Try the fitdistrBayes package in your browser

Any scripts or data that you put into this service are public.

fitdistrBayes documentation built on Sept. 21, 2026, 5:08 p.m.