Skip to content

Commit d40f9e3

Browse files
committed
Enh(metrics): Add elo latex export
1 parent a7d9f5a commit d40f9e3

1 file changed

Lines changed: 75 additions & 15 deletions

File tree

codeclash/analysis/metrics/elo2.py

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import argparse
33
import json
44
from collections import defaultdict
5-
from dataclasses import dataclass
65
from pathlib import Path
76
from typing import Literal, TypeAlias, get_args
87

@@ -14,6 +13,7 @@
1413

1514
from codeclash.analysis.metrics.elo import get_scores
1615
from codeclash.analysis.significance import calculate_p_value
16+
from codeclash.analysis.viz.utils import MODEL_TO_DISPLAY_NAME
1717
from codeclash.constants import LOCAL_LOG_DIR, RESULT_TIE
1818
from codeclash.utils.log import get_logger
1919

@@ -699,20 +699,6 @@ def create_validation_plots(self, output_dir: Path, regularization: float = 0.01
699699
plt.close()
700700

701701

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-
716702
class BootStrapRankStability:
717703
def __init__(
718704
self,
@@ -1003,6 +989,79 @@ def print_results(results: dict[str, dict]) -> None:
1003989
print(f" {player:<30s} {strength:12.3f} {elo:8.0f}")
1004990

1005991

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+
10061065
if __name__ == "__main__":
10071066
parser = argparse.ArgumentParser(description="Build win matrix and fit Bradley-Terry model")
10081067
parser.add_argument("-d", "--log_dir", type=Path, default=LOCAL_LOG_DIR)
@@ -1067,6 +1126,7 @@ def print_results(results: dict[str, dict]) -> None:
10671126
plotter = BradleyTerryFitterPlots(results, builder.win_matrix)
10681127
plotter.create_validation_plots(args.output_dir, regularization=args.regularization)
10691128
plotter.create_elo_plots(args.output_dir)
1129+
write_latex_table(results, args.output_dir)
10701130

10711131
if uncertainties_supported:
10721132
for bootstrap_type in ["nonparametric", "parametric"]:

0 commit comments

Comments
 (0)