rogermt commited on
Commit
4f199d6
·
verified ·
1 Parent(s): 43007c3

Move own-solver/neurogolf_utils.py to own-solver/

Browse files
Files changed (1) hide show
  1. own-solver/neurogolf_utils.py +359 -0
own-solver/neurogolf_utils.py ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Module containing utilities for the IJCAI-ECAI 2026 NeuroGolf Challenge."""
16
+
17
+ import itertools
18
+ import json
19
+ import math
20
+ import pathlib
21
+ import traceback
22
+
23
+ import IPython.display
24
+ import matplotlib.pyplot as plt
25
+ import numpy as np
26
+ import onnx
27
+ import onnx_tool
28
+ import onnxruntime
29
+
30
+
31
+ display = IPython.display.display
32
+ FileLink = IPython.display.FileLink
33
+
34
+ _BATCH_SIZE, _CHANNELS, _HEIGHT, _WIDTH = 1, 10, 30, 30
35
+ _NEUROGOLF_DIR = "/kaggle/input/competitions/neurogolf-2026/"
36
+ _COLORS = [
37
+ (0, 0, 0),
38
+ (30, 147, 255),
39
+ (250, 61, 49),
40
+ (78, 204, 48),
41
+ (255, 221, 0),
42
+ (153, 153, 153),
43
+ (229, 59, 163),
44
+ (255, 133, 28),
45
+ (136, 216, 241),
46
+ (147, 17, 49),
47
+ (240, 240, 240),
48
+ (146, 117, 86)
49
+ ]
50
+ _DATA_TYPE = onnx.TensorProto.FLOAT
51
+ _EXCLUDED_OP_TYPES = ["LOOP", "SCAN", "NONZERO", "UNIQUE", "SCRIPT", "FUNCTION"]
52
+ _FILESIZE_LIMIT_IN_BYTES = 1.44 * 1024 * 1024
53
+ _GRID_SHAPE = [_BATCH_SIZE, _CHANNELS, _HEIGHT, _WIDTH]
54
+ _IR_VERSION, _OPSET_IMPORTS = 10, [onnx.helper.make_opsetid("", 10)]
55
+ _TASK_ZERO = {
56
+ "train": [{
57
+ "input": [
58
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
59
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
60
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
61
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
62
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
63
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
64
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
65
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
66
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
67
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
68
+ ],
69
+ "output": [
70
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
71
+ [5, 1, 1, 1, 1, 1, 1, 5, 5, 5],
72
+ [5, 1, 1, 1, 1, 1, 1, 0, 5, 5],
73
+ [5, 1, 1, 1, 1, 1, 1, 0, 5, 5],
74
+ [5, 1, 1, 1, 1, 1, 1, 0, 5, 5],
75
+ [5, 1, 1, 1, 1, 1, 1, 0, 5, 5],
76
+ [5, 1, 1, 1, 1, 1, 1, 0, 5, 5],
77
+ [5, 5, 0, 0, 0, 0, 0, 0, 5, 5],
78
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
79
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
80
+ ],
81
+ }],
82
+ "test": [{
83
+ "input": [
84
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
85
+ [5, 5, 4, 4, 4, 4, 4, 4, 5, 5],
86
+ [5, 5, 4, 4, 4, 4, 4, 4, 5, 5],
87
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
88
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
89
+ [5, 5, 4, 4, 4, 4, 4, 5, 5, 5],
90
+ [5, 5, 4, 5, 5, 5, 4, 5, 5, 5],
91
+ [5, 5, 4, 5, 5, 5, 4, 5, 5, 5],
92
+ [5, 5, 4, 4, 4, 4, 4, 5, 5, 5],
93
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
94
+ ],
95
+ "output": [
96
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
97
+ [5, 5, 4, 4, 4, 4, 4, 4, 5, 5],
98
+ [5, 5, 4, 4, 4, 4, 4, 4, 0, 5],
99
+ [5, 5, 5, 0, 0, 0, 0, 0, 0, 5],
100
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
101
+ [5, 5, 4, 4, 4, 4, 4, 5, 5, 5],
102
+ [5, 5, 4, 0, 0, 0, 4, 0, 5, 5],
103
+ [5, 5, 4, 0, 5, 5, 4, 0, 5, 5],
104
+ [5, 5, 4, 4, 4, 4, 4, 0, 5, 5],
105
+ [5, 5, 5, 0, 0, 0, 0, 0, 5, 5],
106
+ ],
107
+ }],
108
+ "arc-gen": [{
109
+ "input": [
110
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
111
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
112
+ [5, 5, 2, 2, 2, 2, 2, 2, 5, 5],
113
+ [5, 5, 2, 5, 5, 5, 5, 2, 5, 5],
114
+ [5, 5, 2, 5, 5, 5, 5, 2, 5, 5],
115
+ [5, 5, 2, 5, 5, 5, 5, 2, 5, 5],
116
+ [5, 5, 2, 5, 5, 5, 5, 2, 5, 5],
117
+ [5, 5, 2, 2, 2, 2, 2, 2, 5, 5],
118
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
119
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
120
+ ],
121
+ "output": [
122
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
123
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
124
+ [5, 5, 2, 2, 2, 2, 2, 2, 5, 5],
125
+ [5, 5, 2, 0, 0, 0, 0, 2, 0, 5],
126
+ [5, 5, 2, 0, 5, 5, 5, 2, 0, 5],
127
+ [5, 5, 2, 0, 5, 5, 5, 2, 0, 5],
128
+ [5, 5, 2, 0, 5, 5, 5, 2, 0, 5],
129
+ [5, 5, 2, 2, 2, 2, 2, 2, 0, 5],
130
+ [5, 5, 5, 0, 0, 0, 0, 0, 0, 5],
131
+ [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
132
+ ],
133
+ }],
134
+ }
135
+
136
+
137
+ def check_network(filename):
138
+ file_path = pathlib.Path(filename)
139
+ if not file_path.is_file():
140
+ print(f"Error: File {filename} does not exist.")
141
+ return False
142
+ if (filesize := file_path.stat().st_size) > _FILESIZE_LIMIT_IN_BYTES:
143
+ print(f"Error: Filesize {filesize} exceeds {_FILESIZE_LIMIT_IN_BYTES}.")
144
+ return False
145
+ return True
146
+
147
+
148
+ def convert_to_numpy(example):
149
+ benchmark = {}
150
+ example_shape = (1, _CHANNELS, _HEIGHT, _WIDTH)
151
+ for mode in ["input", "output"]:
152
+ benchmark[mode] = np.zeros(example_shape, dtype=np.float32)
153
+ grid = example[mode]
154
+ if max(len(grid), len(grid[0])) > 30: return None
155
+ for r, _ in enumerate(grid):
156
+ for c, color in enumerate(grid[r]):
157
+ benchmark[mode][0][color][r][c] = 1.0
158
+ return benchmark
159
+
160
+
161
+ def convert_from_numpy(benchmark):
162
+ example = []
163
+ _, channels, height, width = benchmark.shape
164
+ for row in range(height):
165
+ cells = []
166
+ for col in range(width):
167
+ colors = [c for c in range(channels) if benchmark[0][c][row][col] == 1]
168
+ cells.append(colors[0] if len(colors) == 1 else (11 if colors else 10))
169
+ while cells and cells[-1] == 10:
170
+ cells.pop(-1)
171
+ example.append(cells)
172
+ while example and not example[-1]:
173
+ example.pop(-1)
174
+ return example
175
+
176
+
177
+ def score_network(m):
178
+ model = onnx_tool.loadmodel(m, {'verbose': False})
179
+ g = model.graph
180
+ g.graph_reorder_nodes()
181
+ g.shape_infer(None)
182
+ g.profile()
183
+ if not g.valid_profile:
184
+ print("Error: Invalid profile.")
185
+ return None, None, None
186
+ for key in g.nodemap.keys():
187
+ if g.nodemap[key].op_type.upper() in _EXCLUDED_OP_TYPES:
188
+ print(f"Error: Op type {g.nodemap[key].op_type} is not permitted.")
189
+ return None, None, None
190
+ if g.nodemap[key].memory < 0:
191
+ print(f"Error: Negative memory value detected.")
192
+ return None, None, None
193
+ return int(sum(g.macs)), int(g.memory), int(g.params)
194
+
195
+
196
+ def load_examples(task_num):
197
+ """Loads relevant data from ARC-AGI and ARC-GEN."""
198
+ if not task_num:
199
+ return _TASK_ZERO
200
+ with open(_NEUROGOLF_DIR + f"task{task_num:03d}.json") as f:
201
+ examples = json.load(f)
202
+ return examples
203
+
204
+
205
+ def run_network(session, benchmark_input):
206
+ result = session.run(["output"], {"input": benchmark_input})
207
+ return (result[0] > 0.0).astype(float)
208
+
209
+
210
+ def show_examples(examples, bgcolor=(255, 255, 255)):
211
+ # Determine the dimensions of the image to be rendered.
212
+ width, height, offset = 0, 0, 1
213
+ for example in examples:
214
+ grid, output = example["input"], example["output"]
215
+ width += len(grid[0]) + 1 + len(output[0]) + 4
216
+ height = max(height, max(len(grid), len(output)) + 4)
217
+ # Determine the contents of the image.
218
+ image = [[bgcolor for _ in range(width)] for _ in range(height)]
219
+ for example in examples:
220
+ grid, output = example["input"], example["output"]
221
+ grid_width, output_width = len(grid[0]), len(output[0])
222
+ for r, row in enumerate(grid):
223
+ for c, cell in enumerate(row):
224
+ image[r + 2][offset + c + 1] = _COLORS[cell]
225
+ offset += grid_width + 1
226
+ for r, row in enumerate(output):
227
+ for c, cell in enumerate(row):
228
+ image[r + 2][offset + c + 1] = _COLORS[cell]
229
+ offset += output_width + 4
230
+ # Draw the image.
231
+ fig = plt.figure(figsize=(10, 5))
232
+ ax = fig.add_axes([0, 0, 1, 1])
233
+ ax.imshow(np.array(image))
234
+ # Draw the horizontal and vertical lines.
235
+ offset = 1
236
+ for example in examples:
237
+ grid, output = example["input"], example["output"]
238
+ grid_width, grid_height = len(grid[0]), len(grid)
239
+ output_width, output_height = len(output[0]), len(output)
240
+ ax.hlines([r + 1.5 for r in range(grid_height+1)],
241
+ xmin=offset+0.5, xmax=offset+grid_width+0.5, color="black")
242
+ ax.vlines([offset + c + 0.5 for c in range(grid_width+1)],
243
+ ymin=1.5, ymax=grid_height+1.5, color="black")
244
+ offset += grid_width + 1
245
+ ax.hlines([r + 1.5 for r in range(output_height+1)],
246
+ xmin=offset+0.5, xmax=offset+output_width+0.5, color="black")
247
+ ax.vlines([offset + c + 0.5 for c in range(output_width+1)],
248
+ ymin=1.5, ymax=output_height+1.5, color="black")
249
+ offset += output_width + 2
250
+ ax.vlines([offset+0.5], ymin=-0.5, ymax=height-0.5, color="black")
251
+ offset += 2
252
+ ax.set_xticks([])
253
+ ax.set_yticks([])
254
+
255
+
256
+ def show_legend():
257
+ image = [[(255, 255, 255) for _ in range(21)] for _ in range(5)]
258
+ for idx, color in enumerate(_COLORS[:10]):
259
+ image[1][2 * idx + 1] = color
260
+ for idx, color in enumerate(_COLORS[10:]):
261
+ for col in range(3):
262
+ image[3][12 * idx + col + 3] = color
263
+ fig = plt.figure(figsize=(10, 5))
264
+ ax = fig.add_axes([0, 0, 1, 1])
265
+ ax.imshow(np.array(image))
266
+ for idx, _ in enumerate(_COLORS[:10]):
267
+ color = "white" if idx in [0, 9] else "black"
268
+ ax.text(2 * idx + 0.9, 1.1, str(idx), color=color)
269
+ ax.text(3.4, 3.1, "no color", color="black")
270
+ ax.text(5.75, 3.1, "<--- special colors to indicate one-hot encoding errors --->", color="black")
271
+ ax.text(14.85, 3.1, "too many colors", color="white")
272
+ ax.set_xticks([])
273
+ ax.set_yticks([])
274
+
275
+
276
+ def single_layer_conv2d_network(weight_fn, kernel_size):
277
+ kernel_offsets = range(-kernel_size // 2 + 1, kernel_size // 2 + 1)
278
+ kernel_shape = [kernel_size, kernel_size]
279
+ w_shape = [_CHANNELS, _CHANNELS, kernel_size, kernel_size]
280
+ pads = [kernel_size // 2] * 4
281
+ weight_cells = itertools.product(range(_CHANNELS), range(_CHANNELS),
282
+ kernel_offsets, kernel_offsets)
283
+ weights = [weight_fn(o, i, (r, c)) for (o, i, r, c) in weight_cells]
284
+
285
+ x = onnx.helper.make_tensor_value_info("input", _DATA_TYPE, _GRID_SHAPE)
286
+ y = onnx.helper.make_tensor_value_info("output", _DATA_TYPE, _GRID_SHAPE)
287
+ w = onnx.helper.make_tensor("W", _DATA_TYPE, w_shape, weights)
288
+ node_def = onnx.helper.make_node("Conv", ["input", "W"], ["output"],
289
+ kernel_shape=kernel_shape, pads=pads)
290
+ graph_def = onnx.helper.make_graph([node_def], "graph", [x], [y], [w])
291
+ model_def = onnx.helper.make_model(graph_def, ir_version=_IR_VERSION,
292
+ opset_imports=_OPSET_IMPORTS)
293
+ return model_def
294
+
295
+
296
+ def verify_network(network, task_num, examples):
297
+ filename = "task{:03d}.onnx".format(task_num)
298
+ onnx.save(network, filename)
299
+ if not check_network(filename): return
300
+ try:
301
+ session = onnxruntime.InferenceSession(filename)
302
+ except onnxruntime.ONNXRuntimeError as e:
303
+ print(f"Error: Unable to load ONNX model: {e}")
304
+ return
305
+ arc_agi_right, arc_agi_wrong, arc_agi_expected = verify_subset(session, examples["train"] + examples["test"])
306
+ arc_gen_right, arc_gen_wrong, arc_gen_expected = verify_subset(session, examples["arc-gen"])
307
+ print(f"Results on ARC-AGI examples: {arc_agi_right} pass, {arc_agi_wrong} fail")
308
+ print(f"Results on ARC-GEN examples: {arc_gen_right} pass, {arc_gen_wrong} fail")
309
+ print()
310
+ macs, memory, params = score_network(filename)
311
+ if macs is None or memory is None or params is None:
312
+ print("Error: Your network performance could not be measured")
313
+ elif arc_agi_wrong + arc_gen_wrong == 0:
314
+ print("Your network IS READY for submission!")
315
+ print()
316
+ print("Performance stats:")
317
+ onnx_tool.model_profile(filename)
318
+ points = max(1.0, 25.0 - math.log(macs + memory + params))
319
+ print()
320
+ print(f"It appears to require {macs} MACs + {memory} bytes + {params} params, yielding {points:.3f} points.")
321
+ print()
322
+ print("Next steps:")
323
+ print(f" * Click the link below to download {filename} onto your local machine.")
324
+ print(" * Create a zip file containing that network along with all others.")
325
+ print(" * Submit that zip file to the Kaggle competition so that it can be officially scored.")
326
+ print()
327
+ display(FileLink(filename))
328
+ else:
329
+ print("Your network IS NOT ready for submission.")
330
+ expected = None
331
+ expected = arc_agi_expected if arc_agi_expected is not None else expected
332
+ expected = arc_gen_expected if arc_gen_expected is not None else expected
333
+ if expected is None: return
334
+ benchmark = convert_to_numpy(expected)
335
+ actual = {}
336
+ actual["input"] = expected["input"]
337
+ actual["output"] = convert_from_numpy(run_network(session, benchmark["input"]))
338
+ print("The expected result is shown in green; your actual result is shown in red.")
339
+ show_examples([expected], bgcolor=(200, 255, 200))
340
+ show_examples([actual], bgcolor=(255, 200, 200))
341
+
342
+
343
+ def verify_subset(session, example_subset):
344
+ right, wrong, expected, error = 0, 0, None, ""
345
+ for example in example_subset:
346
+ benchmark = convert_to_numpy(example)
347
+ if not benchmark: continue
348
+ try:
349
+ user_output = run_network(session, benchmark["input"])
350
+ if np.array_equal(user_output, benchmark["output"]):
351
+ right += 1
352
+ else:
353
+ expected = example
354
+ wrong += 1
355
+ except onnxruntime.ONNXRuntimeError:
356
+ error = traceback.format_exc()
357
+ wrong += 1
358
+ if error: print(f"Error: {error}")
359
+ return right, wrong, expected