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

## ----pivotal-fn---------------------------------------------------------------
random_pivotal_sample <- function(pik, n = NULL, prn = NULL, ...) {
  tol <- 1e-06
  active <- which(pik > tol & pik < 1 - tol)

  while (length(active) >= 2L) {
    ij <- sample(active, 2L)
    i <- ij[1L]
    j <- ij[2L]
    total <- pik[i] + pik[j]

    if (total < 1) {
      if (runif(1) < pik[i] / total)
        pik[c(i, j)] <- c(total, 0)
      else pik[c(i, j)] <- c(0, total)
    } else {
      if (runif(1) < (1 - pik[j]) / (2 - total)) {
        pik[c(i, j)] <- c(1, total - 1)
      } else {
        pik[c(i, j)] <- c(total - 1, 1)
      }
    }
    active <- which(pik > tol & pik < 1 - tol)
  }
  sort(which(pik > 0.5))
}

## ----pivotal-register---------------------------------------------------------
library(sondage)

register_method(
  "random_pivotal",
  type            = "wor",
  sample_fn       = random_pivotal_sample,
  joint_fn        = he_jip,
  fixed_size      = TRUE,
  variance_family = "pps_brewer",
  probabilities   = "exact"
)

## ----pivotal-use--------------------------------------------------------------
pik <- inclusion_prob(c(2, 3, 4, 5, 6, 7, 8, 9), n = 4)
s <- unequal_prob_wor(pik, method = "random_pivotal")
s

pikl <- joint_inclusion_prob(s)
round(pikl, 4)
round(sampling_cov(s, weighted = TRUE), 4)
joint_inclusion_prob(s, sampled_only = TRUE)

## ----pivotal-verify-----------------------------------------------------------
sim <- unequal_prob_wor(pik, method = "random_pivotal", nrep = 5000)
freq <- tabulate(sim$sample, nbins = length(pik)) / 5000
cbind(target = pik, empirical = freq)

N <- length(pik)
co_occur <- matrix(0, N, N)
for (j in seq_len(5000)) {
  selected <- sim$sample[, j]
  co_occur[selected, selected] <- co_occur[selected, selected] + 1
}
empirical_jip <- co_occur / 5000
he_pikl <- he_jip(pik)

pairs <- data.frame(
  i = c(1, 2, 3, 5),
  j = c(8, 7, 6, 8)
)
pairs$HE <- round(he_pikl[cbind(pairs$i, pairs$j)], 4)
pairs$empirical <- round(empirical_jip[cbind(pairs$i, pairs$j)], 4)
pairs

## ----pivotal-cleanup, include = FALSE-----------------------------------------
unregister_method("random_pivotal")

## ----tille, eval = requireNamespace("sampling", quietly = TRUE)---------------
tille_sample <- function(pik, n = NULL, prn = NULL, ...) {
  which(as.logical(sampling::UPtille(pik)))
}

tille_joint <- function(pik, sample_idx = NULL, ...) {
  pikl <- sampling::UPtillepi2(pik)
  if (!is.null(sample_idx)) {
    pikl <- pikl[sample_idx, sample_idx, drop = FALSE]
  }
  pikl
}

register_method(
  "tille",
  type            = "wor",
  sample_fn       = tille_sample,
  joint_fn        = tille_joint,
  fixed_size      = TRUE,
  variance_family = "pps_brewer",
  probabilities   = "exact"
)

pik <- inclusion_prob(c(2, 3, 4, 5, 6, 7, 8, 9), n = 4)
s <- unequal_prob_wor(pik, method = "tille")
s

# Exact joint inclusion probabilities from UPtillepi2
round(joint_inclusion_prob(s), 4)

# Full variance estimation chain
round(sampling_cov(s), 4)

## ----tille-cleanup, include = FALSE, eval = requireNamespace("sampling", quietly = TRUE)----
unregister_method("tille")

## ----custom-wr----------------------------------------------------------------
custom_multinomial_sample <- function(hits, n = NULL, prn = NULL, ...) {
  sample.int(length(hits), size = n, replace = TRUE, prob = hits)
}

custom_multinomial_joint <- function(hits, sample_idx = NULL, ...) {
  n <- round(sum(hits))
  keep <- if (is.null(sample_idx)) seq_along(hits) else sample_idx
  h <- hits[keep]
  factor <- (n - 1) / n
  joint <- factor * outer(h, h)
  diag(joint) <- h + factor * h^2
  joint
}

register_method(
  "custom_multinomial",
  type            = "wr",
  sample_fn       = custom_multinomial_sample,
  joint_fn        = custom_multinomial_joint,
  variance_family = "wr",
  probabilities   = "exact"
)

hits <- expected_hits(c(2, 3, 5, 10), n = 4)
s_wr <- unequal_prob_wr(hits, method = "custom_multinomial")
s_wr
joint_expected_hits(s_wr)
joint_expected_hits(s_wr, sampled_only = TRUE)

## ----custom-wr-cleanup, include = FALSE---------------------------------------
unregister_method("custom_multinomial")

## ----cube-lp, eval = requireNamespace("sampling", quietly = TRUE)-------------
cube_lp_sample <- function(pik, n = NULL, aux = NULL, ...) {
  X <- cbind(pik, aux)
  which(sampling::samplecube(X, pik, comment = FALSE) == 1)
}

register_method(
  "cube_lp",
  type            = "balanced",
  sample_fn       = cube_lp_sample,
  joint_fn        = he_jip,
  variance_family = "pps_brewer",
  probabilities   = "exact"
)

pik <- inclusion_prob(c(2, 3, 4, 5, 6, 7, 8, 9), n = 4)
x <- matrix(c(10, 20, 15, 25, 30, 35, 40, 45))
s <- balanced_wor(pik, aux = x, method = "cube_lp")
s

# Balancing check: HT estimate of the aux total vs the true total
colSums(x[s$sample, , drop = FALSE] / pik[s$sample]) - colSums(x)

## ----cube-lp-strata, eval = requireNamespace("sampling", quietly = TRUE)------
cube_lp_stratified <- function(pik, n = NULL, aux = NULL, strata = NULL, ...) {
  if (is.null(strata)) {
    return(cube_lp_sample(pik, n = n, aux = aux))
  }
  X <- if (is.null(aux)) matrix(pik, ncol = 1) else cbind(pik, aux)
  which(sampling::balancedstratification(X, strata, pik, comment = FALSE) == 1)
}

register_method(
  "cube_lp_str",
  type            = "balanced",
  sample_fn       = cube_lp_stratified,
  joint_fn        = he_jip,
  variance_family = "pps_brewer",
  supports_strata = TRUE,
  probabilities   = "exact"
)

pik <- rep(0.5, 8)
strata <- rep(1:2, each = 4)
s <- balanced_wor(pik, aux = matrix(as.double(1:8)), strata = strata,
                  method = "cube_lp_str")

# Within-stratum sample sizes are preserved
tabulate(strata[s$sample], nbins = 2)

## ----cube-lp-cleanup, include = FALSE, eval = requireNamespace("sampling", quietly = TRUE)----
unregister_method("cube_lp")
unregister_method("cube_lp_str")

## ----lpm1-fn------------------------------------------------------------------
lpm1_sample <- function(pik, n = NULL, aux = NULL, spread = NULL, ...) {
  d <- as.matrix(dist(spread))
  diag(d) <- Inf
  p <- pik
  eps <- 1e-9
  repeat {
    u <- which(p > eps & p < 1 - eps)
    if (length(u) == 0L) {
      break
    }
    if (length(u) == 1L) {
      p[u] <- as.numeric(runif(1) < p[u])
      break
    }
    i <- u[sample.int(length(u), 1L)]
    v <- u[u != i]
    j <- v[which.min(d[i, v])]
    w <- u[u != j]
    if (w[which.min(d[j, w])] != i) {
      next # not mutual nearest neighbours: redraw i
    }
    s <- p[i] + p[j]
    if (s > 1) {
      if (runif(1) < (1 - p[j]) / (2 - s)) {
        p[i] <- 1
        p[j] <- s - 1
      } else {
        p[j] <- 1
        p[i] <- s - 1
      }
    } else {
      if (runif(1) < p[j] / s) {
        p[j] <- s
        p[i] <- 0
      } else {
        p[i] <- s
        p[j] <- 0
      }
    }
  }
  which(p > 1 - eps)
}

register_method(
  "lpm1",
  type             = "balanced",
  sample_fn        = lpm1_sample,
  variance_family  = "unsupported",
  supports_aux     = FALSE,
  supports_spread  = TRUE,
  probabilities    = "exact"
)

## ----lpm1-run-----------------------------------------------------------------
set.seed(25)
N <- 200
coords <- cbind(runif(N), runif(N))
pik <- rep(0.15, N)

s <- balanced_wor(pik, spread = coords, method = "lpm1")
s

# Spread diagnostic: mean distance to the nearest sampled neighbour
# (larger is better spread)
nn_dist <- function(idx) {
  d <- as.matrix(dist(coords[idx, ]))
  diag(d) <- Inf
  mean(apply(d, 1, min))
}
s2 <- balanced_wor(pik, spread = coords, method = "lpm2")
s3 <- balanced_wor(pik, spread = coords, method = "scps")
c(
  lpm1 = nn_dist(s$sample),
  lpm2 = nn_dist(s2$sample),
  scps = nn_dist(s3$sample),
  srs = nn_dist(sample.int(N, s$n))
)

## ----lpm1-cleanup, include = FALSE--------------------------------------------
unregister_method("lpm1")

## ----mc-joint, eval = FALSE---------------------------------------------------
# mc_joint <- function(pik, sample_idx = NULL, ..., B = 5000) {
#   N <- length(pik)
#   n <- as.integer(round(sum(pik)))
#   co <- matrix(0, N, N)
#   for (b in seq_len(B)) {
#     s <- my_sampler(pik, n = n)
#     co[s, s] <- co[s, s] + 1
#   }
#   pikl <- co / B
#   diag(pikl) <- tabulate(unlist(
#     replicate(B, my_sampler(pik, n = n), simplify = FALSE)
#   ), nbins = N) / B
#   if (!is.null(sample_idx)) {
#     pikl <- pikl[sample_idx, sample_idx, drop = FALSE]
#   }
#   pikl
# }

