|
2 | 2 | import argparse |
3 | 3 | import json |
4 | 4 | from collections import defaultdict |
5 | | -from dataclasses import dataclass |
6 | 5 | from pathlib import Path |
7 | 6 | from typing import Literal, TypeAlias, get_args |
8 | 7 |
|
|
14 | 13 |
|
15 | 14 | from codeclash.analysis.metrics.elo import get_scores |
16 | 15 | from codeclash.analysis.significance import calculate_p_value |
| 16 | +from codeclash.analysis.viz.utils import MODEL_TO_DISPLAY_NAME |
17 | 17 | from codeclash.constants import LOCAL_LOG_DIR, RESULT_TIE |
18 | 18 | from codeclash.utils.log import get_logger |
19 | 19 |
|
@@ -699,20 +699,6 @@ def create_validation_plots(self, output_dir: Path, regularization: float = 0.01 |
699 | 699 | plt.close() |
700 | 700 |
|
701 | 701 |
|
702 | | -@dataclass |
703 | | -class BootStrapRankStabilityConfig: |
704 | | - n_bootstrap: int = 200 |
705 | | - game: str = "ALL" |
706 | | - regularization: float = 0.01 |
707 | | - topks: list[int] | None = None |
708 | | - rng_seed: int | None = None |
709 | | - bootstrap_type: Literal["nonparametric", "parametric"] = "nonparametric" |
710 | | - |
711 | | - def __post_init__(self) -> None: |
712 | | - if self.topks is None: |
713 | | - self.topks = [1, 3, 5] |
714 | | - |
715 | | - |
716 | 702 | class BootStrapRankStability: |
717 | 703 | def __init__( |
718 | 704 | self, |
@@ -1003,6 +989,79 @@ def print_results(results: dict[str, dict]) -> None: |
1003 | 989 | print(f" {player:<30s} {strength:12.3f} {elo:8.0f}") |
1004 | 990 |
|
1005 | 991 |
|
| 992 | +def write_latex_table(results: dict[str, dict], output_dir: Path) -> None: |
| 993 | + """Write LaTeX table with ELO results to file. |
| 994 | +
|
| 995 | + Args: |
| 996 | + results: Dictionary mapping game name to fit results |
| 997 | + output_dir: Directory to save the LaTeX file |
| 998 | + """ |
| 999 | + output_dir.mkdir(parents=True, exist_ok=True) |
| 1000 | + output_file = output_dir / "main_results.tex" |
| 1001 | + |
| 1002 | + single_arena_games = ["BattleSnake", "CoreWar", "Halite", "HuskyBench", "RoboCode", "RobotRumble"] |
| 1003 | + games_in_table = [g for g in single_arena_games if g in results] |
| 1004 | + |
| 1005 | + if "ALL" not in results: |
| 1006 | + logger.warning("No 'ALL' game found in results, skipping LaTeX table generation") |
| 1007 | + return |
| 1008 | + |
| 1009 | + all_result = results["ALL"] |
| 1010 | + all_players = all_result["players"] |
| 1011 | + all_strengths = all_result["strengths"] |
| 1012 | + all_elos = {p: BradleyTerryFitter.bt_to_elo(s) for p, s in zip(all_players, all_strengths)} |
| 1013 | + sorted_players = sorted(all_elos.items(), key=lambda x: x[1], reverse=True) |
| 1014 | + |
| 1015 | + lines = [] |
| 1016 | + lines.append("% LaTeX commands for formatting ELO results") |
| 1017 | + lines.append(r"\newcommand{\eloSingleArenaResult}[1]{\textcolor{gray}{\small #1}}") |
| 1018 | + lines.append(r"\newcommand{\eloMainResult}[1]{#1}") |
| 1019 | + lines.append("") |
| 1020 | + lines.append(r"\begin{table}[t]") |
| 1021 | + lines.append(r"\centering") |
| 1022 | + lines.append(r"\begin{tabular}{l|" + "c" * len(games_in_table) + "|c}") |
| 1023 | + lines.append(r"\toprule") |
| 1024 | + |
| 1025 | + display_names = [g.replace("HuskyBench", "Poker") for g in games_in_table] |
| 1026 | + header_parts = [""] + [rf"\scriptsize{{{g}}}" for g in display_names] + [r"\textbf{All}"] |
| 1027 | + lines.append(" & ".join(header_parts) + r" \\") |
| 1028 | + lines.append(r"\midrule") |
| 1029 | + |
| 1030 | + def format_elo(elo: float) -> str: |
| 1031 | + """Format ELO number with phantom padding for numbers < 1000""" |
| 1032 | + elo_int = int(elo) |
| 1033 | + if elo_int < 1000: |
| 1034 | + return rf"\phantom{{0}}{elo_int}" |
| 1035 | + return str(elo_int) |
| 1036 | + |
| 1037 | + for player, all_elo in sorted_players: |
| 1038 | + display_name = MODEL_TO_DISPLAY_NAME.get(player, player) |
| 1039 | + row_parts = [display_name.replace("_", r"\_")] |
| 1040 | + |
| 1041 | + for game_name in games_in_table: |
| 1042 | + if game_name in results: |
| 1043 | + game_result = results[game_name] |
| 1044 | + if player in game_result["players"]: |
| 1045 | + idx = game_result["players"].index(player) |
| 1046 | + strength = game_result["strengths"][idx] |
| 1047 | + elo = BradleyTerryFitter.bt_to_elo(strength) |
| 1048 | + row_parts.append(rf"\eloSingleArenaResult{{{format_elo(elo)}}}") |
| 1049 | + else: |
| 1050 | + row_parts.append("--") |
| 1051 | + else: |
| 1052 | + row_parts.append("--") |
| 1053 | + |
| 1054 | + row_parts.append(rf"\eloMainResult{{{format_elo(all_elo)}}}") |
| 1055 | + lines.append(" & ".join(row_parts) + r" \\") |
| 1056 | + |
| 1057 | + lines.append(r"\bottomrule") |
| 1058 | + lines.append(r"\end{tabular}") |
| 1059 | + lines.append(r"\end{table}") |
| 1060 | + |
| 1061 | + output_file.write_text("\n".join(lines) + "\n") |
| 1062 | + logger.info(f"Saved LaTeX table: {output_file}") |
| 1063 | + |
| 1064 | + |
1006 | 1065 | if __name__ == "__main__": |
1007 | 1066 | parser = argparse.ArgumentParser(description="Build win matrix and fit Bradley-Terry model") |
1008 | 1067 | parser.add_argument("-d", "--log_dir", type=Path, default=LOCAL_LOG_DIR) |
@@ -1067,6 +1126,7 @@ def print_results(results: dict[str, dict]) -> None: |
1067 | 1126 | plotter = BradleyTerryFitterPlots(results, builder.win_matrix) |
1068 | 1127 | plotter.create_validation_plots(args.output_dir, regularization=args.regularization) |
1069 | 1128 | plotter.create_elo_plots(args.output_dir) |
| 1129 | + write_latex_table(results, args.output_dir) |
1070 | 1130 |
|
1071 | 1131 | if uncertainties_supported: |
1072 | 1132 | for bootstrap_type in ["nonparametric", "parametric"]: |
|
0 commit comments