384 lines
16 KiB
R
Executable File
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") |