Files
LithosAnanake/scripts/analyze_heartbeat_stability.R

384 lines
16 KiB
R
Executable File

#!/usr/bin/env Rscript
################################################################################
#
# analyze_heartbeat_stability.R
# StarForth Heartbeat DoE Analysis Template
#
# Purpose: Analyze heartbeat stability metrics from Phase 2 DoE experiment
# Inputs: experiment_results_heartbeat.csv from run_factorial_doe_with_heartbeat.sh
# Outputs: Stability rankings, visualizations, golden configuration recommendation
#
# Usage:
# Rscript analyze_heartbeat_stability.R /path/to/experiment_results_heartbeat.csv
# Rscript analyze_heartbeat_stability.R # Uses ./experiment_results_heartbeat.csv
#
# Dependencies:
# library(tidyverse) # Data manipulation
# library(ggplot2) # Visualization
# library(gridExtra) # Multi-panel plots
#
################################################################################
library(tidyverse)
library(ggplot2)
library(gridExtra)
# ============================================================================
# Configuration
# ============================================================================
# Get input file from command line or use default
args <- commandArgs(trailingOnly = TRUE)
if (length(args) > 0) {
csv_file <- args[1]
} else {
csv_file <- "experiment_results_heartbeat.csv"
}
if (!file.exists(csv_file)) {
stop(sprintf("Error: File not found: %s", csv_file))
}
cat("Loading data from:", csv_file, "\n")
# ============================================================================
# Load and Prepare Data
# ============================================================================
doe_data <- read.csv(csv_file, check.names = FALSE) %>%
mutate(
configuration = as.factor(configuration),
timestamp = as.POSIXct(timestamp),
# Ensure all numeric columns are numeric
across(starts_with("tick_"), as.numeric),
across(starts_with("cache_hit_"), as.numeric),
across(starts_with("window_"), as.numeric),
across(starts_with("decay_slope_"), as.numeric),
across(starts_with("load_"), as.numeric),
across(starts_with("settling_"), as.numeric),
overall_stability_score = as.numeric(overall_stability_score)
)
cat("\n=== DATA SUMMARY ===\n")
cat(sprintf("Total runs: %d\n", nrow(doe_data)))
cat(sprintf("Configurations: %d\n", n_distinct(doe_data$configuration)))
cat(sprintf("Runs per config: %s\n",
paste(unique(table(doe_data$configuration)), collapse = ", ")))
cat(sprintf("Columns: %d\n", ncol(doe_data)))
# ============================================================================
# MAIN ANALYSIS: Stability Rankings
# ============================================================================
cat("\n=== STABILITY RANKINGS ===\n")
stability_rank <- doe_data %>%
group_by(configuration) %>%
summarize(
n_runs = n(),
# Jitter control (lower CV = better)
mean_jitter_cv = mean(tick_interval_cv, na.rm = TRUE),
sd_jitter_cv = sd(tick_interval_cv, na.rm = TRUE),
# Outlier ratio (lower = better)
mean_outlier_ratio = mean(tick_outlier_ratio, na.rm = TRUE),
# Convergence speed (higher = faster)
mean_convergence_rate = mean(decay_slope_convergence_rate, na.rm = TRUE),
sd_convergence_rate = sd(decay_slope_convergence_rate, na.rm = TRUE),
# Load response coupling (higher = better)
mean_load_correlation = mean(load_interval_correlation, na.rm = TRUE),
# Overall stability (higher = better)
mean_stability_score = mean(overall_stability_score, na.rm = TRUE),
sd_stability_score = sd(overall_stability_score, na.rm = TRUE),
# Settling time (lower = faster)
mean_settling_time = mean(settling_time_ticks, na.rm = TRUE),
# Performance (duration, lower = faster)
mean_workload_ms = mean(vm_workload_duration_ns_q48 / 65536 / 1e6, na.rm = TRUE)
) %>%
mutate(
rank_stability = rank(-mean_stability_score, ties.method = "min"),
rank_jitter = rank(mean_jitter_cv, ties.method = "min"),
rank_convergence = rank(-mean_convergence_rate, ties.method = "min"),
rank_coupling = rank(-mean_load_correlation, ties.method = "min"),
overall_rank = (rank_stability + rank_jitter + rank_convergence + rank_coupling) / 4
) %>%
arrange(rank_stability)
print(stability_rank)
# Write rankings to file
write.csv(stability_rank, "stability_rankings.csv", row.names = FALSE)
cat("\nRankings saved to: stability_rankings.csv\n")
# ============================================================================
# VISUALIZATION 1: Stability Score Boxplots
# ============================================================================
cat("\n=== VISUALIZATION 1: Stability Scores ===\n")
p1 <- ggplot(doe_data, aes(x = reorder(configuration, overall_stability_score, FUN=median),
y = overall_stability_score,
fill = configuration)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
geom_jitter(width = 0.2, alpha = 0.4, size = 2) +
coord_flip() +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12),
legend.position = "none"
) +
labs(title = "Overall Stability Scores by Configuration",
subtitle = "Higher = more stable (0-100 scale)",
x = "Configuration",
y = "Stability Score")
ggsave("01_stability_scores.png", p1, width = 10, height = 6, dpi = 300)
cat("Saved: 01_stability_scores.png\n")
# ============================================================================
# VISUALIZATION 2: Jitter Control (CV)
# ============================================================================
cat("\n=== VISUALIZATION 2: Jitter Control ===\n")
p2 <- ggplot(doe_data, aes(x = reorder(configuration, tick_interval_cv, FUN=median),
y = tick_interval_cv,
fill = configuration)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
coord_flip() +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12),
legend.position = "none"
) +
labs(title = "Heartbeat Jitter Control (Coefficient of Variation)",
subtitle = "Lower CV = steadier heartbeat (target < 0.15)",
x = "Configuration",
y = "CV (standard deviation / mean)")
ggsave("02_jitter_control.png", p2, width = 10, height = 6, dpi = 300)
cat("Saved: 02_jitter_control.png\n")
# ============================================================================
# VISUALIZATION 3: Convergence Speed
# ============================================================================
cat("\n=== VISUALIZATION 3: Convergence Speed ===\n")
p3 <- ggplot(doe_data, aes(x = reorder(configuration, decay_slope_convergence_rate, FUN=median),
y = decay_slope_convergence_rate,
fill = configuration)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
coord_flip() +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12),
legend.position = "none"
) +
labs(title = "Decay Slope Convergence Speed",
subtitle = "Higher = faster convergence to optimal slope",
x = "Configuration",
y = "Convergence Rate")
ggsave("03_convergence_speed.png", p3, width = 10, height = 6, dpi = 300)
cat("Saved: 03_convergence_speed.png\n")
# ============================================================================
# VISUALIZATION 4: Load-Response Coupling Strength
# ============================================================================
cat("\n=== VISUALIZATION 4: Load-Response Coupling ===\n")
p4 <- ggplot(doe_data, aes(x = reorder(configuration, load_interval_correlation, FUN=median),
y = load_interval_correlation,
fill = configuration)) +
geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
coord_flip() +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12),
legend.position = "none"
) +
labs(title = "Load-Heartbeat Response Coupling",
subtitle = "Higher correlation = smoother load response (target > 0.7)",
x = "Configuration",
y = "Correlation Coefficient")
ggsave("04_load_coupling.png", p4, width = 10, height = 6, dpi = 300)
cat("Saved: 04_load_coupling.png\n")
# ============================================================================
# VISUALIZATION 5: Multi-Metric Comparison
# ============================================================================
cat("\n=== VISUALIZATION 5: Multi-Metric Heatmap ===\n")
# Normalize metrics to 0-100 scale for heatmap
metrics_normalized <- stability_rank %>%
select(configuration, mean_jitter_cv, mean_convergence_rate,
mean_load_correlation, mean_stability_score) %>%
mutate(
# Invert jitter (lower is better)
jitter_inverted = 100 * (1 - (mean_jitter_cv / max(mean_jitter_cv))),
convergence_norm = 100 * (mean_convergence_rate / max(mean_convergence_rate)),
coupling_norm = 100 * (mean_load_correlation / max(mean_load_correlation)),
stability_norm = mean_stability_score
) %>%
select(configuration, jitter_inverted, convergence_norm, coupling_norm, stability_norm) %>%
pivot_longer(-configuration, names_to = "metric", values_to = "score") %>%
mutate(
metric = factor(metric, levels = c("jitter_inverted", "convergence_norm", "coupling_norm", "stability_norm"),
labels = c("Jitter Control", "Convergence", "Load Coupling", "Overall Stability"))
)
p5 <- ggplot(metrics_normalized, aes(x = configuration, y = metric, fill = score)) +
geom_tile(color = "white", size = 1) +
geom_text(aes(label = sprintf("%.0f", score)), color = "black", size = 4) +
scale_fill_gradient(low = "#d73027", mid = "#fee090", high = "#1a9850",
midpoint = 50, limits = c(0, 100),
name = "Score") +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10),
legend.position = "right"
) +
labs(title = "Heartbeat Stability Metrics Heatmap",
subtitle = "Green = good, Red = poor (all normalized to 0-100)",
x = "Configuration",
y = "Metric")
ggsave("05_metrics_heatmap.png", p5, width = 10, height = 6, dpi = 300)
cat("Saved: 05_metrics_heatmap.png\n")
# ============================================================================
# VISUALIZATION 6: Scatter - Jitter vs Convergence
# ============================================================================
cat("\n=== VISUALIZATION 6: Jitter vs Convergence Trade-off ===\n")
p6 <- ggplot(doe_data, aes(x = tick_interval_cv,
y = decay_slope_convergence_rate,
color = configuration,
shape = configuration)) +
geom_point(alpha = 0.6, size = 3) +
geom_smooth(method = "lm", se = FALSE, alpha = 0.2) +
facet_wrap(~configuration, nrow = 2) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12),
legend.position = "bottom"
) +
labs(title = "Jitter vs Convergence Trade-off",
subtitle = "Upper-left = best (low jitter, fast convergence)",
x = "Heartbeat Jitter (CV)",
y = "Convergence Rate")
ggsave("06_tradeoff_jitter_vs_convergence.png", p6, width = 12, height = 8, dpi = 300)
cat("Saved: 06_tradeoff_jitter_vs_convergence.png\n")
# ============================================================================
# STATISTICAL TESTS
# ============================================================================
cat("\n=== STATISTICAL VALIDATION ===\n")
# Test if top configuration is significantly better than others
top_config <- stability_rank$configuration[1]
top_data <- doe_data %>% filter(configuration == top_config) %>% pull(overall_stability_score)
other_data <- doe_data %>% filter(configuration != top_config) %>% pull(overall_stability_score)
# T-test
t_test <- t.test(top_data, other_data, alternative = "greater")
cat(sprintf("\nT-test: %s vs all others\n", top_config))
cat(sprintf(" Mean stability (%s): %.2f ± %.2f\n",
top_config, mean(top_data, na.rm=TRUE), sd(top_data, na.rm=TRUE)))
cat(sprintf(" Mean stability (others): %.2f ± %.2f\n",
mean(other_data, na.rm=TRUE), sd(other_data, na.rm=TRUE)))
cat(sprintf(" t-statistic: %.3f\n", t_test$statistic))
cat(sprintf(" p-value: %.4f\n", t_test$p.value))
if (t_test$p.value < 0.05) {
cat(sprintf(" ✓ SIGNIFICANT: %s is statistically better (p < 0.05)\n", top_config))
} else {
cat(sprintf(" ✗ NOT SIGNIFICANT: Difference may be due to chance\n"))
}
# Effect size (Cohen's d)
cohens_d <- (mean(top_data, na.rm=TRUE) - mean(other_data, na.rm=TRUE)) /
sqrt((var(top_data, na.rm=TRUE) + var(other_data, na.rm=TRUE)) / 2)
cat(sprintf(" Effect size (Cohen's d): %.3f\n", cohens_d))
# ANOVA: Are all groups different?
anova_result <- aov(overall_stability_score ~ configuration, data = doe_data)
anova_summary <- summary(anova_result)
cat("\nANOVA: Configuration effect on overall stability\n")
print(anova_summary)
# ============================================================================
# GOLDEN CONFIGURATION SELECTION
# ============================================================================
cat("\n\n")
cat("═══════════════════════════════════════════════════════════════════════════════\n")
cat("GOLDEN CONFIGURATION RECOMMENDATION\n")
cat("═══════════════════════════════════════════════════════════════════════════════\n")
golden <- stability_rank %>% slice(1)
cat(sprintf("\n✓ SELECTED: %s\n", as.character(golden$configuration)))
cat(sprintf("\n Stability Metrics:\n"))
cat(sprintf(" • Overall Stability Score: %.2f / 100\n", golden$mean_stability_score))
cat(sprintf(" • Heartbeat Jitter (CV): %.4f (target < 0.15)\n", golden$mean_jitter_cv))
cat(sprintf(" • Outlier Ratio: %.2f%%\n", golden$mean_outlier_ratio * 100))
cat(sprintf(" • Convergence Rate: %.2f (higher = faster)\n", golden$mean_convergence_rate))
cat(sprintf(" • Load Coupling: %.4f (target > 0.7)\n", golden$mean_load_correlation))
cat(sprintf(" • Settling Time: %.1f ticks\n", golden$mean_settling_time))
cat(sprintf(" • Mean Workload Duration: %.2f ms\n", golden$mean_workload_ms))
cat(sprintf("\n Performance vs Baseline:\n"))
baseline <- stability_rank %>% arrange(rank_stability) %>% slice(n())
cat(sprintf(" • vs Worst Config (%s):\n", as.character(baseline$configuration)))
cat(sprintf(" - Stability improvement: %.1f%%\n",
100 * (golden$mean_stability_score - baseline$mean_stability_score) / baseline$mean_stability_score))
cat(sprintf(" - Jitter reduction: %.1f%%\n",
100 * (baseline$mean_jitter_cv - golden$mean_jitter_cv) / baseline$mean_jitter_cv))
cat(sprintf("\n Recommendation for Use:\n"))
cat(sprintf(" • Deploy as physics baseline for MamaForth/PapaForth\n"))
cat(sprintf(" • Use as default configuration for production\n"))
cat(sprintf(" • Consider this configuration stable and reliable\n"))
cat("\n═══════════════════════════════════════════════════════════════════════════════\n\n")
# ============================================================================
# Summary Report
# ============================================================================
cat("\n=== ANALYSIS COMPLETE ===\n")
cat(sprintf("\nGenerated files:\n"))
cat(" • stability_rankings.csv - Detailed rankings table\n")
cat(" • 01_stability_scores.png - Main stability comparison\n")
cat(" • 02_jitter_control.png - Heartbeat jitter by config\n")
cat(" • 03_convergence_speed.png - Convergence speed comparison\n")
cat(" • 04_load_coupling.png - Load-response coupling strength\n")
cat(" • 05_metrics_heatmap.png - All metrics normalized heatmap\n")
cat(" • 06_tradeoff_jitter_vs_convergence.png - Trade-off analysis\n")
cat("\nNext steps:\n")
cat(" 1. Review stability_rankings.csv for complete rankings\n")
cat(" 2. Examine PNG visualizations for patterns\n")
cat(" 3. Use golden configuration as production baseline\n")
cat(" 4. Archive results for future reference\n")