512 lines
23 KiB
R
512 lines
23 KiB
R
#!/usr/bin/env Rscript
|
||
# analyse_multirep.R
|
||
# StarForth LithosAnanke — Multi-Replication DoE Analysis
|
||
#
|
||
# Pools all 9 outer DoE cells (3 reps × 3 arches) and tests:
|
||
# 1. Replication main effect on attractor metrics (should be nil)
|
||
# 2. Seed → shuffle independence (independent permutations = independent reps)
|
||
# 3. Inter-rep consistency of execution rate invariance
|
||
# 4. Per-rep Kruskal-Wallis to verify arch divergence in timer quality is
|
||
# reproducible across seeds
|
||
#
|
||
# Rep 1: seed 12345 (amd64, aarch64, riscv64)
|
||
# Rep 2: seed 67890 (riscv64, aarch64, amd64)
|
||
# Rep 3: seed 13579 (aarch64, amd64, riscv64)
|
||
|
||
suppressPackageStartupMessages({
|
||
library(ggplot2)
|
||
library(svglite)
|
||
library(dplyr)
|
||
library(tidyr)
|
||
library(scales)
|
||
library(patchwork)
|
||
library(viridis)
|
||
})
|
||
|
||
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")
|
||
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 — Multi-Replication DoE Analysis\n")
|
||
cat(" 9 outer cells (3 reps × 3 arches), ~783k rows each rep\n")
|
||
cat("══════════════════════════════════════════════════════════════════\n\n")
|
||
|
||
Q48 <- 65536.0
|
||
|
||
# ── manifest of all 9 canonical runs ─────────────────────────────────────────
|
||
manifest <- list(
|
||
list(rep=1, seed=12345, arch="amd64", file="doe-amd64-20260612-110212.csv"),
|
||
list(rep=1, seed=12345, arch="aarch64", file="doe-aarch64-20260612-132412.csv"),
|
||
list(rep=1, seed=12345, arch="riscv64", file="doe-riscv64-20260612-135612.csv"),
|
||
list(rep=2, seed=67890, arch="riscv64", file="doe-riscv64-20260612-141129.csv"),
|
||
list(rep=2, seed=67890, arch="aarch64", file="doe-aarch64-20260612-141722.csv"),
|
||
list(rep=2, seed=67890, arch="amd64", file="doe-amd64-20260612-142323.csv"),
|
||
list(rep=3, seed=13579, arch="aarch64", file="doe-aarch64-20260612-160132.csv"),
|
||
list(rep=3, seed=13579, arch="amd64", file="doe-amd64-20260612-160656.csv"),
|
||
list(rep=3, seed=13579, arch="riscv64", file="doe-riscv64-20260612-174414.csv")
|
||
)
|
||
|
||
load_run <- function(m) {
|
||
path <- file.path(RUNS_DIR, m$file)
|
||
cat(sprintf(" Rep%d seed=%-6d %-8s ... ", m$rep, m$seed, m$arch))
|
||
df <- read.csv(path, stringsAsFactors = FALSE)
|
||
cat(sprintf("%d rows\n", nrow(df)))
|
||
df$rep <- m$rep
|
||
df$seed <- m$seed
|
||
df$arch <- m$arch
|
||
|
||
df <- df[!is.na(df$tick_interval_ns) & df$tick_interval_ns > 0, ]
|
||
|
||
df$heat <- df$avg_word_heat_q48 / Q48
|
||
df$variance <- df$variance_q48 / Q48
|
||
df$time_trust <- df$time_trust_q48 / Q48
|
||
df$elapsed_s <- df$elapsed_ns / 1e9
|
||
df
|
||
}
|
||
|
||
all_runs <- lapply(manifest, load_run)
|
||
df_all <- bind_rows(all_runs)
|
||
df_all$arch <- factor(df_all$arch, levels = c("amd64","aarch64","riscv64"))
|
||
df_all$rep <- factor(df_all$rep)
|
||
|
||
cat(sprintf("\n Total rows across all 9 cells: %s\n\n",
|
||
formatC(nrow(df_all), format="d", big.mark=",")))
|
||
|
||
# ── 1. Per-cell summary ───────────────────────────────────────────────────────
|
||
cat("Computing per-cell summary (rep × arch)...\n")
|
||
|
||
cell_tbl <- df_all %>%
|
||
group_by(rep, seed, arch) %>%
|
||
summarise(
|
||
n_ticks = n(),
|
||
mean_exec_delta = mean(word_executions_delta, na.rm=TRUE),
|
||
sd_exec_delta = sd(word_executions_delta, na.rm=TRUE),
|
||
mean_heat = mean(heat, na.rm=TRUE),
|
||
max_heat = max(heat, na.rm=TRUE),
|
||
mean_trust = mean(time_trust, na.rm=TRUE),
|
||
mean_variance = mean(variance, na.rm=TRUE),
|
||
mean_win = mean(window_width, na.rm=TRUE),
|
||
.groups = "drop"
|
||
)
|
||
|
||
write.csv(cell_tbl, file.path(OUT_TABLES, "cell_summary.csv"), row.names=FALSE)
|
||
cat(" Saved: cell_summary.csv\n")
|
||
print(as.data.frame(cell_tbl))
|
||
cat("\n")
|
||
|
||
# ── 2. Replication main effect on exec_delta (should be F≈0, p≈1) ───────────
|
||
cat("ANOVA: exec_delta ~ rep (should show nil replication effect)...\n")
|
||
fit_rep <- aov(mean_exec_delta ~ rep, data = cell_tbl)
|
||
print(summary(fit_rep))
|
||
cat("\n")
|
||
|
||
# ── 3. Per-rep Kruskal-Wallis: timer quality divergence reproducible? ─────────
|
||
cat("Per-rep Kruskal-Wallis: time_trust ~ arch (divergent region only)\n")
|
||
cat("──────────────────────────────────────────────────────────────────\n")
|
||
|
||
kw_results <- list()
|
||
for (r in 1:3) {
|
||
df_r <- df_all %>%
|
||
filter(rep == r) %>%
|
||
group_by(arch) %>%
|
||
filter(row_number() > 26251) %>%
|
||
ungroup()
|
||
|
||
kw <- kruskal.test(time_trust ~ arch, data = df_r)
|
||
kw_results[[r]] <- data.frame(
|
||
rep = r,
|
||
seed = unique(df_all$seed[df_all$rep == r]),
|
||
H = round(kw$statistic, 1),
|
||
df = kw$parameter,
|
||
p_value = kw$p.value,
|
||
decision = if (kw$p.value < 2.2e-16) "REJECT H0 (arch divergence confirmed)" else "fail to reject"
|
||
)
|
||
cat(sprintf(" Rep %d (seed %5d): H=%.1f df=%d p=%.2e → %s\n",
|
||
r, kw_results[[r]]$seed, kw$statistic, kw$parameter, kw$p.value,
|
||
kw_results[[r]]$decision))
|
||
}
|
||
kw_tbl <- bind_rows(kw_results)
|
||
write.csv(kw_tbl, file.path(OUT_TABLES, "kw_per_rep.csv"), row.names=FALSE)
|
||
cat(" Saved: kw_per_rep.csv\n\n")
|
||
|
||
# ── 4. Exec rate: stable across reps? ────────────────────────────────────────
|
||
cat("Exec rate stability across reps (per arch):\n")
|
||
exec_stability <- cell_tbl %>%
|
||
group_by(arch) %>%
|
||
summarise(
|
||
rep_mean_of_means = mean(mean_exec_delta),
|
||
rep_sd_of_means = sd(mean_exec_delta),
|
||
cv_across_reps = sd(mean_exec_delta) / mean(mean_exec_delta) * 100,
|
||
.groups = "drop"
|
||
)
|
||
print(as.data.frame(exec_stability))
|
||
write.csv(exec_stability, file.path(OUT_TABLES, "exec_stability.csv"), row.names=FALSE)
|
||
cat(" Saved: exec_stability.csv\n\n")
|
||
|
||
# ── colour themes ─────────────────────────────────────────────────────────────
|
||
arch_colours <- c(amd64 = "#E07B39",
|
||
aarch64 = "#4A90D9",
|
||
riscv64 = "#50C878")
|
||
rep_colours <- c("1" = "#E07B39", "2" = "#4A90D9", "3" = "#50C878")
|
||
|
||
theme_light_report <- function() {
|
||
theme_minimal(base_size = 11) %+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 = 12),
|
||
plot.subtitle = element_text(colour = "grey40", size = 9),
|
||
legend.position = "bottom"
|
||
)
|
||
}
|
||
|
||
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)
|
||
}
|
||
|
||
cat("Generating multi-rep charts...\n\n")
|
||
|
||
# ── CHART MR-1: Exec rate per cell (3×3 grid, rep×arch) ──────────────────────
|
||
cat("[MR-1] Per-cell execution rate comparison...\n")
|
||
|
||
p_exec_cells <- ggplot(cell_tbl,
|
||
aes(x = arch, y = mean_exec_delta, colour = rep, shape = rep)) +
|
||
geom_point(size = 4, alpha = 0.85) +
|
||
geom_errorbar(aes(ymin = mean_exec_delta - sd_exec_delta,
|
||
ymax = mean_exec_delta + sd_exec_delta),
|
||
width = 0.15, alpha = 0.5) +
|
||
scale_colour_manual(values = rep_colours, name = "Replication") +
|
||
scale_shape_manual(values = c("1"=16,"2"=17,"3"=15), name = "Replication") +
|
||
scale_y_continuous(limits = c(253, 259)) +
|
||
labs(
|
||
title = "Execution Rate per Cell (mean ± SD)",
|
||
subtitle = "3 replications × 3 architectures — rate invariant across ISA and seed",
|
||
x = "Architecture",
|
||
y = "Words per 10 µs tick"
|
||
) +
|
||
theme_light_report()
|
||
|
||
save_svg(p_exec_cells, "multirep_exec_rate", w = 8, h = 6)
|
||
|
||
# ── CHART MR-2: Heat mean per cell ────────────────────────────────────────────
|
||
cat("[MR-2] Per-cell heat comparison...\n")
|
||
|
||
p_heat_cells <- ggplot(cell_tbl,
|
||
aes(x = rep, y = mean_heat, fill = arch, colour = arch)) +
|
||
geom_col(position = "dodge", alpha = 0.8, width = 0.6) +
|
||
scale_fill_manual(values = arch_colours) +
|
||
scale_colour_manual(values = arch_colours) +
|
||
labs(
|
||
title = "Mean Word Heat by Replication × Architecture",
|
||
subtitle = "amd64 heat ~8× higher due to APIC residual accumulation; consistent across reps",
|
||
x = "Replication",
|
||
y = "Mean avg word heat (Q48.16 decoded)"
|
||
) +
|
||
theme_light_report()
|
||
|
||
save_svg(p_heat_cells, "multirep_heat", w = 8, h = 6)
|
||
|
||
# ── CHART MR-3: Trust mean per cell ───────────────────────────────────────────
|
||
cat("[MR-3] Per-cell time-trust comparison...\n")
|
||
|
||
p_trust_cells <- ggplot(cell_tbl,
|
||
aes(x = rep, y = mean_trust, fill = arch, colour = arch)) +
|
||
geom_col(position = "dodge", alpha = 0.8, width = 0.6) +
|
||
scale_fill_manual(values = arch_colours) +
|
||
scale_colour_manual(values = arch_colours) +
|
||
coord_cartesian(ylim = c(0.99, 1.001)) +
|
||
labs(
|
||
title = "Mean Time-Trust by Replication × Architecture",
|
||
subtitle = "amd64 trust ~0.993; aarch64/riscv64 = 1.000 exactly — reproducible across all 3 seeds",
|
||
x = "Replication",
|
||
y = "Mean time_trust (Q48.16 decoded)"
|
||
) +
|
||
theme_light_report()
|
||
|
||
save_svg(p_trust_cells, "multirep_trust", w = 8, h = 6)
|
||
|
||
# ── CHART MR-4: KW H-statistic across reps (robustness check) ────────────────
|
||
cat("[MR-4] KW H-statistic reproducibility across reps...\n")
|
||
|
||
p_kw <- ggplot(kw_tbl, aes(x = factor(rep), y = H, fill = factor(rep))) +
|
||
geom_col(alpha = 0.85, width = 0.5) +
|
||
geom_text(aes(label = formatC(H, format="f", digits=0)),
|
||
vjust = -0.3, size = 4, fontface = "bold") +
|
||
scale_fill_manual(values = rep_colours, guide = "none") +
|
||
scale_y_continuous(labels = scales::comma, expand = expansion(mult = c(0, 0.15))) +
|
||
labs(
|
||
title = "Kruskal-Wallis H-Statistic: Timer Quality Divergence per Replication",
|
||
subtitle = "H ≈ 668,000 in all three reps — arch divergence is fully reproducible across seeds",
|
||
x = "Replication",
|
||
y = "K-W H statistic"
|
||
) +
|
||
theme_light_report() +
|
||
theme(legend.position = "none")
|
||
|
||
save_svg(p_kw, "multirep_kw_h", w = 7, h = 6)
|
||
|
||
# ── CHART MR-5: Window Poincaré map — overlay all 3 reps by arch ─────────────
|
||
cat("[MR-5] Multi-rep Poincaré map overlay (all 9 cells)...\n")
|
||
|
||
df_poin <- df_all %>%
|
||
mutate(
|
||
win_next = ave(window_width, interaction(rep, arch),
|
||
FUN = function(x) c(x[-1], NA_real_))
|
||
) %>%
|
||
filter(!is.na(win_next), window_width > 0, win_next > 0)
|
||
|
||
p_poin_mr <- ggplot(df_poin,
|
||
aes(x = log2(window_width), y = log2(win_next), colour = rep)) +
|
||
stat_bin_2d(bins = 40, aes(fill = after_stat(count)), alpha = 0.7) +
|
||
scale_fill_viridis_c(option = "plasma", trans = "sqrt",
|
||
name = "Density", labels = scales::comma) +
|
||
scale_colour_manual(values = rep_colours, guide = "none") +
|
||
scale_x_continuous(breaks = log2(c(256,512,1024,2048,4096)),
|
||
labels = c("256","512","1024","2048","4096")) +
|
||
scale_y_continuous(breaks = log2(c(256,512,1024,2048,4096)),
|
||
labels = c("256","512","1024","2048","4096")) +
|
||
geom_abline(slope = 1, intercept = 0, colour = "grey75",
|
||
linewidth = 0.4, linetype = "dashed") +
|
||
facet_wrap(~ arch, ncol = 3) +
|
||
labs(
|
||
title = "Multi-Replication Window Poincaré Map (Reps 1–3 pooled)",
|
||
subtitle = "3 seeds × 3 arches — attractor orbit structure identical across all replications",
|
||
x = expression(W[n]~"(log"[2]*" scale)"),
|
||
y = expression(W[n+1]~"(log"[2]*" scale)")
|
||
) +
|
||
theme_light_report() +
|
||
theme(legend.position = "right")
|
||
|
||
save_svg(p_poin_mr, "multirep_poincare", w = 14, h = 6)
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# CHART MR-6+7: 3×3 Thermal Phase Portrait — "the attractor grid"
|
||
# Rows = replication (Rep 1/2/3), Columns = architecture
|
||
# Styled after L8 capsule mode portraits: bold facet labels, dark + light
|
||
# Shows: same attractor across ALL 9 outer DoE cells
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
cat("[MR-6+7] 3×3 attractor grid — thermal portrait (light + dark)...\n")
|
||
|
||
df_th9 <- df_all %>%
|
||
mutate(
|
||
delta_heat = ave(heat, interaction(rep, arch),
|
||
FUN = function(x) c(diff(x), NA_real_))
|
||
) %>%
|
||
filter(!is.na(delta_heat)) %>%
|
||
group_by(arch, rep) %>%
|
||
filter(abs(delta_heat) < quantile(abs(delta_heat), 0.995, na.rm = TRUE)) %>%
|
||
ungroup() %>%
|
||
mutate(
|
||
arch_label = factor(
|
||
case_when(
|
||
arch == "amd64" ~ "x86-64 (amd64)",
|
||
arch == "aarch64" ~ "ARMv8-A (aarch64)",
|
||
arch == "riscv64" ~ "RISC-V (riscv64)"
|
||
),
|
||
levels = c("x86-64 (amd64)", "ARMv8-A (aarch64)", "RISC-V (riscv64)")
|
||
),
|
||
rep_label = factor(
|
||
case_when(
|
||
rep == "1" ~ "REP 1 · seed 12345",
|
||
rep == "2" ~ "REP 2 · seed 67890",
|
||
rep == "3" ~ "REP 3 · seed 13579"
|
||
),
|
||
levels = c("REP 1 · seed 12345",
|
||
"REP 2 · seed 67890",
|
||
"REP 3 · seed 13579")
|
||
)
|
||
)
|
||
|
||
make_attractor_grid <- function(dark = FALSE) {
|
||
bg <- if (dark) "#0d0d0d" else "white"
|
||
lo <- if (dark) "#0d0d0d" else "white"
|
||
mid <- if (dark) "#003366" else "#ffdd00"
|
||
hi <- if (dark) "#00e5ff" else "#cc2200"
|
||
vhigh <- if (dark) "white" else "#1a0000"
|
||
grid_col <- if (dark) "#1a1a2e" else "grey88"
|
||
txt_col <- if (dark) "#aaaaaa" else "grey25"
|
||
ttl_col <- if (dark) "white" else "black"
|
||
stl_col <- if (dark) "#666666" else "grey45"
|
||
|
||
ggplot(df_th9, aes(x = heat, y = delta_heat)) +
|
||
stat_bin_2d(bins = 120, aes(fill = after_stat(count))) +
|
||
scale_fill_gradientn(
|
||
colours = c(lo, mid, hi, vhigh),
|
||
values = scales::rescale(c(0, 0.05, 0.30, 1)),
|
||
name = "Density",
|
||
trans = "sqrt",
|
||
labels = scales::comma
|
||
) +
|
||
geom_hline(yintercept = 0,
|
||
colour = if (dark) "#2a3a4a" else "grey78",
|
||
linewidth = 0.4, linetype = "dashed") +
|
||
facet_grid(rep_label ~ arch_label) +
|
||
labs(
|
||
title = "Compudynamic Attractor — Thermal Phase Space (All 9 Cells)",
|
||
subtitle = expression(paste("Heat"[n], " vs ", Delta, "Heat"[n],
|
||
" · 3 replications × 3 architectures · attractor topology invariant")),
|
||
x = "Avg word heat (Q48.16 decoded)",
|
||
y = expression(Delta * "Heat per tick")
|
||
) +
|
||
theme_minimal(base_size = 10) %+replace%
|
||
theme(
|
||
panel.background = element_rect(fill = bg, colour = NA),
|
||
plot.background = element_rect(fill = bg, colour = NA),
|
||
panel.grid.major = element_line(colour = grid_col),
|
||
panel.grid.minor = element_blank(),
|
||
axis.text = element_text(colour = txt_col, size = 7),
|
||
axis.title = element_text(colour = txt_col, size = 9),
|
||
# Column labels (architecture) — bold, prominent like the reference
|
||
strip.text.x = element_text(colour = ttl_col, face = "bold",
|
||
size = 10, margin = margin(b = 4)),
|
||
# Row labels (replication)
|
||
strip.text.y = element_text(colour = ttl_col, face = "bold",
|
||
size = 8, angle = 270,
|
||
margin = margin(l = 4)),
|
||
strip.background = element_rect(fill = if(dark) "#1a1a2e" else "grey95",
|
||
colour = NA),
|
||
plot.title = element_text(colour = ttl_col, face = "bold",
|
||
size = 12, margin = margin(b = 4)),
|
||
plot.subtitle = element_text(colour = stl_col, size = 8,
|
||
margin = margin(b = 8)),
|
||
legend.position = "right",
|
||
legend.background = element_rect(fill = bg, colour = NA),
|
||
legend.text = element_text(colour = txt_col, size = 7),
|
||
legend.title = element_text(colour = txt_col, size = 8),
|
||
panel.spacing = unit(0.6, "lines")
|
||
)
|
||
}
|
||
|
||
save_svg(make_attractor_grid(FALSE), "attractor_grid_light", w = 14, h = 10)
|
||
save_svg(make_attractor_grid(TRUE), "attractor_grid_dark", w = 14, h = 10)
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# CHART MR-8+9: 3×3 Poincaré Map Grid — window attractor, all 9 cells
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
cat("[MR-8+9] 3×3 attractor grid — Poincaré map (light + dark)...\n")
|
||
|
||
df_po9 <- df_all %>%
|
||
mutate(
|
||
win_next = ave(window_width, interaction(rep, arch),
|
||
FUN = function(x) c(x[-1], NA_real_))
|
||
) %>%
|
||
filter(!is.na(win_next), window_width > 0, win_next > 0) %>%
|
||
mutate(
|
||
arch_label = factor(
|
||
case_when(
|
||
arch == "amd64" ~ "x86-64 (amd64)",
|
||
arch == "aarch64" ~ "ARMv8-A (aarch64)",
|
||
arch == "riscv64" ~ "RISC-V (riscv64)"
|
||
),
|
||
levels = c("x86-64 (amd64)", "ARMv8-A (aarch64)", "RISC-V (riscv64)")
|
||
),
|
||
rep_label = factor(
|
||
case_when(
|
||
rep == "1" ~ "REP 1 · seed 12345",
|
||
rep == "2" ~ "REP 2 · seed 67890",
|
||
rep == "3" ~ "REP 3 · seed 13579"
|
||
),
|
||
levels = c("REP 1 · seed 12345",
|
||
"REP 2 · seed 67890",
|
||
"REP 3 · seed 13579")
|
||
)
|
||
)
|
||
|
||
make_poincare_grid <- function(dark = FALSE) {
|
||
bg <- if (dark) "#0d0d0d" else "white"
|
||
lo <- if (dark) "#0d0d0d" else "white"
|
||
mid <- if (dark) "#003366" else "#ffdd00"
|
||
hi <- if (dark) "#00e5ff" else "#cc2200"
|
||
vhigh <- if (dark) "white" else "#1a0000"
|
||
grid_col <- if (dark) "#1a1a2e" else "grey88"
|
||
txt_col <- if (dark) "#aaaaaa" else "grey25"
|
||
ttl_col <- if (dark) "white" else "black"
|
||
stl_col <- if (dark) "#666666" else "grey45"
|
||
|
||
ggplot(df_po9, aes(x = log2(window_width), y = log2(win_next))) +
|
||
stat_bin_2d(bins = 40, aes(fill = after_stat(count))) +
|
||
scale_fill_gradientn(
|
||
colours = c(lo, mid, hi, vhigh),
|
||
values = scales::rescale(c(0, 0.05, 0.30, 1)),
|
||
name = "Density",
|
||
trans = "sqrt",
|
||
labels = scales::comma
|
||
) +
|
||
geom_abline(slope = 1, intercept = 0,
|
||
colour = if (dark) "#2a3a4a" else "grey78",
|
||
linewidth = 0.4, linetype = "dashed") +
|
||
scale_x_continuous(
|
||
breaks = log2(c(256, 512, 1024, 2048, 4096)),
|
||
labels = c("256","512","1024","2048","4096")
|
||
) +
|
||
scale_y_continuous(
|
||
breaks = log2(c(256, 512, 1024, 2048, 4096)),
|
||
labels = c("256","512","1024","2048","4096")
|
||
) +
|
||
facet_grid(rep_label ~ arch_label) +
|
||
labs(
|
||
title = "Compudynamic Attractor — Window Poincaré Map (All 9 Cells)",
|
||
subtitle = expression(paste(W[n], " vs ", W[n+1],
|
||
" (log"[2], " scale) · breathing cycle 256 ↔ 4096 preserved across all cells")),
|
||
x = expression(W[n]~"(words, log"[2]*" scale)"),
|
||
y = expression(W[n+1]~"(words, log"[2]*" scale)")
|
||
) +
|
||
theme_minimal(base_size = 10) %+replace%
|
||
theme(
|
||
panel.background = element_rect(fill = bg, colour = NA),
|
||
plot.background = element_rect(fill = bg, colour = NA),
|
||
panel.grid.major = element_line(colour = grid_col),
|
||
panel.grid.minor = element_blank(),
|
||
axis.text = element_text(colour = txt_col, size = 6),
|
||
axis.title = element_text(colour = txt_col, size = 9),
|
||
strip.text.x = element_text(colour = ttl_col, face = "bold",
|
||
size = 10, margin = margin(b = 4)),
|
||
strip.text.y = element_text(colour = ttl_col, face = "bold",
|
||
size = 8, angle = 270,
|
||
margin = margin(l = 4)),
|
||
strip.background = element_rect(fill = if(dark) "#1a1a2e" else "grey95",
|
||
colour = NA),
|
||
plot.title = element_text(colour = ttl_col, face = "bold",
|
||
size = 12, margin = margin(b = 4)),
|
||
plot.subtitle = element_text(colour = stl_col, size = 8,
|
||
margin = margin(b = 8)),
|
||
legend.position = "right",
|
||
legend.background = element_rect(fill = bg, colour = NA),
|
||
legend.text = element_text(colour = txt_col, size = 7),
|
||
legend.title = element_text(colour = txt_col, size = 8),
|
||
panel.spacing = unit(0.6, "lines")
|
||
)
|
||
}
|
||
|
||
save_svg(make_poincare_grid(FALSE), "poincare_grid_light", w = 14, h = 10)
|
||
save_svg(make_poincare_grid(TRUE), "poincare_grid_dark", w = 14, h = 10)
|
||
|
||
# ── summary ───────────────────────────────────────────────────────────────────
|
||
svg_new <- list.files(OUT_CHARTS, pattern = "^multirep.*\\.svg$")
|
||
cat(sprintf("\n══════════════════════════════════════════════════════════════════\n"))
|
||
cat(sprintf(" Multi-rep analysis complete. %d new charts.\n", length(svg_new)))
|
||
cat(sprintf("══════════════════════════════════════════════════════════════════\n\n"))
|
||
|
||
cat("Key findings:\n")
|
||
cat(sprintf(" Exec rate CV across reps: amd64=%.4f%% / aarch64=%.4f%% / riscv64=%.4f%%\n",
|
||
exec_stability$cv_across_reps[exec_stability$arch=="amd64"],
|
||
exec_stability$cv_across_reps[exec_stability$arch=="aarch64"],
|
||
exec_stability$cv_across_reps[exec_stability$arch=="riscv64"]))
|
||
cat(sprintf(" KW H range: %.0f – %.0f (all p < 2.2e-16)\n",
|
||
min(kw_tbl$H), max(kw_tbl$H)))
|
||
cat(" Conclusion: attractor metrics stable across seeds; timer divergence reproducible.\n\n")
|