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

## -----------------------------------------------------------------------------
library(randomizr)

set.seed(20260824)
sims <- 2000

# An exact promise: the property either held on every draw or it did not.
exact <- function(draw, property) {
  ok <- replicate(sims, isTRUE(property(draw())))
  list(result = sprintf("holds on %d of %d draws", sum(ok), sims),
       holds = all(ok))
}

# A probability promise: no unit's realized rate may sit more than five Monte
# Carlo standard errors from the probability the package claims for it.
probability <- function(draw, target, condition = 1) {
  realized <- rowMeans(replicate(sims, draw()) == condition)
  gap <- max(abs(realized - target))
  tolerance <- 5 * sqrt(max(target * (1 - target)) / sims)
  list(result = sprintf("largest gap %.3f, tolerance %.3f", gap, tolerance),
       holds = gap < tolerance)
}

## -----------------------------------------------------------------------------
N <- 100
blocks <- rep(c("a", "b", "c", "d", "e"), each = 20)
clusters <- rep(1:20, each = 5)
regions <- rep(c("north", "south"), each = 50)
p_unit <- seq(0.05, 0.95, length.out = N)
x <- rnorm(N)

# helpers the properties below are written in terms of
by_group <- function(z, g) tapply(z, g, sum)
uniform_within <- function(z, g) all(tapply(z, g, function(x) length(unique(x))) == 1)

## -----------------------------------------------------------------------------
checks <- list(
  list("simple_ra()", "each unit is treated with probability 0.25",
       probability(function() simple_ra(N, prob = 0.25), 0.25)),

  list("simple_ra()", "the number treated is random, not fixed",
       local({
         m <- replicate(sims, sum(simple_ra(N, prob = 0.25)))
         list(result = sprintf("sd %.2f against a binomial %.2f, %d distinct counts",
                               sd(m), sqrt(N * 0.25 * 0.75), length(unique(m))),
              holds = abs(sd(m) / sqrt(N * 0.25 * 0.75) - 1) < 0.1)
       })),

  list("complete_ra()", "each unit is treated with probability 25/100",
       probability(function() complete_ra(N, m = 25), 0.25)),

  list("complete_ra()", "exactly 25 units are treated",
       exact(function() complete_ra(N, m = 25), function(z) sum(z) == 25)),

  list("complete_ra(num_arms = 3)", "the three arms get 33, 33 and 34 units",
       exact(function() complete_ra(N, num_arms = 3),
             function(z) all(sort(as.vector(table(z))) == c(33, 33, 34)))),

  list("complete_ra(num_arms = 3)", "each unit reaches arm T1 with probability 1/3",
       probability(function() complete_ra(N, num_arms = 3), 1 / 3, condition = "T1")),

  list("block_ra()", "exactly half of every block of 20 is treated",
       exact(function() block_ra(blocks = blocks, prob = 0.5),
             function(z) all(by_group(z, blocks) == 10))),

  list("block_ra()", "each unit is treated with its block's probability, 0.5",
       probability(function() block_ra(blocks = blocks, prob = 0.5), 0.5)),

  list("cluster_ra()", "no cluster is ever split across conditions",
       exact(function() cluster_ra(clusters = clusters),
             function(z) uniform_within(z, clusters))),

  list("cluster_ra()", "each of the 20 clusters is treated with probability 0.5",
       probability(function() tapply(cluster_ra(clusters = clusters), clusters, `[`, 1), 0.5)),

  list("block_and_cluster_ra()", "clusters stay intact and each region treats 25 units",
       exact(function() block_and_cluster_ra(clusters = clusters, blocks = regions),
             function(z) uniform_within(z, clusters) && all(by_group(z, regions) == 25))),

  list("balanced_ra()", "each unit is treated with its own probability",
       probability(function() balanced_ra(prob_unit = p_unit), p_unit)),

  list("balanced_ra()", "the number treated is the floor or ceiling of its target",
       exact(function() balanced_ra(prob_unit = p_unit),
             function(z) sum(z) %in% c(floor(sum(p_unit)), ceiling(sum(p_unit))))),

  list("balanced_ra(formula = ~ x)", "each unit is treated with its own probability",
       probability(function() balanced_ra(prob_unit = p_unit, formula = ~ x), p_unit)),

  list("balanced_ra(formula = ~ x)", "the number treated is the floor or ceiling of its target",
       exact(function() balanced_ra(prob_unit = p_unit, formula = ~ x),
             function(z) sum(z) %in% c(floor(sum(p_unit)), ceiling(sum(p_unit))))),

  list("declare_ra() + conduct_ra()", "realized rates match the declared probabilities",
       local({
         declaration <- declare_ra(
           blocks = blocks,
           block_m_each = rbind(c(15, 5), c(14, 6), c(13, 7), c(12, 8), c(11, 9)))
         probability(function() conduct_ra(declaration),
                     declaration$probabilities_matrix[, 2])
       })),

  list("complete_rs()", "exactly 25 units are sampled",
       exact(function() complete_rs(N, n = 25), function(s) sum(s) == 25)),

  list("strata_rs()", "exactly half of every stratum is sampled",
       exact(function() strata_rs(strata = blocks, prob = 0.5),
             function(s) all(by_group(s, blocks) == 10)))
)

guarantees <- data.frame(
  Function = vapply(checks, function(x) x[[1]], character(1)),
  Promise = vapply(checks, function(x) x[[2]], character(1)),
  Measured = vapply(checks, function(x) x[[3]]$result, character(1)),
  Holds = vapply(checks, function(x) x[[3]]$holds, logical(1))
)

## ----echo=FALSE---------------------------------------------------------------
knitr::kable(guarantees, row.names = FALSE)

## -----------------------------------------------------------------------------
all(guarantees$Holds)

