Repository navigation
Expand file tree
/
Copy pathrun.py
More file actions
250 lines (215 loc) · 7.88 KB
/
Copy pathrun.py
File metadata and controls
250 lines (215 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
import asyncio
import json
import os
import time
import typing as T
from pathlib import Path
from pydantic import BaseModel, TypeAdapter
from src import logfire
from src.data import build_challenges
from src.logic import (
CacheData,
random_string,
solve_challenge,
solve_challenge_background,
)
from src.models import GRID, Challenge, RootAttemptConfig
from src.trees import deepseek, experiments, o3
class ChallengeSolution(BaseModel):
attempt_1: GRID
attempt_2: GRID
async def solve_and_write(
solutions_d: dict[str, list[ChallengeSolution]],
challenge: Challenge,
tree: list[RootAttemptConfig],
solutions_dir: Path,
solutions_path: str,
) -> None:
start = time.time()
print(f"[{challenge.id}] starting challenge...")
if server_url := os.environ.get("SERVER_URL"):
url = f"{server_url}/solve_challenge"
else:
url = None
first_solutions, second_solutions = await solve_challenge(
challenge=challenge,
tree=tree,
url=url,
)
solutions_d[challenge.id] = []
for i in range(len(first_solutions)):
solutions_d[challenge.id].append(
ChallengeSolution(
attempt_1=first_solutions[i],
attempt_2=second_solutions[i],
)
)
# just write after each challenge in case program crashes
logfire.debug(
f"[{challenge.id}] solution",
challenge_id=challenge.id,
challenge=challenge,
solution_d=solutions_d[challenge.id],
)
open(solutions_dir / f"{challenge.id}.json", "w").write(
TypeAdapter(list[ChallengeSolution])
.dump_json(solutions_d[challenge.id])
.decode("utf-8")
)
# just write to be safe
open(solutions_path, "w").write(
TypeAdapter(dict[str, list[ChallengeSolution]])
.dump_json(solutions_d)
.decode("utf-8")
)
took_secs = time.time() - start
logfire.debug(f"[{challenge.id}] took {took_secs:.2f} secs to solve and write")
async def process_challenges_with_limit(
challenges: list[Challenge],
solutions_d: dict[str, list[ChallengeSolution]],
tree: list[RootAttemptConfig],
solutions_dir: Path,
solutions_path: str,
max_concurrent: int,
) -> list[T.Any]:
# Create a semaphore to limit concurrent tasks
semaphore = asyncio.Semaphore(max_concurrent)
async def bounded_solve_and_write(challenge: Challenge) -> None:
"""
Wrapper for solve_and_write that respects the semaphore limit
"""
async with semaphore:
try:
return await solve_and_write(
solutions_d=solutions_d,
challenge=challenge,
tree=tree,
solutions_dir=solutions_dir,
solutions_path=solutions_path,
)
except Exception as e:
import traceback
print(traceback.format_exc())
logfire.debug(f"Error processing challenge: {e}")
raise
# Create tasks for all challenges
tasks = [bounded_solve_and_write(challenge) for challenge in challenges]
# Run all tasks and gather results
results = await asyncio.gather(*tasks, return_exceptions=True)
# Check for any exceptions in results
errors = [r for r in results if isinstance(r, Exception)]
if errors:
message = f"Encountered {len(errors)} errors during processing"
logfire.debug(message)
print(message)
return results
async def run_from_json(
*,
challenges_path: str,
solutions_path: str,
temp_solutions_dir_path: str,
tree: list[RootAttemptConfig],
limit: int | None,
offset: int | None = None,
use_smallest_first: bool | None = None,
only_run_ids: set[str] = None,
max_concurrent: int,
truth_solutions_path: str | None = None,
) -> None:
start = time.time()
challenges = build_challenges(
challenges_path=Path(challenges_path),
solutions_path=Path(truth_solutions_path) if truth_solutions_path else None,
)
if only_run_ids:
challenges = {k: challenges[k] for k in only_run_ids}
if offset:
challenges = {k: challenges[k] for k in list(challenges)[offset:]}
if limit:
# Sort by length of str(Challenge) where Challenge is the value
if use_smallest_first:
# Sort dict items by length of str(value)
sorted_items = sorted(challenges.items(), key=lambda x: len(str(x[1])))
challenges = dict(sorted_items)
# Only include the first 'limit' challenges
challenges = {k: challenges[k] for k in list(challenges.keys())[:limit]}
# Print lengths to verify
for k, v in challenges.items():
print(f"Key: {k}, Length: {len(str(v))}")
solutions_d: dict[str, list[ChallengeSolution]] = {}
# run all challenges in parallel to start
solutions_dir = Path(temp_solutions_dir_path)
solutions_dir.mkdir(exist_ok=True)
await process_challenges_with_limit(
challenges=list(challenges.values()),
solutions_d=solutions_d,
tree=tree,
solutions_dir=solutions_dir,
solutions_path=solutions_path,
max_concurrent=max_concurrent,
)
# iterate through solutions dir and load in the solutions? or just use solutions_d
open(solutions_path, "w").write(
TypeAdapter(dict[str, list[ChallengeSolution]])
.dump_json(solutions_d)
.decode("utf-8")
)
message = f"FINAL: took {(time.time() - start):.2f} secs to run {len(challenges)} challenges"
logfire.debug(message)
print(message)
async def run() -> None:
training_or_eval = "evaluation"
v1or2 = "2025"
challenges_path = f"arc-prize-{v1or2}/arc-agi_{training_or_eval}_challenges.json"
truth_solutions_path = (
f"arc-prize-{v1or2}/arc-agi_{training_or_eval}_solutions.json"
)
RUN = f"{training_or_eval}-{v1or2}-232390_MANY"
attempts_solutions_path = f"test_data/{training_or_eval}_solutions{RUN}.json"
await run_from_json(
challenges_path=challenges_path,
solutions_path=attempts_solutions_path,
truth_solutions_path=truth_solutions_path,
temp_solutions_dir_path=f"test_data/tmp_solutions{RUN}",
# tree=experiments.sonnet_writeup_deep,
# tree=experiments.sonnet_writeup_shallow,
# tree=experiments.sonnet_writeup_med,
# tree=experiments.sonnet_pooling_example,
# tree=experiments.deepseek_pooling_example,
# tree=deepseek.prod_kaggle_tree,
# tree=deepseek.small_baseten_tree,
# tree=o3.small_tree,
tree=o3.small_tree,
# limit=10,
# offset=50,
limit=5,
use_smallest_first=True,
offset=0,
max_concurrent=10,
# only_run_ids={"045e512c"},
)
evaluate_solutions(
attempts_solutions_path=attempts_solutions_path,
truth_solutions_path=truth_solutions_path,
)
def evaluate_solutions(attempts_solutions_path: str, truth_solutions_path: str) -> None:
truth: dict[str, list[GRID]] = json.loads(open(truth_solutions_path).read())
attempts: dict[str, list[ChallengeSolution]] = TypeAdapter(
dict[str, list[ChallengeSolution]]
).validate_json(open(attempts_solutions_path).read())
total_count = 0
correct_count = 0
for challenge_id, attempt_list in attempts.items():
truth_grids: list[GRID] = truth[challenge_id]
for i, truth_grid in enumerate(truth_grids):
total_count = total_count + 1
attempt_grids = attempt_list[i]
if attempt_grids.attempt_1 == truth_grid:
correct_count = correct_count + 1
elif attempt_grids.attempt_2 == truth_grid:
correct_count = correct_count + 1
print("total count", total_count, "correct count", correct_count)
async def main() -> None:
await run()
if __name__ == "__main__":
asyncio.run(main())