AAGI Symposium 2026 workshop
November 11, 2026
Lest we forget
I will assume a working knowledge of experimental design, but please ask if something isn’t clear.
initialise_design_df() or otherwise)speed(): optimise the layoutautoplot(): visualise itsummary(): evaluate it| Question | Argument |
|---|---|
| What moves? | swap, linked_cols* |
| Where is it allowed to move to? | swap_within |
| What should it be balanced across? | spatial_factors |
| What counts as a ‘neighbour’? | grid_factors + ... |
*: optional
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '22px', 'fontFamily': 'Arial', 'lineColor': '#414042'}}}%%
flowchart LR
A(["<span style='display:inline-block;padding:14px 30px;font-size:30px;font-weight:bold'>Design</span>"]) --> B["Swap two plots,<br/>score the new design"]
B --> C{"Better?"}
C -- "yes" --> D["Accept"]
C -- "no" --> E["Sometimes accept<br/>(less often as it cools)"]
D --> A
E --> A
A -- "optimal score or<br/>no improvement" --> H(["Stop"])
classDef hub fill:#00808b,stroke:#004d54,stroke-width:5px,color:#ffffff
classDef design fill:#00808b,stroke:#00808b,color:#ffffff
classDef step fill:#f2f2f2,stroke:#00808b,color:#414042
classDef good fill:#b6d438,stroke:#54921e,color:#414042
classDef maybe fill:#ffbc42,stroke:#ec8525,color:#414042
class A hub
class H design
class B,C step
class D good
class E maybe
| Question | Argument | Input |
|---|---|---|
| What moves? | swap |
treatment |
| Where is it allowed to move to? | swap_within |
default |
| What should it be balanced across? | spatial_factors |
default |
| What counts as a ‘neighbour’? | grid_factors |
default |
Optimising level: single treatment within whole design
Optimal score reached at iteration 730 for level single treatment within whole design
| Question | Argument | Input |
|---|---|---|
| What moves? | swap |
treatment |
| Where is it allowed to move to? | swap_within |
block |
| What should it be balanced across? | spatial_factors |
default |
| What counts as a ‘neighbour’? | grid_factors |
default |
Exercise 1 (about 10 minutes, on the Exercises page)
summary(): structure and optimisationsummary(sbbd_result), part 1 of 2
Design Summary
==============
Structure
---------
Layout: 4 rows x 6 cols (24 plots)
Treatments: 6
Replication: 4 each
Spatial: row (4), col (6)
Optimisation
------------
Seed: 42
Objective: objective_function
Score: 1.6 (initial 34 -> final 1.6)
adjacency 0
balance 1.6
Optimal: 1.6 (reached)
Iterations: 329 / 10,000 (optimal reached)
Temperature: start 100, cooling 0.99
summary(): evaluationsummary(sbbd_result), part 2 of 2
Design Summary
==============
Evaluation
----------
Connected: connected - treatment estimable given row + col + block [model (row + col + block)]
Concurrence: complete blocks - not informative
Blk. spread: each treatment in 4 of 4 blocks [block: block]
Repl. span: worst-case 2 (row), 2 (col) across 6 replicated treatment(s)
Efficiency: not requested (set efficiency = TRUE)
Self-adj.: none
Neighbour: min 1, max 4 over 15 pairs (variance 0.6952)
One answer per level (wp, then sp):
| Question | Argument | Whole plots | Subplots |
|---|---|---|---|
| What moves? | swap |
wholeplot_treatment |
subplot_treatment |
| Where? | swap_within |
block |
wholeplot |
| Balance | spatial_factors |
default | default |
| Neighbour | grid_factors |
default | default |
Optimising level: wp
Level: wp Iteration: 1000 Score: 100 Best: 100 Since Improvement: 1000
Level: wp Iteration: 2000 Score: 100 Best: 100 Since Improvement: 2000
Early stopping at iteration 2000 for level wp
Optimising level: sp
Optimal score reached at iteration 570 for level sp
| Question | Argument | Input |
|---|---|---|
| What moves? | swap |
entry |
| Where? | swap_within |
default |
| Balance | spatial_factors |
~ block + row + col |
| Neighbour | grid_factors |
default |
Optimising level: single entry within whole design
Level: single entry within whole design Iteration: 1000 Score: 3.41703 Best: 3.41703 Since Improvement: 0
Level: single entry within whole design Iteration: 2000 Score: 3.29703 Best: 3.29703 Since Improvement: 27
Level: single entry within whole design Iteration: 3000 Score: 3.29703 Best: 3.29703 Since Improvement: 1027
Early stopping at iteration 3973 for level single entry within whole design
linked_cols: keep variety names with their codesExercise 2 (about 10 minutes, on the Exercises page)
all_treatments <- c(rep(1:50, 7), rep(51:57, 8))
met_design <- initialise_design_df(
items = all_treatments,
designs = list(
a = list(nrows = 28, ncols = 5, block_nrows = 7, block_ncols = 5),
b = list(nrows = 20, ncols = 3, block_nrows = 10, block_ncols = 3),
c = list(nrows = 20, ncols = 4, block_nrows = 10, block_ncols = 4),
d = list(nrows = 15, ncols = 4, block_nrows = 5, block_ncols = 4),
e = list(nrows = 22, ncols = 3, block_nrows = 11, block_ncols = 3)
)
)
met_design$site_col <- paste(met_design$site, met_design$col, sep = "_")
met_design$site_block <- paste(met_design$site, met_design$block, sep = "_")Two levels: first connect the sites, then balance within each site.
| Question | Argument | Connectivity | Balance |
|---|---|---|---|
| What moves? | swap |
treatment |
treatment |
| Where? | swap_within |
default | site |
| Balance | spatial_factors |
~ site |
~ site_col + site_block |
| Neighbour | grid_factors |
by = "site" |
by = "site" |
MET Part 1
MET Part 2
Treat related entries as partial repeats when scoring adjacency.
items <- paste0("T", 1:16)
groups <- setNames(rep(c("A", "B", "C", "D"), each = 4), items)
rel <- matrix(0, 16, 16, dimnames = list(items, items))
within <- c(A = 0.8, B = 0.4, C = 0.1, D = 0.0)
for (g in names(within)) rel[items[groups == g],
items[groups == g]] <- within[[g]]
diag(rel) <- 1
head(rel) T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16
T1 1.0 0.8 0.8 0.8 0.0 0.0 0.0 0.0 0 0 0 0 0 0 0 0
T2 0.8 1.0 0.8 0.8 0.0 0.0 0.0 0.0 0 0 0 0 0 0 0 0
T3 0.8 0.8 1.0 0.8 0.0 0.0 0.0 0.0 0 0 0 0 0 0 0 0
T4 0.8 0.8 0.8 1.0 0.0 0.0 0.0 0.0 0 0 0 0 0 0 0 0
T5 0.0 0.0 0.0 0.0 1.0 0.4 0.4 0.4 0 0 0 0 0 0 0 0
T6 0.0 0.0 0.0 0.0 0.4 1.0 0.4 0.4 0 0 0 0 0 0 0 0
Optimised Experimental Design
----------------------------
Score: 4.266667
Iterations Run: 3511
Stopped Early: TRUE
Treatments: T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
Seed: 42
Tiles show each treatment’s relationship group (A: highly related, D: unrelated) not treatment.
An objective returns a list with a score; lower is better.
get_cooccurrence <- function(design_df, swap) {
pairs <- unlist(lapply(unique(design_df$block), function(block) {
combn(sort(as.character(design_df[design_df$block == block, swap])), 2,
FUN = function(x) paste(x, collapse = ","))
}))
return(as.list(table(pairs)))
}
bibd_objective <- function(layout_df, swap, spatial_cols,
row_column = "row", col_column = "col", ...) {
adj <- calculate_adjacency_score(layout_df, swap, row_column, col_column)
bal <- calculate_balance_score(layout_df, swap, spatial_cols)
pair_var <- var(as.numeric(get_cooccurrence(layout_df, swap)))
return(list(score = round(adj + bal + 10 * pair_var, 10)))
}bibd_df <- initialise_design_df(items = 5, nrows = 3, ncols = 10,
block_nrows = 3, block_ncols = 1)
bibd_result <- speed(bibd_df, swap = "treatment",
spatial_factors = ~ block + row + col,
obj_function = bibd_objective,
optimise_params = optim_params(random_initialisation = TRUE),
seed = 42, quiet = TRUE)
unique(unlist(get_cooccurrence(bibd_result$design_df, "treatment")))[1] 3
T1 and T2 share a block exactly 3 times, like every other pair.
treatment? Pass treatments = to autoplot()linked_colsgrid_factors = list(..., by = "site")summary() flags: a run that hit the iteration cap may need more iterations?speed, ?initialise_design_df, ?optim_params