#!/usr/bin/env python3 """Generate LaTeX table fragments for the paper appendices from row-level CSVs. Each fragment contains only a tabular environment; captions and labels live in the paper source. Uncertainty follows the paper's nesting convention: feedback draws are averaged within each forward initialization first, and standard errors are computed across forward-initialization means. """ from __future__ import annotations import argparse import csv import math import statistics from collections import defaultdict from pathlib import Path def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--depth-dir", type=Path, default=Path("outputs/aaai_depth_experiments")) parser.add_argument( "--cnn-dir", type=Path, default=Path("outputs/cnn_initialization_validation") ) parser.add_argument( "--lr-dir", type=Path, default=Path("outputs/learning_rate_compensation") ) parser.add_argument("--outdir", type=Path, default=Path("outputs/tables")) return parser.parse_args() def read_rows(path: Path) -> list[dict[str, str]]: if not path.exists(): raise FileNotFoundError(path) with path.open() as handle: return list(csv.DictReader(handle)) def sem(values: list[float]) -> float: if len(values) < 2: return float("nan") return statistics.stdev(values) / math.sqrt(len(values)) def write_fragment(path: Path, lines: list[str]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text("\n".join(lines) + "\n") print(f"wrote {path}") def initialization_table(depth_dir: Path, outdir: Path) -> None: rows = read_rows(depth_dir / "initialization_rows.csv") cells: dict[tuple[int, int], dict[str, dict[str, float]]] = defaultdict(dict) for row in rows: key = (int(row["depth"]), int(row["init_seed"])) cells[key][row["rule"]] = { "pred": float(row["predicted_deficit"]), "mean": float(row["empirical_deficit"]), "se": float(row["empirical_stderr"]), } lines = [ r"\begin{tabular}{@{}rrccc@{}}", r"\toprule", r"Depth & Init & Prediction & FA measured (SE) & DFA measured (SE) \\", r"\midrule", ] for (depth, init_seed), by_rule in sorted(cells.items()): fa, dfa = by_rule["FA"], by_rule["DFA"] assert abs(fa["pred"] - dfa["pred"]) < 1e-12, (depth, init_seed) lines.append( f"{depth} & {init_seed - 10_000} & {fa['pred']:.6f} & " f"{fa['mean']:.6f} ({fa['se']:.2g}) & " f"{dfa['mean']:.6f} ({dfa['se']:.2g}) \\\\" ) lines += [r"\bottomrule", r"\end{tabular}"] write_fragment(outdir / "initialization_rows.tex", lines) def finite_time_table(depth_dir: Path, outdir: Path) -> None: rows = read_rows(depth_dir / "finite_time_rows.csv") cells: dict[tuple[int, int], dict[int, dict[str, list[float]]]] = defaultdict( lambda: defaultdict(lambda: defaultdict(list)) ) fields = ["empirical_gap", "fixed_gap", "velocity_gap", "retangent_gap"] for row in rows: cell = cells[(int(row["depth"]), int(row["width"]))][int(row["init_seed"])] for field in fields: cell[field].append(float(row[field])) lines = [ r"\begin{tabular}{@{}rrccccc@{}}", r"\toprule", r"Depth & Width & Measured & SE & Frozen & Linear & Relinearized \\", r"\midrule", ] for (depth, width), by_init in sorted(cells.items()): init_means = { field: [statistics.mean(values[field]) for _, values in sorted(by_init.items())] for field in fields } measured = statistics.mean(init_means["empirical_gap"]) lines.append( f"{depth} & {width} & {measured:.6f} & {sem(init_means['empirical_gap']):.2g} & " f"{statistics.mean(init_means['fixed_gap']):.6f} & " f"{statistics.mean(init_means['velocity_gap']):.6f} & " f"{statistics.mean(init_means['retangent_gap']):.6f} \\\\" ) lines += [r"\bottomrule", r"\end{tabular}"] write_fragment(outdir / "finite_time_cells.tex", lines) def cnn_table(cnn_dir: Path, outdir: Path) -> None: rows = read_rows(cnn_dir / "cnn_initialization_rows.csv") lines = [ r"\begin{tabular}{@{}lrccc@{}}", r"\toprule", r"Rule & Init & Prediction & Measured & SE \\", r"\midrule", ] for row in sorted(rows, key=lambda r: (r["rule"], int(r["init_seed"]))): lines.append( f"{row['rule']} & {int(row['init_seed']) - 10_000} & " f"{float(row['predicted_deficit']):.6f} & " f"{float(row['empirical_deficit']):.6f} & " f"{float(row['empirical_stderr']):.2g} \\\\" ) lines += [r"\bottomrule", r"\end{tabular}"] write_fragment(outdir / "cnn_initialization_rows.tex", lines) def learning_rate_table(lr_dir: Path, outdir: Path) -> None: rows = read_rows(lr_dir / "evaluation_rows.csv") regime_order = {"same_lr": 0, "initial_compensation": 1, "independently_tuned": 2} regime_label = { "same_lr": "Same rate", "initial_compensation": "Initial compensation", "independently_tuned": "Independently selected", } cells: dict[tuple[int, str], dict[int, list[float]]] = defaultdict(lambda: defaultdict(list)) fa_rates: dict[tuple[int, str], list[float]] = defaultdict(list) bp_rates: dict[tuple[int, str], set[float]] = defaultdict(set) for row in rows: assert row["stable"] == "True", row key = (int(row["depth"]), row["regime"]) cells[key][int(row["init_seed"])].append(float(row["gap_to_bp"])) fa_rates[key].append(float(row["fa_lr"])) bp_rates[key].add(float(row["bp_lr"])) lines = [ r"\begin{tabular}{@{}rlcccc@{}}", r"\toprule", r"Depth & Regime & BP rate & Mean FA rate & Gap & SE \\", r"\midrule", ] for (depth, regime), by_init in sorted( cells.items(), key=lambda item: (item[0][0], regime_order[item[0][1]]) ): init_means = [statistics.mean(values) for _, values in sorted(by_init.items())] (bp_rate,) = bp_rates[(depth, regime)] lines.append( f"{depth} & {regime_label[regime]} & {bp_rate:g} & " f"{statistics.mean(fa_rates[(depth, regime)]):.4g} & " f"{statistics.mean(init_means):.5f} & {sem(init_means):.2g} \\\\" ) lines += [r"\bottomrule", r"\end{tabular}"] write_fragment(outdir / "learning_rate_regimes.tex", lines) def main() -> None: args = parse_args() initialization_table(args.depth_dir, args.outdir) finite_time_table(args.depth_dir, args.outdir) cnn_table(args.cnn_dir, args.outdir) learning_rate_table(args.lr_dir, args.outdir) if __name__ == "__main__": main()