## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4.1,
  dpi = 144
)
library(drmTMB)

spatial_guide_theme <- function() {
  ggplot2::theme_minimal(base_size = 11) +
    ggplot2::theme(
      panel.grid.minor = ggplot2::element_blank(),
      axis.title = ggplot2::element_text(colour = "grey15"),
      axis.text = ggplot2::element_text(colour = "grey25"),
      plot.title = ggplot2::element_text(
        face = "bold",
        colour = "grey10",
        margin = ggplot2::margin(b = 4)
      ),
      plot.subtitle = ggplot2::element_text(
        colour = "grey30",
        margin = ggplot2::margin(b = 8)
      ),
      legend.position = "bottom"
    )
}

spatial_eye_theme <- function() {
  spatial_guide_theme() +
    ggplot2::theme(
      panel.grid.major.y = ggplot2::element_blank(),
      legend.position = "none"
    )
}

simulate_spatial_guide_data <- function(seed = 20260534) {
  set.seed(seed)
  sites <- paste0("site_", seq_len(12))
  theta <- seq(0, 1.8 * pi, length.out = length(sites))
  coords <- data.frame(
    x = cos(theta) + seq_along(sites) / 30,
    y = sin(theta)
  )
  rownames(coords) <- sites

  distance <- as.matrix(dist(coords))
  range <- stats::median(distance[distance > 0])
  spatial_cov <- exp(-distance / range)
  diag(spatial_cov) <- diag(spatial_cov) + 1e-6
  spatial_intercept <- as.vector(
    t(chol(spatial_cov)) %*% rnorm(length(sites), sd = 0.45)
  )
  spatial_slope <- as.vector(
    t(chol(spatial_cov)) %*% rnorm(length(sites), sd = 0.20)
  )
  names(spatial_intercept) <- sites
  names(spatial_slope) <- sites

  site <- rep(sites, each = 6)
  temp <- rnorm(length(site))
  depth <- rnorm(length(site))
  sigma <- exp(-1.25 + 0.15 * depth)
  y <- 0.4 + 0.35 * temp + 0.20 * depth +
    spatial_intercept[site] + spatial_slope[site] * depth +
    rnorm(length(site), sd = sigma)

  list(
    data = data.frame(y = y, temp = temp, depth = depth, site = site),
    coords = coords
  )
}

simulate_spatial_q2_guide_data <- function(seed = 260805001L) {
  set.seed(seed)
  sites <- paste0("site_", seq_len(36))
  theta <- seq(0, 1.5 * pi, length.out = length(sites))
  coords <- data.frame(
    x = cos(theta) + seq_along(sites) / (3 * length(sites)),
    y = sin(theta)
  )
  rownames(coords) <- sites

  distance <- as.matrix(dist(coords))
  range <- stats::median(distance[distance > 0])
  spatial_cov <- exp(-distance / range)
  diag(spatial_cov) <- diag(spatial_cov) + 1e-6

  z1 <- rnorm(length(sites))
  z2 <- 0.45 * z1 + sqrt(1 - 0.45^2) * rnorm(length(sites))
  u1 <- as.vector(t(chol(spatial_cov)) %*% z1) * 0.55
  u2 <- as.vector(t(chol(spatial_cov)) %*% z2) * 0.55
  names(u1) <- sites
  names(u2) <- sites

  site <- rep(sites, each = 3)
  x1 <- rnorm(length(site))
  x2 <- rnorm(length(site))
  e1 <- rnorm(length(site))
  e2 <- -0.10 * e1 + sqrt(1 - (-0.10)^2) * rnorm(length(site))

  list(
    data = data.frame(
      site = site,
      x1 = x1,
      x2 = x2,
      y1 = 0.35 + 0.25 * x1 + u1[site] + 0.18 * e1,
      y2 = -0.20 - 0.30 * x2 + u2[site] + 0.20 * e2
    ),
    coords = coords
  )
}

## -----------------------------------------------------------------------------
mesh_dat <- data.frame(
  y = c(1.1, 1.7, 2.4, 2.0),
  longitude = c(-123.10, -123.05, -123.00, -123.07),
  latitude = c(49.20, 49.23, 49.21, 49.25),
  site = letters[1:4]
)
coords_xy <- spatial_coords(mesh_dat, longitude, latitude, crs_out = "EPSG:32610")
mesh <- make_mesh(coords_xy, kappa = 1 / 10000)
fit_mesh <- drmTMB(
  bf(y ~ spatial(1 | site, mesh = mesh), sigma ~ 1),
  data = mesh_dat, family = gaussian(),
  control = drm_control(se = FALSE)
)
c(
  vertices = ncol(mesh$A_st),
  observations = nrow(mesh$A_st),
  max_projection_row_error = max(abs(Matrix::rowSums(mesh$A_st) - 1))
)
mesh_parameters <- summary(fit_mesh)$parameters
mesh_parameters[mesh_parameters$parm == "sd:mu:spatial(1 | site)", ]
ranef(fit_mesh, "spatial_mu")$projected
profile_targets(fit_mesh)[, c("parm", "profile_ready", "profile_note")]

## ----spatial-guide-fit--------------------------------------------------------
spatial_example <- simulate_spatial_guide_data()
spatial_dat <- spatial_example$data
coords <- spatial_example$coords

fit_spatial <- drmTMB(
  drm_formula(
    y ~ depth + temp + spatial(1 | site, coords = coords),
    sigma ~ depth
  ),
  family = gaussian(),
  data = spatial_dat
)

fit_spatial_slope <- drmTMB(
  drm_formula(
    y ~ depth + temp + spatial(1 + depth | site, coords = coords),
    sigma ~ depth
  ),
  family = gaussian(),
  data = spatial_dat
)

## ----spatial-site-field-figure, fig.width = 6.6, fig.height = 4.4, fig.cap = "Simulated example of coordinate-spatial fitted site deviations from `ranef(fit_spatial, \"spatial_mu\")`. Points are conditional location-effect estimates; uncertainty is not shown.", fig.alt = "Map of twelve simulated sampled sites. Each point is positioned at the site coordinates and coloured by the fitted conditional spatial location deviation; positive deviations are teal and negative deviations are orange."----
if (requireNamespace("ggplot2", quietly = TRUE)) {
  spatial_effect <- ranef(fit_spatial, "spatial_mu")$terms[[1]]
  spatial_field <- data.frame(
    site = names(spatial_effect),
    fitted_spatial_deviation = unname(spatial_effect),
    coords[names(spatial_effect), , drop = FALSE],
    row.names = NULL
  )
  field_limit <- max(abs(spatial_field$fitted_spatial_deviation))
  if (!is.finite(field_limit) || field_limit == 0) field_limit <- 1

  ggplot2::ggplot(
    spatial_field,
    ggplot2::aes(
      x = x,
      y = y,
      fill = fitted_spatial_deviation
    )
  ) +
    ggplot2::geom_hline(yintercept = 0, colour = "grey90", linewidth = 0.4) +
    ggplot2::geom_vline(xintercept = 0, colour = "grey90", linewidth = 0.4) +
    ggplot2::geom_point(
      shape = 21,
      size = 7,
      colour = "grey20",
      stroke = 0.35
    ) +
    ggplot2::scale_fill_gradient2(
      low = "#D55E00",
      mid = "white",
      high = "#009E73",
      midpoint = 0,
      limits = c(-field_limit, field_limit),
      name = "Fitted\nspatial deviation"
    ) +
    ggplot2::coord_equal() +
    spatial_guide_theme() +
    ggplot2::labs(
      title = "Fitted spatial location field",
      subtitle = "Conditional fitted deviations; uncertainty not shown",
      x = "Coordinate x",
      y = "Coordinate y"
    )
}

## ----spatial-sd-summary-------------------------------------------------------
spatial_sd <- data.frame(
  Component = c("Spatial intercept SD", "Spatial depth-slope SD"),
  Estimate = formatC(
    summary(fit_spatial_slope)$parameters[
      match(
        c("sd:mu:spatial(1 | site)", "sd:mu:spatial(0 + depth | site)"),
        summary(fit_spatial_slope)$parameters$parm
      ),
      "estimate"
    ],
    digits = 4,
    format = "g"
  ),
  Unit = c("Response units", "Response units per depth unit"),
  Status = c(
    "Point estimate; interval not validated",
    "Near-zero boundary; interval not validated"
  ),
  check.names = FALSE
)

knitr::kable(spatial_sd, align = c("l", "r", "l", "l"))

## ----spatial-q2-correlation-fit-----------------------------------------------
spatial_q2_example <- simulate_spatial_q2_guide_data()
spatial_q2_dat <- spatial_q2_example$data
spatial_q2_coords <- spatial_q2_example$coords

fit_spatial_q2_example <- drmTMB(
  drm_formula(
    mu1 = y1 ~ x1 + spatial(1 | p | site, coords = spatial_q2_coords),
    mu2 = y2 ~ x2 + spatial(1 | p | site, coords = spatial_q2_coords),
    sigma1 = ~ 1,
    sigma2 = ~ 1,
    rho12 = ~ 1
  ),
  family = biv_gaussian(),
  data = spatial_q2_dat,
  REML = TRUE,
  control = drm_control(
    optimizer = list(eval.max = 1000L, iter.max = 1000L),
    fallback_optimizer = "BFGS"
  )
)

## ----spatial-q2-profile-intervals---------------------------------------------
spatial_q2_targets <- c(
  "sd:mu:mu1:spatial(1 | p | site)",
  "sd:mu:mu2:spatial(1 | p | site)",
  "cor:spatial:cor(mu1:(Intercept),mu2:(Intercept) | p | site)"
)

spatial_q2_profile <- stats::confint(
  fit_spatial_q2_example,
  parm = spatial_q2_targets,
  method = "profile",
  profile_engine = "endpoint"
)
spatial_q2_target_table <- profile_targets(fit_spatial_q2_example)
spatial_q2_estimate <- spatial_q2_target_table$estimate[
  match(spatial_q2_targets, spatial_q2_target_table$parm)
]
spatial_q2_eye <- data.frame(
  target = factor(
    c(
      "Spatial SD: response 1",
      "Spatial SD: response 2",
      "Latent spatial correlation"
    ),
    levels = c(
      "Spatial SD: response 1",
      "Spatial SD: response 2",
      "Latent spatial correlation"
    )
  ),
  estimate = unname(spatial_q2_estimate),
  lower = spatial_q2_profile$lower,
  upper = spatial_q2_profile$upper
)
spatial_q2_eye_region <- do.call(
  rbind,
  lapply(seq_len(nrow(spatial_q2_eye)), function(i) {
    eye_x <- seq(
      spatial_q2_eye$lower[i],
      spatial_q2_eye$upper[i],
      length.out = 101
    )
    left_width <- max(
      spatial_q2_eye$estimate[i] - spatial_q2_eye$lower[i],
      .Machine$double.eps
    )
    right_width <- max(
      spatial_q2_eye$upper[i] - spatial_q2_eye$estimate[i],
      .Machine$double.eps
    )
    taper <- ifelse(
      eye_x <= spatial_q2_eye$estimate[i],
      (eye_x - spatial_q2_eye$lower[i]) / left_width,
      (spatial_q2_eye$upper[i] - eye_x) / right_width
    )
    half_height <- 0.10 * sqrt(pmax(taper, 0))
    data.frame(
      target = spatial_q2_eye$target[i],
      eye_x = c(eye_x, rev(eye_x)),
      eye_y = c(half_height, rev(-half_height))
    )
  })
)

## ----spatial-q2-confidence-eye, fig.width = 9.2, fig.height = 3.1, fig.cap = "Confidence Eye for the three direct fixed-kappa Gaussian q2 spatial targets at the tested M rung (36 sites x 3 observations, baseline ring geometry). Each coloured pale eye spans a 95% endpoint profile-likelihood interval; the larger hollow circle is the point estimate. Calibration passed jointly at the exact M and H rungs and failed at L.", fig.alt = "Three side-by-side facets show coloured tapered confidence eyes for two spatial standard deviations and one latent spatial correlation. Each eye's horizontal width is its 95 percent profile interval, and a large hollow circle marks the estimate. Each facet uses its own horizontal scale."----
if (requireNamespace("ggplot2", quietly = TRUE)) {
  ggplot2::ggplot(spatial_q2_eye) +
    ggplot2::geom_vline(
      data = data.frame(
        target = factor(
          "Latent spatial correlation",
          levels = levels(spatial_q2_eye$target)
        ),
        zero = 0
      ),
      ggplot2::aes(xintercept = zero),
      inherit.aes = FALSE,
      linetype = "dotted",
      linewidth = 0.5,
      colour = "grey55"
    ) +
    ggplot2::geom_polygon(
      data = spatial_q2_eye_region,
      ggplot2::aes(
        x = eye_x,
        y = eye_y,
        group = target,
        fill = target
      ),
      inherit.aes = FALSE,
      alpha = 0.24,
      colour = NA
    ) +
    ggplot2::geom_point(
      ggplot2::aes(
        x = estimate,
        y = 0,
        colour = target
      ),
      shape = 21,
      fill = "white",
      size = 4.2,
      stroke = 1.2
    ) +
    ggplot2::facet_wrap(~target, scales = "free_x", nrow = 1) +
    ggplot2::scale_x_continuous(
      expand = ggplot2::expansion(mult = c(0.20, 0.20))
    ) +
    ggplot2::scale_y_continuous(
      NULL,
      breaks = NULL,
      limits = c(-0.22, 0.32),
      expand = c(0, 0)
    ) +
    ggplot2::scale_fill_manual(
      values = c(
        "Spatial SD: response 1" = "#0072B2",
        "Spatial SD: response 2" = "#D55E00",
        "Latent spatial correlation" = "#009E73"
      ),
      guide = "none"
    ) +
    ggplot2::scale_colour_manual(
      values = c(
        "Spatial SD: response 1" = "#0072B2",
        "Spatial SD: response 2" = "#D55E00",
        "Latent spatial correlation" = "#009E73"
      ),
      guide = "none"
    ) +
    ggplot2::labs(
      x = "Target value (facet-specific scale)",
      title = "Profile uncertainty for the calibrated spatial q2 target set",
      subtitle = "Each eye is a 95% endpoint profile interval; hollow circle marks the estimate"
    ) +
    ggplot2::theme_minimal(base_size = 12) +
    ggplot2::theme(
      panel.grid.major.x = ggplot2::element_line(
        colour = "grey90",
        linewidth = 0.35
      ),
      panel.grid.major.y = ggplot2::element_blank(),
      panel.grid.minor = ggplot2::element_blank(),
      panel.spacing.x = grid::unit(1.3, "lines"),
      strip.text = ggplot2::element_text(face = "bold"),
      plot.title.position = "plot"
    )
}

