Skip to content

Commit 611a16e

Browse files
committed
Merge branch 'main' of github.com:emagedoc/CodeClash
2 parents 28d2455 + 4fb7e3a commit 611a16e

50 files changed

Lines changed: 932 additions & 216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codeclash/analysis/metrics/elo.py

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
22
import argparse
33
import json
4+
from collections import Counter
45
from dataclasses import dataclass
56
from pathlib import Path
67

@@ -125,11 +126,12 @@ class SkipTournamentException(Exception):
125126

126127

127128
class ELOCalculator:
128-
def __init__(self, *, k_factor: float, starting_elo: float, weighting_function: str, alpha: float):
129+
def __init__(self, *, k_factor: float, starting_elo: float, weighting_function: str, alpha: float, unit: str):
129130
self._k_factor = k_factor
130131
self._starting_elo = starting_elo
131132
self._weighting_function = weighting_function
132133
self._alpha = alpha
134+
self._unit = unit
133135
self._player_profiles = {}
134136

135137
@property
@@ -161,6 +163,49 @@ def _analyze_round(self, stats: dict, player2profile: dict, *, total_rounds: int
161163
weighted_k_factor = self._k_factor * round_weight
162164
update_profiles(prof_and_score, weighted_k_factor)
163165

166+
def _score_per_round(self, metadata: dict, player2profile: dict, total_rounds: int) -> None:
167+
for idx, stats in metadata["round_stats"].items():
168+
idx = int(idx)
169+
if idx == 0:
170+
# Skip initial round
171+
continue
172+
assert idx == stats["round_num"], (idx, stats["round_num"])
173+
self._analyze_round(stats, player2profile, total_rounds=total_rounds)
174+
175+
def _score_per_tournament(self, metadata: dict, player2profile: dict, total_rounds: int) -> None:
176+
# Update score = number of rounds won per tournament (ties count as 0.5 each)
177+
round_stats = metadata["round_stats"]
178+
winners = []
179+
ties = 0
180+
for k, v in round_stats.items():
181+
try:
182+
if int(k) == 0:
183+
continue
184+
except Exception:
185+
pass
186+
w = v.get("winner")
187+
if w == RESULT_TIE:
188+
ties += 1
189+
elif w is not None:
190+
winners.append(w)
191+
192+
win_counts = Counter(winners)
193+
194+
# Ensure both players are considered (even if they have zero wins)
195+
players = list(player2profile.keys())
196+
for p in players:
197+
player2profile[p].rounds_played += 1 # Each player played all rounds
198+
wins_per_player = {p: win_counts.get(p, 0) + 0.5 * ties for p in players}
199+
200+
wins_total = sum(wins_per_player.values())
201+
if wins_total == 0:
202+
raise SkipTournamentException
203+
204+
prof_and_score = [(player2profile[p], wins_per_player[p] / wins_total) for p in players]
205+
206+
weighted_k_factor = self._k_factor * (1 / total_rounds) if total_rounds > 0 else self._k_factor
207+
update_profiles(prof_and_score, weighted_k_factor)
208+
164209
def _analyze_tournament(self, tournament_log_folder: Path) -> None:
165210
"""Update all profiles based on the results of one tournament"""
166211
with open(tournament_log_folder / "metadata.json") as f:
@@ -190,15 +235,10 @@ def _analyze_tournament(self, tournament_log_folder: Path) -> None:
190235
# Determine total rounds for weighting calculation
191236
total_rounds = len([k for k in metadata["round_stats"].keys() if k != "0"])
192237

193-
for idx, stats in metadata["round_stats"].items():
194-
idx = int(idx)
195-
if idx == 0:
196-
# Skip initial round
197-
continue
198-
199-
assert idx == stats["round_num"], (idx, stats["round_num"])
200-
201-
self._analyze_round(stats, player2profile, total_rounds=total_rounds)
238+
if self._unit == "round":
239+
self._score_per_round(metadata, player2profile, total_rounds=total_rounds)
240+
elif self._unit == "tournament":
241+
self._score_per_tournament(metadata, player2profile, total_rounds=total_rounds)
202242

203243
def analyze(self, log_dir: Path) -> None:
204244
print(f"Calculating weighted ELO ratings from logs in {log_dir} ...")
@@ -320,12 +360,20 @@ def print_results(self) -> None:
320360
default=2.0,
321361
help="Alpha parameter for exponential weighting (Default: 2.0, ignored for linear weighting)",
322362
)
363+
parser.add_argument(
364+
"-u",
365+
"--unit",
366+
choices=["round", "tournament"],
367+
default="round",
368+
help="Calculate Elo ratings on a per-round or per-tournament basis (Default: round)",
369+
)
323370
args = parser.parse_args()
324371
calculator = ELOCalculator(
325372
k_factor=args.k_factor,
326373
starting_elo=args.starting_elo,
327374
weighting_function=args.weighting_function,
328375
alpha=args.alpha,
376+
unit=args.unit,
329377
)
330378
calculator.analyze(args.log_dir)
331379
calculator.print_results()

codeclash/analysis/viz/cdf_files_edited_per_round.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ def main():
5353

5454
LIM = 20
5555
plt.xlim(0, LIM) # Limit x-axis to 40 for better visibility
56-
plt.xticks(range(0, LIM + 1, 5), fontsize=12, fontproperties=FONT_REG)
57-
plt.yticks([i / 10 for i in range(11)], [f"{i * 10}%" for i in range(11)], fontsize=12, fontproperties=FONT_REG)
56+
plt.xticks(range(0, LIM + 1, 5), fontsize=18, fontproperties=FONT_REG)
57+
plt.yticks([i / 10 for i in range(11)], [f"{i * 10}%" for i in range(11)], fontsize=18, fontproperties=FONT_REG)
5858
plt.xlabel("Files Edited per Round", fontproperties=FONT_BOLD, fontsize=18)
5959
# plt.ylabel("Cumulative Probability", fontproperties=FONT_BOLD, fontsize=18)
6060
# plt.title("CDF of Files Edited per Round by Model")
61-
FONT_BOLD.set_size(14)
61+
FONT_BOLD.set_size(20)
6262
plt.legend(prop=FONT_BOLD)
6363
plt.grid(True)
6464
plt.savefig(OUTPUT_FILE, dpi=300, bbox_inches="tight")

codeclash/analysis/viz/cdf_steps_per_round.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from matplotlib import pyplot as plt
55
from tqdm.auto import tqdm
66

7-
from codeclash.analysis.viz.utils import ASSETS_DIR, FONT_BOLD, FONT_REG, MODEL_TO_COLOR, MODEL_TO_DISPLAY_NAME
7+
from codeclash.analysis.viz.utils import ASSETS_DIR, FONT_BOLD, MODEL_TO_COLOR, MODEL_TO_DISPLAY_NAME
88
from codeclash.constants import LOCAL_LOG_DIR
99

1010
OUTPUT_FILE = ASSETS_DIR / "cdf_steps_per_round.png"
@@ -54,12 +54,12 @@ def main():
5454
yvals = [i / len(sorted_steps) for i in range(len(sorted_steps))]
5555
plt.step(sorted_steps, yvals, label=MODEL_TO_DISPLAY_NAME[model], where="post", color=MODEL_TO_COLOR[model])
5656

57-
plt.xticks(range(0, 31, 5), fontsize=12, fontproperties=FONT_REG)
58-
plt.yticks([i / 10 for i in range(11)], [f"{i * 10}%" for i in range(11)], fontsize=12, fontproperties=FONT_REG)
57+
plt.xticks(range(0, 31, 5), fontsize=18, fontproperties=FONT_BOLD)
58+
plt.yticks([i / 10 for i in range(11)], [f"{i * 10}%" for i in range(11)], fontsize=18, fontproperties=FONT_BOLD)
5959
plt.xlabel("Steps per Round", fontproperties=FONT_BOLD, fontsize=18)
6060
# plt.ylabel("Cumulative Probability")
6161
# plt.title("CDF of Steps per Round by Model")
62-
FONT_BOLD.set_size(14)
62+
FONT_BOLD.set_size(18)
6363
plt.legend(prop=FONT_BOLD)
6464
plt.grid(True)
6565
plt.savefig(OUTPUT_FILE, dpi=300, bbox_inches="tight")

codeclash/analysis/viz/cdf_thought_length_per_round.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@ def main():
5454
model_to_steps = json.load(f)
5555

5656
# Plot CDF
57-
plt.figure(figsize=(10, 6))
57+
plt.figure(figsize=(8, 8))
5858
for model, thought_length in model_to_steps.items():
5959
sorted_steps = sorted(thought_length)
6060
yvals = [i / len(sorted_steps) for i in range(len(sorted_steps))]
6161
plt.step(sorted_steps, yvals, label=MODEL_TO_DISPLAY_NAME[model], where="post", color=MODEL_TO_COLOR[model])
6262

6363
LIM = 200
6464
plt.xlim(0, LIM)
65-
plt.xticks(range(0, LIM + 1, 20), fontsize=12, fontproperties=FONT_REG)
66-
plt.yticks([i / 10 for i in range(11)], [f"{i * 10}%" for i in range(11)], fontsize=12, fontproperties=FONT_REG)
65+
plt.xticks(range(0, LIM + 1, 20), fontsize=18, fontproperties=FONT_REG)
66+
plt.yticks([i / 10 for i in range(11)], [f"{i * 10}%" for i in range(11)], fontsize=18, fontproperties=FONT_REG)
6767
plt.xlabel("Thought length (in words) per action", fontproperties=FONT_BOLD, fontsize=18)
6868
# plt.ylabel("Cumulative Probability")
6969
# plt.title("CDF of Thought Length (in Words) per Round by Model")

codeclash/analysis/viz/line_chart_per_round_changes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,11 @@ def plot_averages(model_to_round_lines, out_png: Path):
140140
plt.plot(x, avgs, marker="o", label=display, linewidth=1.5, markersize=6, color=color)
141141
ymax = max(ymax, max(avgs) if avgs else 0)
142142

143-
plt.xlabel("Round", fontsize=14, fontproperties=FONT_BOLD)
144-
plt.ylabel("Average Lines Changed", fontsize=14, fontproperties=FONT_BOLD)
145-
plt.xticks(x, fontproperties=FONT_BOLD, fontsize=12)
146-
FONT_BOLD.set_size(12)
143+
plt.xlabel("Round", fontsize=18, fontproperties=FONT_BOLD)
144+
plt.ylabel("Average Lines Changed", fontsize=18, fontproperties=FONT_BOLD)
145+
plt.xticks(x, fontproperties=FONT_BOLD, fontsize=18)
146+
plt.yticks(fontproperties=FONT_BOLD, fontsize=18)
147+
FONT_BOLD.set_size(18)
147148
# plt.legend(bbox_to_anchor=(1, 1), loc="upper left", prop=FONT_BOLD)
148149
plt.grid(True, alpha=0.3)
149150
plt.ylim(0, max(10, ymax + 5))

0 commit comments

Comments
 (0)