%% SCRAP: experiments/02-experiments/factorial-doe/guide %% SOURCE: docs/working/experiments/02-experiments/factorial-doe/guide.md %% STATUS: CURRENT %% FITS: experiments/ch-factorial, cookbook/ch-doe %% EDITORIAL: lifted — prose rewritten to press voice \section{Complete $2^6$ Factorial Design of Experiments} \label{sec:factorial-doe-guide} \subsection{Design Rationale} Incremental tuning — enabling one feedback loop, measuring, then enabling a second — cannot reveal interaction effects. Two loops that each contribute a 3\% improvement in isolation may deliver 10\% when combined, or may cancel each other. The $2^6$ factorial design addresses this by testing \emph{all} 64 binary combinations of the six feedback loops in a single randomised experiment, collecting a unified dataset for post-hoc analysis. \subsection{The Six Feedback Loops} \begin{center} \begin{tabular}{clp{7cm}} \toprule Loop & Makefile flag & Purpose \\ \midrule L1 & \texttt{ENABLE\_LOOP\_1\_HEAT\_TRACKING} & Count word execution frequency \\ L2 & \texttt{ENABLE\_LOOP\_2\_ROLLING\_WINDOW} & Capture execution history (circular buffer) \\ L3 & \texttt{ENABLE\_LOOP\_3\_LINEAR\_DECAY} & Age words by exponential decay over time \\ L4 & \texttt{ENABLE\_LOOP\_4\_PIPELINING\_METRICS} & Track word-to-word transitions \\ L5 & \texttt{ENABLE\_LOOP\_5\_WINDOW\_INFERENCE} & Infer optimal window width via Levene's test \\ L6 & \texttt{ENABLE\_LOOP\_6\_DECAY\_INFERENCE} & Infer decay slope via linear regression \\ \bottomrule \end{tabular} \end{center} \subsection{Configuration Naming} Each configuration is named by a six-digit binary string \texttt{L1\_L2\_L3\_L4\_L5\_L6} where each digit is 0 (off) or 1 (on). The baseline \texttt{000000} represents pure FORTH-79 with no adaptive physics; \texttt{111111} enables all loops. \subsection{Execution} Three run levels are available: \begin{lstlisting}[language=bash] # Minimal validation (~30-45 min, 64 builds x 1 run) ./scripts/run_factorial_doe.sh --runs-per-config 1 TEST_RUN # Standard production (~2-4 hours, 64 x 30 = 1,920 runs) ./scripts/run_factorial_doe.sh --runs-per-config 30 FULL_FACTORIAL # High-precision overnight (~6-12 hours, 64 x 100 = 6,400 runs) ./scripts/run_factorial_doe.sh --runs-per-config 100 HIGH_PRECISION_DOE \end{lstlisting} %% TODO(bob): confirm canonical path for run_factorial_doe.sh in published repo Each configuration requires a clean \texttt{make} rebuild to prevent state contamination. All runs are randomised into a single execution matrix to eliminate thermal ramp, temporal, and ordering biases. \subsection{Output Structure} The experiment produces a CSV file with one row per run and a set of run logs: \begin{lstlisting}[language=bash] / experiment_results.csv # All measurements (1,920+ rows) experiment_summary.txt # Timing, metadata, statistics configuration_manifest.txt # All 64 configs documented test_matrix.txt # Randomised execution order run_logs/ # Per-run logs \end{lstlisting} Each CSV row contains a timestamp, configuration identifier, run number, 35+ performance metrics from the test harness, and the six loop-enable flags. This schema allows every metric to be correlated with every loop combination. \subsection{Analysis} \paragraph{Main effects.} The average change in a response metric when loop $k$ transitions from off to on, marginalised over all other loops: \begin{equation} \hat{\beta}_k = \bar{y}_{(L_k=1)} - \bar{y}_{(L_k=0)} \end{equation} \paragraph{Two-way interactions.} The interaction effect for loops $a$ and $b$: \begin{equation} \hat{\beta}_{ab} = \bar{y}_{(1,1)} - \bar{y}_{(1,0)} - \bar{y}_{(0,1)} + \bar{y}_{(0,0)} \end{equation} A positive $\hat{\beta}_{ab}$ indicates synergy; a negative value indicates suppression. \begin{lstlisting}[language=Python] import pandas as pd df = pd.read_csv("experiment_results.csv") factor_cols = ['enable_loop_1_heat_tracking', 'enable_loop_2_rolling_window', 'enable_loop_3_linear_decay', 'enable_loop_4_pipelining_metrics', 'enable_loop_5_window_inference', 'enable_loop_6_decay_inference'] metric = 'vm_workload_duration_ns_q48' main_effects = { col: df[df[col]==1][metric].mean() - df[df[col]==0][metric].mean() for col in factor_cols } \end{lstlisting} %% TODO(bob): verify column names against actual CSV header from a completed run \subsection{Key Design Principles} \begin{itemize} \item \textbf{Rebuild every configuration.} Ensures isolated, uncontaminated measurements for each loop combination. \item \textbf{Randomise all runs.} The 1{,}920 runs are interleaved in a single randomised matrix, not grouped by configuration. \item \textbf{Collect once, analyse separately.} Data collection and statistical analysis are strictly separated to prevent confirmation bias. \item \textbf{Full factorial, not fractional.} All $2^6 = 64$ combinations are tested; fractional designs would alias higher-order interactions. \end{itemize}