Fix SWAP-MTX: Fisher-Yates shuffle was never actually shuffling correctly
Found while building the analysis report for the ACL-RWT relaunch campaign: cfg=0 was missing from run coverage for 2 of 3 seeds, reproduced identically across all three architectures. Root-caused rather than worked around, per Captain Bob's "this is worrisome." SWAP-MTX (capsules/doe.4th Block 2104) never actually swapped two RUN-MATRIX cells -- it performed a lossy one-way copy (second MATRIX! call mis-targeted mat[i] again instead of mat[j]). Confirmed by direct empirical test on the hosted build: INIT-MATRIX gives mat[0]=0, mat[5]=5; after 0 5 SWAP-MTX, mat[0]=0 (unchanged, should be 5) and mat[5]=0 (correct), with the original value 5 permanently destroyed. Every Fisher-Yates shuffle this mechanism has ever run silently duplicated some values and dropped others -- not a true permutation. Not new, not introduced by item 4.6/Stadium work; predates this session. Fixed with explicit temp variables (SW-I/SW-J/SW-VI/SW-VJ), trivially verifiable by inspection over clever stack juggling. Verified on the hosted build for all three seeds used by the relaunch campaign: each now produces all 16 cfg values exactly 30 times, run_id 0-479 fully distinct. Three-arch QEMU acceptance clean: 1012/0/0 POST on all three, identical dict_hash (expected -- doe.4th isn't C-registered or auto-loaded at boot). BLOCK_MAP.md correctly shows only doe.4th's own hash changed. Also includes the R analysis/chart pipeline (analyse_stadium_relaunch.R) built for the relaunch campaign report, and the three acceptance boot logs. Retroactive caveat: the relaunch campaign's own run-matrix coverage (experiments/bare_metal/runs/acl-rwt-20260820/) is not a valid uniform permutation, having run against the buggy shuffle. Whether to re-run it against the fix is a separate call, not made here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
79d160c1ca
commit
7e2fd9f044
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env Rscript
|
||||
# analyse_stadium_relaunch.R
|
||||
# StarForth LithosAnanke — EXEC-DOE 3x3 Latin square campaign,
|
||||
# re-run on the post-item-4.6 Stadium substrate (2026-08-20).
|
||||
#
|
||||
# Generates figures + tables for stadium_relaunch_report.tex
|
||||
#
|
||||
# Data source: runs/acl-rwt-20260820/{arch}-seed{seed}.csv
|
||||
# 9 cells: {amd64,aarch64,riscv64} x {12345,67890,13579}
|
||||
# 480 rows/cell (16 L8 configs x 30 reps, Fisher-Yates shuffled)
|
||||
#
|
||||
# Scope note: this campaign validates that the EXEC-DOE mechanism runs
|
||||
# cleanly and completely on the Stadium substrate (item 5.1's own concern:
|
||||
# "a green POST suite is not evidence that determinism holds"). It is NOT
|
||||
# an ACL-RWT overhead measurement -- ACL.4th is not self-activated in this
|
||||
# repo's default init.4th, so these cells ran with ACL inactive.
|
||||
|
||||
suppressPackageStartupMessages({
|
||||
library(ggplot2)
|
||||
library(svglite)
|
||||
library(dplyr)
|
||||
library(tidyr)
|
||||
library(scales)
|
||||
library(patchwork)
|
||||
})
|
||||
|
||||
SCRIPT_DIR <- tryCatch(
|
||||
dirname(normalizePath(sys.frames()[[1]]$ofile)),
|
||||
error = function(e) getwd()
|
||||
)
|
||||
BASE_DIR <- normalizePath(file.path(SCRIPT_DIR, ".."))
|
||||
RUNS_DIR <- file.path(BASE_DIR, "runs", "acl-rwt-20260820")
|
||||
OUT_CHARTS <- file.path(SCRIPT_DIR, "charts")
|
||||
OUT_TABLES <- file.path(SCRIPT_DIR, "tables")
|
||||
dir.create(OUT_CHARTS, showWarnings = FALSE, recursive = TRUE)
|
||||
dir.create(OUT_TABLES, showWarnings = FALSE, recursive = TRUE)
|
||||
|
||||
cat("══════════════════════════════════════════════════════════════════\n")
|
||||
cat(" StarForth LithosAnanke — Stadium-substrate EXEC-DOE relaunch\n")
|
||||
cat(" Item 5.1 / F.3 — campaign-mechanism validation, 2026-08-20\n")
|
||||
cat("══════════════════════════════════════════════════════════════════\n\n")
|
||||
|
||||
# ── palette (matches analyse_acl_rwt.R) ───────────────────────────────────────
|
||||
arch_colours <- c(amd64 = "#E07B39",
|
||||
aarch64 = "#4A90D9",
|
||||
riscv64 = "#50C878")
|
||||
|
||||
theme_light_sf <- function(base = 11) {
|
||||
theme_minimal(base_size = base) %+replace% theme(
|
||||
panel.grid.minor = element_blank(),
|
||||
panel.grid.major = element_line(colour = "grey90"),
|
||||
strip.text = element_text(face = "bold"),
|
||||
plot.title = element_text(face = "bold", size = base + 1),
|
||||
plot.subtitle = element_text(colour = "grey40", size = base - 2),
|
||||
legend.position = "bottom",
|
||||
legend.key.size = unit(0.5, "cm")
|
||||
)
|
||||
}
|
||||
|
||||
theme_dark_sf <- function(base = 11) {
|
||||
theme_minimal(base_size = base) %+replace% theme(
|
||||
panel.background = element_rect(fill = "#0d0d0d", colour = NA),
|
||||
plot.background = element_rect(fill = "#0d0d0d", colour = NA),
|
||||
panel.grid.major = element_line(colour = "#1e1e1e"),
|
||||
panel.grid.minor = element_blank(),
|
||||
axis.text = element_text(colour = "#aaaaaa"),
|
||||
axis.title = element_text(colour = "#cccccc"),
|
||||
strip.text = element_text(colour = "white", face = "bold"),
|
||||
plot.title = element_text(colour = "white", face = "bold", size = base + 1),
|
||||
plot.subtitle = element_text(colour = "#666666", size = base - 2),
|
||||
legend.text = element_text(colour = "#aaaaaa"),
|
||||
legend.title = element_text(colour = "#cccccc"),
|
||||
legend.background = element_rect(fill = "#0d0d0d", colour = NA),
|
||||
legend.position = "bottom",
|
||||
legend.key.size = unit(0.5, "cm")
|
||||
)
|
||||
}
|
||||
|
||||
save_svg <- function(plot, name, w = 12, h = 7) {
|
||||
path <- file.path(OUT_CHARTS, paste0(name, ".svg"))
|
||||
svglite(path, width = w, height = h)
|
||||
print(plot)
|
||||
dev.off()
|
||||
cat(sprintf(" Saved: %s.svg\n", name))
|
||||
invisible(path)
|
||||
}
|
||||
|
||||
# ── load data ──────────────────────────────────────────────────────────────
|
||||
col_names <- c("run_id", "cfg", "rep", "ent_in", "cv_in", "tmp_in", "stb_in",
|
||||
"l8_mode", "win_div", "infer_win", "infer_dec_q", "infer_var_q",
|
||||
"early_exit", "bc_mean_q", "bb_mean_q", "fit_q")
|
||||
|
||||
archs <- c("amd64", "aarch64", "riscv64")
|
||||
seeds <- c("12345", "67890", "13579")
|
||||
|
||||
load_cell <- function(arch, seed) {
|
||||
f <- file.path(RUNS_DIR, sprintf("%s-seed%s.csv", arch, seed))
|
||||
raw <- readLines(f)
|
||||
data_lines <- raw[grepl("^[0-9]", raw)]
|
||||
df <- read.csv(text = paste(data_lines, collapse = "\n"),
|
||||
header = FALSE, col.names = col_names,
|
||||
strip.white = TRUE)
|
||||
df$arch <- arch
|
||||
df$seed <- seed
|
||||
df
|
||||
}
|
||||
|
||||
cat("Loading 9 cells...\n")
|
||||
all_data <- bind_rows(lapply(archs, function(a) {
|
||||
bind_rows(lapply(seeds, function(s) load_cell(a, s)))
|
||||
}))
|
||||
cat(sprintf(" Total rows loaded: %d (expect 4320)\n", nrow(all_data)))
|
||||
|
||||
# ── per-cell completeness table ───────────────────────────────────────────
|
||||
cell_summary <- all_data %>%
|
||||
group_by(arch, seed) %>%
|
||||
summarise(
|
||||
n_rows = n(),
|
||||
n_distinct_run_id = n_distinct(run_id),
|
||||
l8_modes = n_distinct(l8_mode),
|
||||
mean_win_div = mean(win_div),
|
||||
mean_fit_q = mean(fit_q),
|
||||
mean_bc_mean_q = mean(bc_mean_q),
|
||||
mean_bb_mean_q = mean(bb_mean_q),
|
||||
.groups = "drop"
|
||||
)
|
||||
cell_summary$arch <- factor(cell_summary$arch, levels = archs)
|
||||
write.csv(cell_summary, file.path(OUT_TABLES, "stadium_relaunch_cell_summary.csv"),
|
||||
row.names = FALSE)
|
||||
cat("\nCell summary:\n")
|
||||
print(as.data.frame(cell_summary))
|
||||
|
||||
# ── cross-arch / cross-seed invariance tests (Kruskal-Wallis, matches the
|
||||
# original report's own non-parametric methodology for this kind of
|
||||
# campaign-level distributional comparison) ─────────────────────────────
|
||||
kw_arch_fit <- kruskal.test(fit_q ~ arch, data = all_data)
|
||||
kw_arch_win <- kruskal.test(win_div ~ arch, data = all_data)
|
||||
kw_seed_fit <- kruskal.test(fit_q ~ seed, data = all_data)
|
||||
kw_seed_win <- kruskal.test(win_div ~ seed, data = all_data)
|
||||
|
||||
kw_results <- capture.output({
|
||||
cat("Kruskal-Wallis: fit_q ~ arch\n"); print(kw_arch_fit); cat("\n")
|
||||
cat("Kruskal-Wallis: win_div ~ arch\n"); print(kw_arch_win); cat("\n")
|
||||
cat("Kruskal-Wallis: fit_q ~ seed\n"); print(kw_seed_fit); cat("\n")
|
||||
cat("Kruskal-Wallis: win_div ~ seed\n"); print(kw_seed_win); cat("\n")
|
||||
})
|
||||
writeLines(kw_results, file.path(OUT_TABLES, "stadium_relaunch_kw_results.txt"))
|
||||
cat("\n")
|
||||
cat(paste(kw_results, collapse = "\n"))
|
||||
cat("\n\n")
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# FIGURE 1: 3x3 Latin square completeness heatmap (rows captured per cell)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
cat("[SR-1] Completeness heatmap (light + dark)...\n")
|
||||
|
||||
df_heat <- cell_summary %>% mutate(seed = factor(seed, levels = seeds))
|
||||
|
||||
make_heatmap <- function(dark = FALSE) {
|
||||
thm <- if (dark) theme_dark_sf() else theme_light_sf()
|
||||
txt <- if (dark) "white" else "grey10"
|
||||
ggplot(df_heat, aes(x = seed, y = arch, fill = n_rows)) +
|
||||
geom_tile(colour = if (dark) "#0d0d0d" else "white", linewidth = 1.5) +
|
||||
geom_text(aes(label = sprintf("%d/480", n_rows)), colour = txt,
|
||||
fontface = "bold", size = 4.2) +
|
||||
scale_fill_gradient(low = "#c0392b", high = "#2CA02C", limits = c(0, 480),
|
||||
name = "Rows captured") +
|
||||
labs(
|
||||
title = "EXEC-DOE Campaign Completeness — Stadium Substrate",
|
||||
subtitle = "3x3 Latin square: 3 seeds x 3 ISAs, 30 reps x 16 L8 configs per cell (480 rows/cell)",
|
||||
x = "Seed", y = "Architecture"
|
||||
) +
|
||||
thm
|
||||
}
|
||||
save_svg(make_heatmap(FALSE), "stadium_relaunch_completeness_light", w = 9, h = 6)
|
||||
save_svg(make_heatmap(TRUE), "stadium_relaunch_completeness_dark", w = 9, h = 6)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# FIGURE 2: fit_q distribution by architecture (all seeds pooled)
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
cat("[SR-2] fit_q distribution by architecture (light + dark)...\n")
|
||||
|
||||
all_data$arch <- factor(all_data$arch, levels = archs)
|
||||
|
||||
make_fit_box <- function(dark = FALSE) {
|
||||
thm <- if (dark) theme_dark_sf() else theme_light_sf()
|
||||
ggplot(all_data, aes(x = arch, y = fit_q, fill = arch)) +
|
||||
geom_boxplot(alpha = 0.85, outlier.size = 0.6, outlier.alpha = 0.4) +
|
||||
scale_fill_manual(values = arch_colours, guide = "none") +
|
||||
scale_x_discrete(labels = c(amd64 = "amd64\n(x86-64)",
|
||||
aarch64 = "aarch64\n(ARMv8-A)",
|
||||
riscv64 = "riscv64\n(RV64GC)")) +
|
||||
labs(
|
||||
title = "Inference-Fit Distribution Across ISAs",
|
||||
subtitle = sprintf(
|
||||
"Kruskal-Wallis fit_q ~ arch: H=%.3f, p=%.4f (all 4,320 rows, 3 seeds pooled per ISA)",
|
||||
kw_arch_fit$statistic, kw_arch_fit$p.value
|
||||
),
|
||||
x = "Instruction-Set Architecture", y = "fit_q (Q16 fixed-point)"
|
||||
) +
|
||||
thm
|
||||
}
|
||||
save_svg(make_fit_box(FALSE), "stadium_relaunch_fitq_box_light", w = 9, h = 6)
|
||||
save_svg(make_fit_box(TRUE), "stadium_relaunch_fitq_box_dark", w = 9, h = 6)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
# FIGURE 3: mean win_div per (arch, seed) cell — grouped bar
|
||||
# ══════════════════════════════════════════════════════════════════════════
|
||||
cat("[SR-3] Mean window-diversity per cell (light + dark)...\n")
|
||||
|
||||
df_bar <- cell_summary %>% mutate(seed = factor(seed, levels = seeds))
|
||||
|
||||
make_windiv_bar <- function(dark = FALSE) {
|
||||
thm <- if (dark) theme_dark_sf() else theme_light_sf()
|
||||
ggplot(df_bar, aes(x = seed, y = mean_win_div, fill = arch)) +
|
||||
geom_col(position = position_dodge(width = 0.75), width = 0.65,
|
||||
colour = NA, alpha = 0.92) +
|
||||
scale_fill_manual(values = arch_colours, name = "ISA") +
|
||||
labs(
|
||||
title = "Mean Window-Diversity per Campaign Cell",
|
||||
subtitle = "9 cells, 480 runs each, Stadium substrate (post item-4.6 quota-grant fix)",
|
||||
x = "Seed", y = "Mean win_div"
|
||||
) +
|
||||
thm
|
||||
}
|
||||
save_svg(make_windiv_bar(FALSE), "stadium_relaunch_windiv_bar_light", w = 9, h = 6)
|
||||
save_svg(make_windiv_bar(TRUE), "stadium_relaunch_windiv_bar_dark", w = 9, h = 6)
|
||||
|
||||
cat("\nDone. Tables in analysis/tables/, charts in analysis/charts/.\n")
|
||||
Reference in New Issue
Block a user