The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
compute_design_SOAuthors
Chi-Kuang Yeh (Georgia State University) 
Weng Kee Wong (University of California, Los Angeles) 
Julie Zhou (University of Victoria)
gtDesign is an R package for locally optimal
approximate designs on a finite candidate set
for group testing (pooled testing). Designs are found
by convex optimization via CVXR
(typically with the CLARABEL solver).
The main application is the Huang et al. (2020) model for prevalence, sensitivity, and specificity with optional cost that depends on pool size, as used in Yeh, Wong, and Zhou (2025); see arXiv:2508.08445. The same interface also supports generic nonlinear design whenever the (approximate) information matrix is a sum of rank-one terms \(\sum_i w_i h(x_i) h(x_i)^\top\).
# install.packages("remotes")
remotes::install_github("chikuang/gtDesign")# Alternative
devtools::install_github("chikuang/gtDesign")Requirements: R ≥ 4.0 recommended; packages
CVXR, tibble, MASS
(see DESCRIPTION). A conic solver reachable by CVXR
(e.g. CLARABEL) is required at runtime.
Let \(\theta = (p_0, p_1, p_2)\) denote prevalence, sensitivity, and specificity, with \(p_0 \in (0,1)\) and \(p_1, p_2 \in (0.5, 1]\). For a pool of size \(x \in \{1,\ldots,M\}\), the positive response probability is
\[ \pi(x \mid \theta) = p_1 - (p_1 + p_2 - 1)(1 - p_0)^x . \]
Standardized cost (assay + enrollment) is
\[ c(x) = 1 - q + q x, \qquad q = \frac{q_1}{q_0 + q_1} \in [0,1], \]
so \(q = 0\) gives constant cost per test (\(c(x) \equiv 1\)), matching the “equal cost” setting in Huang et al.
The Fisher information matrix for \(\theta\) under independent Bernoulli pool outcomes can be written as
\[ \mathbf{I}(\mathbf{w}, \theta) = \sum_{j=1}^{M} w_j \, \lambda(x_j) \, \mathbf{f}(x_j) \mathbf{f}(x_j)^\top, \]
where \(w_j\) are design weights on candidate pool sizes \(x_j = j\), \(\sum_j w_j = 1\),
\[ \lambda(x) = \frac{1}{c(x)\,\pi(x\mid\theta)\{1-\pi(x\mid\theta)\}}, \]
and \(\mathbf{f}(x) = \nabla_\theta \pi(x\mid\theta)\) is the gradient of \(\pi\) with respect to \(\theta\) (see the paper for the explicit three components).
Many functions (calc_Dopt, calc_Aopt,
check_equivalence, …) assume a regression
form
\[ \mathbf{M}(\mathbf{w}) = \sum_j w_j \, \mathbf{h}(x_j) \mathbf{h}(x_j)^\top \]
with no separate info_weight. That
matches the Huang information matrix if we set
\[ \mathbf{h}(x) = \sqrt{\lambda(x)} \, \mathbf{f}(x). \]
The function
gt_huang2020_regressor(theta, q) returns
the function function(x) that computes \(\mathbf{h}(x)\). You can still use
compute_design_SO(..., info_weight = ...)
with raw \(\mathbf{f}\) and \(\lambda\) if you prefer the factored
form.
Local optimality: all designs are local with respect to a nominal \(\theta^\ast\) (and fixed \(q\), \(M\)).
| Area | Functions |
|---|---|
| Huang (2020) building blocks | gt_huang2020_pi, gt_huang2020_cost,
gt_huang2020_lambda, gt_huang2020_f,
gt_huang2020_regressor |
| Classical approximate designs | calc_Dopt, calc_Aopt,
calc_copt |
| Single-objective (D, A, Ds, c, E) | compute_design_SO |
| Maximin multi-objective | maximin_design_workflow,
compute_maximin_design,
calc_eta_weights_maximin |
| Equivalence | check_equivalence,
check_equivalence_maximin |
| Plots | plot_equivalence,
plot_equivalence_maximin |
| Directional derivatives | calc_directional_derivatives,
calc_multi_directional_derivative |
Nominal \(\theta^\ast = (0.07, 0.93, 0.96)\) as in the paper; D-optimal approximate design on \(\{1,\ldots,61\}\) with \(q=0\):
library(gtDesign)
theta <- c(p0 = 0.07, p1 = 0.93, p2 = 0.96)
M <- 61L
u <- seq_len(M)
f <- gt_huang2020_regressor(theta, q = 0)
res_d <- calc_Dopt(u, f, drop_tol = 1e-6)
res_d$design |> round(3) point weight
1 1 0.333
2 17 0.333
3 61 0.333
res_d$status[1] "optimal"
Support points \(\{1, 17, 61\}\) with weights \(1/3\) each match Table 1 (D-criterion, \(q=0\)) in arXiv:2508.08445.
res_a <- calc_Aopt(u, f, drop_tol = 1e-6)
res_a$design |> round(3) point weight
1 1 0.416
2 16 0.213
3 61 0.371
Larger \(q\) puts more weight on
enrollment in \(c(x) = 1 - q + q x\).
Use f <- gt_huang2020_regressor(theta, q) with your
chosen \(q \in (0,1]\).
f_q <- gt_huang2020_regressor(theta, q = 0.2)
res_d_q <- calc_Dopt(u, f_q, drop_tol = 1e-6)
res_d_q$design point weight
1 1 0.3333
2 10 0.3333
3 61 0.3333
Minimize the asymptotic variance of \(\mathbf{c}^\top \hat{\theta}\) for a user-specified \(\mathbf{c}\) (length 3). Example \(\mathbf{c} = (0,1,1)\) as in Table 1 of the paper:
c_vec <- c(0, 1, 1)
res_c <- calc_copt(u, f, cVec = c_vec, drop_tol = 1e-8)
subset(res_c$design, weight > 0.01) point weight
1 1 0.5213
4 56 0.1800
5 57 0.2988
compute_design_SOres_e <- compute_design_SO(
u = u,
f = f,
criterion = "E",
solver = "CLARABEL"
)
res_e$design# A tibble: 3 × 2
point weight
<int> <dbl>
1 1 0.415
2 16 0.250
3 61 0.335
eq_d <- check_equivalence(res_d, f = f, tol = 1e-4)
eq_d$max_violation[1] 2.217e-05
eq_d$all_nonpositive[1] TRUE
plot_equivalence(eq_d, main = "D-opt: equivalence derivative")
The maximin formulation (Sec. 4 of arXiv:2508.08445) maximizes
the minimum efficiency across several criteria. The
workflow matches the regression-design package cvxDesign
(maximin
section): first obtain reference losses from
single-objective optimal designs on the same candidate
set u and regressor f, then call
compute_maximin_design(). For group testing, f
is typically gt_huang2020_regressor(theta, q); for
polynomial regression, f can be any
function(x) returning a regressor vector (see cvxDesign
examples).
Chunks below call library(gtDesign). When you edit
package source and re-render this file, run
devtools::install() (from the package
root) first so the README runs against the installed version.
Reference losses. Losses must be on the same
scale as compute_design_SO() / internal
scalar_loss: for D, the loss is \(-\log\det(\mathbf{M})\) (so if you use
calc_Dopt(), pass D = -obj$value because
calc_Dopt()$value stores \(\log\det(\mathbf{M})\)). For
A and c, calc_Aopt() /
calc_copt() use the same scalar loss as
compute_design_SO().
The chunks below reuse u, f,
res_d, and res_a from the D-opt and A-opt
examples.
loss_ref <- list(
D = -res_d$value,
A = res_a$value
)
loss_ref$D
[1] -5.796
$A
[1] 0.7058
res_da <- compute_maximin_design(
u = u,
f = f,
loss_ref = loss_ref,
criteria = c("D", "A")
)
res_da$design# A tibble: 4 × 2
point weight
<int> <dbl>
1 1 0.382
2 16 0.114
3 17 0.148
4 61 0.356
res_da$efficiency D A
0.987 0.987
res_da$tstar[1] 1.013
The efficiencies should be approximately
equal at a maximin solution; minimum
efficiency equals \(1 /
t^\ast\) where tstar is returned by the solver.
tol <- 1e-4
eq_eff <- abs(res_da$efficiency["D"] - res_da$efficiency["A"])
eq_t <- abs(min(res_da$efficiency) - 1 / res_da$tstar)
eq_eff < tol && eq_t < tol[1] TRUE
Parameter dimension is \(p = 3\) for
\((p_0,p_1,p_2)\).
calc_eta_weights_maximin solves a small
linear program; use the same f (and
cVec / opts for c) as in
compute_maximin_design(). If the default solver fails, try
solver = "SCS" and slightly relax tol
(e.g. 1e-4); see
?calc_eta_weights_maximin.
dd_da <- calc_directional_derivatives(
u = u,
M = res_da$info_matrix,
f = f,
criteria = c("D", "A")
)
eta_da <- calc_eta_weights_maximin(
tstar = res_da$tstar,
loss_ref = loss_ref,
loss_model = res_da$loss,
directional_derivatives = dd_da,
criteria = c("D", "A"),
q = 3,
tol = 1e-4
)
eta_da D A
0.1898 0.6205
eqm_da <- check_equivalence_maximin(
design_obj = res_da,
directional_derivatives = dd_da,
eta = eta_da,
tol = 1e-3
)
eqm_da$all_nonpositive[1] TRUE
eqm_da$support_equal_zero[1] TRUE
plot_equivalence_maximin(
design_obj = res_da,
directional_derivatives = dd_da,
eta = eta_da,
criteria = c("D", "A")
)
out <- maximin_design_workflow(
u = u,
f = f,
criteria = c("D", "A"),
make_figure = TRUE
)
out$maximin$efficiency D A
0.987 0.987
out$maximin$value[1] 0.987
out$maximin$tstar[1] 1.013
out$maximin$design# A tibble: 4 × 2
point weight
<int> <dbl>
1 1 0.382
2 16 0.114
3 17 0.148
4 61 0.356
Use the same contrast as in the c-optimal example
(c_vec). Pass opts = list(cVec_c = c_vec)
whenever c is included.
loss_ref_dac <- list(
D = -res_d$value,
A = res_a$value,
c = res_c$value
)
res_dac <- compute_maximin_design(
u = u,
f = f,
loss_ref = loss_ref_dac,
criteria = c("D", "A", "c"),
opts = list(cVec_c = c_vec)
)
res_dac$efficiency D A c
0.8907 0.9587 0.8907
Directional derivatives, \(\eta\) weights, and equivalence for three criteria (use a slightly looser tolerance on the combined derivative because of numerical slack):
dd_dac <- calc_directional_derivatives(
u = u,
M = res_dac$info_matrix,
f = f,
criteria = c("D", "A", "c"),
cVec = c_vec
)
eta_dac <- calc_eta_weights_maximin(
tstar = res_dac$tstar,
loss_ref = loss_ref_dac,
loss_model = res_dac$loss,
directional_derivatives = dd_dac,
criteria = c("D", "A", "c"),
q = 3,
tol = 1e-3
)
check_equivalence_maximin(res_dac, dd_dac, eta_dac, tol = 0.002)$all_nonpositive[1] TRUE
plot_equivalence_maximin(
res_dac,
dd_dac,
eta_dac,
criteria = c("D", "A", "c")
)
library(dplyr)
library(knitr)
theta <- c(p0 = 0.07, p1 = 0.93, p2 = 0.96)
u <- 1:61
q_val <- 0.8
f_q0 <- gt_huang2020_regressor(theta, q = q_val)
make_summary <- function(design, criterion) {
design |>
filter(weight > 1e-4) |>
select(1, 2) |>
rename(group_size = 1, weight = 2) |>
mutate(weight = round(weight, 3)) |>
summarise(
Criterion = criterion,
`Support points` = paste(group_size, collapse = ", "),
Weights = paste(weight, collapse = ", ")
)
}
tab_summary <- bind_rows(
make_summary(calc_Dopt(u, f_q0)$design, "D-opt"),
make_summary(calc_Aopt(u, f_q0)$design, "A-opt"),
make_summary(calc_copt(u, f_q0, cVec = c(1, 0, 0))$design, "D_s-opt"),
make_summary(calc_copt(u, f_q0, cVec = c(0, 1, 1))$design, "c-opt")
)
knitr::kable(
tab_summary,
format = "pipe",
caption = paste0(
"Support points and weights for approximate optimal designs ",
"for the Huang (2020) group testing model with q = ", q_val,
" on the design space {", min(u), ", ..., ", max(u), "}."
)
)| Criterion | Support points | Weights |
|---|---|---|
| D-opt | 1, 7, 8, 61 | 0.333, 0.029, 0.304, 0.333 |
| A-opt | 1, 8, 61 | 0.125, 0.183, 0.692 |
| D_s-opt | 1, 7, 61 | 0.095, 0.573, 0.332 |
| c-opt | 1, 56, 57 | 0.139, 0.322, 0.539 |
Support points and weights for approximate optimal designs for the Huang (2020) group testing model with q = 0.8 on the design space {1, …, 61}.
u <- seq_len(150L)
f_q <- gt_huang2020_regressor(theta, q = 0.0)
res_d_q <- calc_Dopt(u, f_q, drop_tol = 1e-6)
res_a_q <- calc_Aopt(u, f_q, drop_tol = 1e-6)
loss_ref <- list(
D = -res_d_q$value,
A = res_a_q$value
)
loss_ref$D
[1] -6.185
$A
[1] 0.5617
### Step 2: maximin design (D and A)
res_da <- compute_maximin_design(
u = u,
f = f_q,
loss_ref = loss_ref,
criteria = c("D", "A")
)
res_da$design# A tibble: 3 × 2
point weight
<int> <dbl>
1 1 0.409
2 19 0.251
3 150 0.339
res_da$efficiency D A
0.9805 0.9805
res_da$tstar[1] 1.02
### Step 3 numerical check
tol <- 1e-4
eq_eff <- abs(res_da$efficiency["D"] - res_da$efficiency["A"])
eq_t <- abs(min(res_da$efficiency) - 1 / res_da$tstar)
eq_eff < tol && eq_t < tol[1] TRUE
u <- seq_len(150L)
cVec <- c(1, 0, 0)
theta = c(0.07, 0.93, 0.96)
f_q <- gt_huang2020_regressor(theta, q = 0.2)
out <- maximin_design_workflow(
u = u,
f = f_q,
criteria = c("D", "A", "c"),
opts = list(cVec_c = cVec),
make_figure = TRUE
)
out$maximin$efficiency D A c
0.9001 0.8545 0.8545
out$maximin$value[1] 0.8545
out$maximin$tstar[1] 1.17
out$maximin$design# A tibble: 3 × 2
point weight
<int> <dbl>
1 1 0.158
2 10 0.364
3 75 0.478
Rounding Algorithm II in Sec. 5.1: approximate D-opt on
u, then [round_gt_design_budget()] for each total cost
C. Re-run for C \in \{100, 500, 10000\} (or as
in the paper table).
theta <- c(p0 = 0.07, p1 = 0.93, p2 = 0.96)
u <- seq_len(150L)
q_cost <- 0.2
f <- gt_huang2020_regressor(theta, q_cost)
# calculate the approximate design
res_D <- calc_Dopt(u, f, drop_tol = 1e-6)
# Example: C = 100 (repeat for C = 500, 10000, ...)
out <- round_gt_design_budget(
approx_design = res_D,
u = u,
theta = theta,
C = 100,
q_cost = q_cost,
criterion = "D",
fix_zero_floor = FALSE
)
out$C_remaining[1] 7.8
out$design_exact [,1] [,2] [,3] [,4]
x 1 10 11 67
n_prime 35 12 1 2
out$efficiency [1] 0.994
out$design_round1 [,1] [,2] [,3]
x 1 10 67
n_prime 33 11 2
out$delta [,1] [,2] [,3]
x 1 10 11
Delta_n 2 1 1
# 8*(1 - q_cost + q_cost*1 ) # 8
# 2*(1 - q_cost + q_cost*10) # 5.6
#
# my_cost <- 0
# for (i in 1:ncol(out$design_round1)) {
# my_cost <- out$design_round1[2, i] * (1 - q_cost + q_cost*out$design_round1[1,i]) + my_cost
# }Table 4 reports exact designs \(\xi_{RAC}\) from the rounding algorithms applied to maximin approximate designs on \(M=61\), \(\boldsymbol{\theta}^*=(0.07,0.93,0.96)\). MinEff is \(\min_j \mathrm{Eff}_j\) at \(\xi_{RAC}\); \(1/t^*\) is the minimum efficiency of the maximin OAD (Table 2). Part (i) uses fixed run count \(n\) with \(q=0\) (Rounding Algorithm I); part (ii) uses budget \(C\) with \(q=0.2\) (Rounding Algorithm II). The helper functions [round_gt_design_n_maximin()] and [round_gt_design_budget_maximin()] provide reusable implementations for these two settings.
Example below shows one Table 4 row: DD-AA, \(q=0.2\), \(C=100\). It should return \(x=(1,10,59,61)\), \(n=(26,8,1,3)\), and MinEff \(\approx 0.932\).
theta <- c(p0 = 0.07, p1 = 0.93, p2 = 0.96)
u <- seq_len(61L)
q_cost <- 0.2
f <- gt_huang2020_regressor(theta, q_cost)
# single-objective references used in maximin efficiencies
res_d <- calc_Dopt(u, f, drop_tol = 1e-6)
res_a <- calc_Aopt(u, f, drop_tol = 1e-6)
loss_da <- list(D = -res_d$value, A = res_a$value)
# maximin approximate design (D-A)
mm_da <- compute_maximin_design(
u = u,
f = f,
loss_ref = loss_da,
criteria = c("D", "A"),
drop_tol = 1e-6
)
# exact design at C = 100 using Algorithm II + modified Step II (MinEff search)
out_da_100 <- round_gt_design_budget_maximin(
approx_design = mm_da,
u = u,
theta = theta,
C = 100,
q_cost = q_cost,
loss_ref = loss_da,
criteria = c("D", "A")
)
out_da_100$design_exact [,1] [,2] [,3] [,4]
x 1 10 59 61
n_prime 26 8 1 3
out_da_100$delta [,1] [,2]
x 1 59
Delta_n 1 1
out_da_100$min_efficiency[1] 0.9318
1 / mm_da$tstar[1] 0.9487
The full chunk below is set to eval=FALSE so the README
renders quickly; set eval=TRUE to run all Table 4 rows.
theta <- c(p0 = 0.07, p1 = 0.93, p2 = 0.96)
u <- seq_len(61L)
q_cost <- 0.2
f <- gt_huang2020_regressor(theta, q_cost)
res_d <- calc_Dopt(u, f, drop_tol = 1e-6)
res_a <- calc_Aopt(u, f, drop_tol = 1e-6)
loss_da <- list(D = -res_d$value, A = res_a$value)
mm_da <- compute_maximin_design(
u = u,
f = f,
loss_ref = loss_da,
criteria = c("D", "A"),
drop_tol = 1e-6
)
min(mm_da$efficiency) # compare to 1/t* in Table 4
1 / mm_da$tstar
for (C in c(100, 500)) {
out_da <- round_gt_design_budget(
mm_da,
u = u,
theta = theta,
C = C,
q_cost = q_cost,
criterion = "D"
)
ed_da <- exact_design_efficiency_maximin(
out_da$M_exact,
loss_da,
c("D", "A")
)
list(C = C, design_exact = out_da$design_exact, min_eff = ed_da$min_efficiency)
}
res_ds <- calc_copt(u, f, cVec = c(1, 0, 0), drop_tol = 1e-6)
loss_dads <- list(D = -res_d$value, A = res_a$value, Ds = res_ds$value)
opts_ds <- list(cVec_Ds = c(1, 0, 0))
mm_dads <- compute_maximin_design(
u = u,
f = f,
loss_ref = loss_dads,
criteria = c("D", "A", "Ds"),
opts = opts_ds,
drop_tol = 1e-6
)
for (C in c(100, 500)) {
out_3 <- round_gt_design_budget(
mm_dads,
u = u,
theta = theta,
C = C,
q_cost = q_cost,
criterion = "D",
opts = opts_ds
)
ed_3 <- exact_design_efficiency_maximin(
out_3$M_exact,
loss_dads,
c("D", "A", "Ds"),
opts = opts_ds
)
list(C = C, design_exact = out_3$design_exact, min_eff = ed_3$min_efficiency)
}Numerical values may differ slightly from the paper due to the CVXR solver and the extension step in [round_gt_design_budget()] (single-criterion merge for Step II).
Yeh, C.-K., Wong, W. K., Zhou, J. (2025). Single and multi-objective optimal designs for group testing experiments. arXiv 2508.08445. https://doi.org/10.48550/arXiv.2508.08445
Huang, S.-Y., Chen, Y.-H., Wang, W. (2020). Optimal group testing designs for estimating prevalence with imperfect tests. Journal of the Royal Statistical Society Series C.
Pukelsheim, F. (2006). Optimal Design of Experiments. SIAM.
Fedorov, V. V. (1972). Theory of Optimal Experiments. Academic Press.
GPL-3 — see the License field in
DESCRIPTION.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.