rogermt commited on
Commit
7d3ba3f
·
verified ·
1 Parent(s): 92fcf00

Add task277 ONNX build script (266/266 PASS, score 14.85 vs base 13.30, gain +1.55)

Browse files
Files changed (1) hide show
  1. medal-solvers/build_task277_onnx.py +195 -0
medal-solvers/build_task277_onnx.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build ONNX model for Task 277 — Smallest CC recoloring. V2 compact.
2
+
3
+ Key facts: Always exactly 3 CCs, gap≥2, no ties.
4
+
5
+ Approach:
6
+ 1. MaxPool label propagation → 3 unique labels
7
+ 2. Find 3 labels: max, second max, third max
8
+ 3. Count cells per label
9
+ 4. Min count → color 2, others → color 1
10
+ """
11
+ import numpy as np
12
+ import onnx
13
+ from onnx import helper, numpy_helper, TensorProto
14
+ import math
15
+ import os
16
+
17
+
18
+ def build_task277():
19
+ nodes, inits, vis = [], [], []
20
+ counter = [0]
21
+
22
+ def nm():
23
+ counter[0] += 1
24
+ return f"t{counter[0]}"
25
+
26
+ def const(name, val, dtype='f'):
27
+ arr = np.array(val, dtype=np.float32 if dtype == 'f' else np.int64)
28
+ inits.append(numpy_helper.from_array(arr, name))
29
+
30
+ def vi(name, shape, dt=TensorProto.FLOAT):
31
+ vis.append(helper.make_tensor_value_info(name, dt, shape))
32
+
33
+ def nd(op, ins, outs_shapes, **kwargs):
34
+ out_names = []
35
+ for sd in outs_shapes:
36
+ if isinstance(sd, tuple):
37
+ shape, dt = sd
38
+ else:
39
+ shape, dt = sd, TensorProto.FLOAT
40
+ n = nm()
41
+ vi(n, shape, dt)
42
+ out_names.append(n)
43
+ nodes.append(helper.make_node(op, ins, out_names, **kwargs))
44
+ return out_names[0] if len(out_names) == 1 else out_names
45
+
46
+ # === CONSTANTS ===
47
+ const('c_one', [1.0])
48
+ const('c_zero', [0.0])
49
+ const('c_half', [0.5])
50
+ const('c_big', [200.0])
51
+
52
+ const('sl_start', [0, 0, 0, 0], 'i')
53
+ const('sl_end', [1, 10, 10, 10], 'i')
54
+ const('sl_axes', [0, 1, 2, 3], 'i')
55
+ const('sl_ch8_start', [0, 8, 0, 0], 'i')
56
+ const('sl_ch8_end', [1, 9, 10, 10], 'i')
57
+
58
+ # Label grid: unique per cell (1-100)
59
+ const('label_init', np.arange(1, 101, dtype=np.float32).reshape(1, 1, 10, 10))
60
+ const('axes23', [2, 3], 'i')
61
+
62
+ const('pad_10_to_30', [0, 0, 0, 0, 0, 0, 20, 20], 'i')
63
+ const('pad_val_zero', [0.0])
64
+ const('zeros_7ch', np.zeros((1, 7, 10, 10), dtype=np.float32))
65
+
66
+ # === STEP 1: Get mask ===
67
+ inp10 = nd('Slice', ['input', 'sl_start', 'sl_end', 'sl_axes'], [[1, 10, 10, 10]])
68
+ mask8 = nd('Slice', [inp10, 'sl_ch8_start', 'sl_ch8_end', 'sl_axes'], [[1, 1, 10, 10]])
69
+
70
+ # === STEP 2: Label propagation ===
71
+ labels = nd('Mul', [mask8, 'label_init'], [[1, 1, 10, 10]])
72
+ for _ in range(10):
73
+ pooled = nd('MaxPool', [labels], [[1, 1, 10, 10]], kernel_shape=[3, 3], pads=[1, 1, 1, 1])
74
+ labels = nd('Mul', [pooled, mask8], [[1, 1, 10, 10]])
75
+
76
+ # === STEP 3: Find 3 unique labels ===
77
+ # L1 = max of all labels
78
+ L1 = nd('ReduceMax', [labels, 'axes23'], [[1, 1, 1, 1]], keepdims=1)
79
+
80
+ # Mask out L1: set cells with label==L1 to 0
81
+ diff_L1 = nd('Abs', [nd('Sub', [labels, L1], [[1, 1, 10, 10]])], [[1, 1, 10, 10]])
82
+ is_L1_b = nd('Less', [diff_L1, 'c_half'], [([1, 1, 10, 10], TensorProto.BOOL)])
83
+ is_L1 = nd('Cast', [is_L1_b], [[1, 1, 10, 10]], to=1)
84
+ not_L1 = nd('Sub', ['c_one', is_L1], [[1, 1, 10, 10]])
85
+ labels_no_L1 = nd('Mul', [labels, not_L1], [[1, 1, 10, 10]])
86
+
87
+ # L2 = max of remaining labels
88
+ L2 = nd('ReduceMax', [labels_no_L1, 'axes23'], [[1, 1, 1, 1]], keepdims=1)
89
+
90
+ # Mask out L2
91
+ diff_L2 = nd('Abs', [nd('Sub', [labels, L2], [[1, 1, 10, 10]])], [[1, 1, 10, 10]])
92
+ is_L2_b = nd('Less', [diff_L2, 'c_half'], [([1, 1, 10, 10], TensorProto.BOOL)])
93
+ is_L2 = nd('Cast', [is_L2_b], [[1, 1, 10, 10]], to=1)
94
+ not_L2 = nd('Sub', ['c_one', is_L2], [[1, 1, 10, 10]])
95
+ labels_no_L1_L2 = nd('Mul', [labels_no_L1, not_L2], [[1, 1, 10, 10]])
96
+
97
+ # L3 mask is just: mask8 - is_L1 - is_L2
98
+ is_L3 = nd('Sub', [mask8, nd('Add', [is_L1, is_L2], [[1, 1, 10, 10]])], [[1, 1, 10, 10]])
99
+
100
+ # === STEP 4: Count cells per CC ===
101
+ count_L1 = nd('ReduceSum', [is_L1, 'axes23'], [[1, 1, 1, 1]], keepdims=1)
102
+ count_L2 = nd('ReduceSum', [is_L2, 'axes23'], [[1, 1, 1, 1]], keepdims=1)
103
+ count_L3 = nd('ReduceSum', [is_L3, 'axes23'], [[1, 1, 1, 1]], keepdims=1)
104
+
105
+ # === STEP 5: Find minimum count ===
106
+ min_12 = nd('Min', [count_L1, count_L2], [[1, 1, 1, 1]])
107
+ min_count = nd('Min', [min_12, count_L3], [[1, 1, 1, 1]])
108
+
109
+ # Which CC has this count? (no ties guaranteed)
110
+ diff_c1 = nd('Abs', [nd('Sub', [count_L1, min_count], [[1, 1, 1, 1]])], [[1, 1, 1, 1]])
111
+ smallest_is_L1_b = nd('Less', [diff_c1, 'c_half'], [([1, 1, 1, 1], TensorProto.BOOL)])
112
+ smallest_is_L1 = nd('Cast', [smallest_is_L1_b], [[1, 1, 1, 1]], to=1)
113
+
114
+ diff_c2 = nd('Abs', [nd('Sub', [count_L2, min_count], [[1, 1, 1, 1]])], [[1, 1, 1, 1]])
115
+ smallest_is_L2_b = nd('Less', [diff_c2, 'c_half'], [([1, 1, 1, 1], TensorProto.BOOL)])
116
+ smallest_is_L2 = nd('Cast', [smallest_is_L2_b], [[1, 1, 1, 1]], to=1)
117
+
118
+ diff_c3 = nd('Abs', [nd('Sub', [count_L3, min_count], [[1, 1, 1, 1]])], [[1, 1, 1, 1]])
119
+ smallest_is_L3_b = nd('Less', [diff_c3, 'c_half'], [([1, 1, 1, 1], TensorProto.BOOL)])
120
+ smallest_is_L3 = nd('Cast', [smallest_is_L3_b], [[1, 1, 1, 1]], to=1)
121
+
122
+ # === STEP 6: Build smallest mask and other mask ===
123
+ sm1 = nd('Mul', [is_L1, smallest_is_L1], [[1, 1, 10, 10]])
124
+ sm2 = nd('Mul', [is_L2, smallest_is_L2], [[1, 1, 10, 10]])
125
+ sm3 = nd('Mul', [is_L3, smallest_is_L3], [[1, 1, 10, 10]])
126
+ smallest_mask = nd('Add', [sm1, nd('Add', [sm2, sm3], [[1, 1, 10, 10]])], [[1, 1, 10, 10]])
127
+ other_mask = nd('Sub', [mask8, smallest_mask], [[1, 1, 10, 10]])
128
+
129
+ # === STEP 7: Build output ===
130
+ bg_mask = nd('Sub', ['c_one', mask8], [[1, 1, 10, 10]])
131
+ out_10 = nd('Concat', [bg_mask, other_mask, smallest_mask, 'zeros_7ch'], [[1, 10, 10, 10]], axis=1)
132
+
133
+ # === STEP 8: Pad — output directly ===
134
+ nodes.append(helper.make_node('Pad', [out_10, 'pad_10_to_30', 'pad_val_zero'], ['output']))
135
+
136
+ x = helper.make_tensor_value_info('input', TensorProto.FLOAT, [1, 10, 30, 30])
137
+ y = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 10, 30, 30])
138
+
139
+ graph = helper.make_graph(nodes, 'task277', [x], [y], initializer=inits, value_info=vis)
140
+ model = helper.make_model(graph, ir_version=10, opset_imports=[helper.make_opsetid('', 18)])
141
+ return model
142
+
143
+
144
+ if __name__ == '__main__':
145
+ import onnxruntime as ort
146
+ import json
147
+
148
+ print("Building Task 277 ONNX model v2...")
149
+ model = build_task277()
150
+
151
+ output_path = '/app/task277.onnx'
152
+ del model.graph.value_info[:]
153
+ model = onnx.shape_inference.infer_shapes(model, strict_mode=True)
154
+ onnx.save(model, output_path)
155
+
156
+ print(f" Nodes: {len(model.graph.node)}")
157
+ print(f" File size: {os.path.getsize(output_path):,} bytes")
158
+
159
+ sess = ort.InferenceSession(output_path)
160
+ with open('/app/task-data/task277.json') as f:
161
+ data = json.load(f)
162
+
163
+ all_examples = data['train'] + data['test'] + data.get('arc-gen', [])
164
+ right_count, wrong_count = 0, 0
165
+ for i, ex in enumerate(all_examples):
166
+ inp = np.zeros((1, 10, 30, 30), dtype=np.float32)
167
+ for r, row in enumerate(ex['input']):
168
+ for c, v in enumerate(row):
169
+ if r < 30 and c < 30:
170
+ inp[0][v][r][c] = 1.0
171
+ result = sess.run(['output'], {'input': inp})
172
+ out = (result[0] > 0.0).astype(float)
173
+ exp = np.zeros((1, 10, 30, 30), dtype=np.float32)
174
+ for r, row in enumerate(ex['output']):
175
+ for c, v in enumerate(row):
176
+ if r < 30 and c < 30:
177
+ exp[0][v][r][c] = 1.0
178
+ if np.array_equal(out, exp):
179
+ right_count += 1
180
+ else:
181
+ wrong_count += 1
182
+ if wrong_count <= 5:
183
+ print(f" FAIL {i}: {len(np.where(out != exp)[0])} diffs")
184
+
185
+ print(f" Results: {right_count}/{right_count+wrong_count} pass ({wrong_count} fail)")
186
+
187
+ if wrong_count == 0:
188
+ params = sum(int(np.prod(init.dims)) for init in model.graph.initializer)
189
+ mem_est = sum(int(np.prod([d.dim_value for d in vi.type.tensor_type.shape.dim])) *
190
+ np.dtype(onnx.helper.tensor_dtype_to_np_dtype(vi.type.tensor_type.elem_type)).itemsize
191
+ for vi in model.graph.value_info
192
+ if vi.type.HasField('tensor_type') and vi.type.tensor_type.HasField('shape'))
193
+ score_est = max(1.0, 25.0 - math.log(max(1.0, mem_est + params)))
194
+ print(f" Params: {params:,}, Memory: {mem_est:,}, Score: {score_est:.3f}")
195
+ print(f" Base: 13.30. Gain: {score_est - 13.30:+.3f}")