The problem in brief
The IOAI logo is described as a function on a rectangle of the plane, with five regions. In the first letter I the field is a linear gradient. In the O it follows a spiral pattern. In the A it is the constant . In the last I it must be random, and the randomness may only come from nn.Dropout. Everywhere else it is . Hidden parameters change the pose of each letter (angle, scale, thickness). You must train from scratch a PyTorch model with fewer than 20,260 parameters that reproduces the field. During the contest you could sample the official generator as much as you liked. At grading time your notebook was re-run on hidden configurations, and the model was scored region by region.
What we could actually evaluate. The official generator is shipped only as obfuscated binaries for Linux x86-64 and CPython 3.13, which cannot run on our machine (no Linux container available). The organisers' Hugging Face dataset, however, contains sampled fields from the same family: 40 training fields (3000 labelled points each) and 8 held-out fields, each with 2500 labelled context points and 6000 query points whose values and region ids are in the private part. In these files the first I ranges from about to , not and more as in the contest configs. We therefore solve the closest scorable version: for each held-out field, train a model on its 2500 context points only, then score it on its 6000 query points with the official region formulas. This is harder than the contest on one side (2500 points instead of unlimited sampling), and easier on the other (smaller values in the first I). The scores below are not comparable to the contest leaderboard.
How you are scored
For the first I, the O and the A, the score is a normalised mean absolute error:
where is the spread between the 1st and 99th percentiles of the true values in the region (1 if the region is constant, as for A). For the background the scale is fixed and tiny, . For the last I, the grader turns dropout on, runs the model times, and gives each point with . A point scores 0 if any of its 10 outputs leaves . The total is the mean of the five region scores, and it is halved if the model has 20,260 parameters or more.
Contest reference points (statement): the official baseline scored 31.22 and the Scientific Committee 63.53 on the real task. In the official results table, the second-best contestant has 71.4 and the median is 14.2; the table's maximum is exactly 100, apparently rescaled to the best contestant. In our sampled-field version, predicting the constant 0 everywhere already scores 46.8, because the first I is centred on zero there.
First look at the data

Three observations decide everything.
- The letters are thin strokes, and the first I has serifs, so it is not convex. Our first geometric idea, "the first I is the convex hull of its points", was wrong and made things worse (see Results).
- The metric is very asymmetric. In the background the scale is . A single background point predicted at value 10 (inside the first I's gradient) adds to the normalised error. With 512 background points that is 20 % of the background score, and in the contest, where the first I is worth , it costs the whole region. A point of the first I predicted as 0 costs at most : little, especially near the middle of the gradient where .
- The O is an exact formula. Plotted in polar coordinates around the ring's centre, the O values are with and : a two-armed spiral. The catch is that the centre must be very precise. The mean of the O points is off by about 0.05, and with that shifts the phase by about 2 radians, which destroys the fit.
Labels for the context points. The context points carry values, not region ids, but the regions can be read off the values: is background, is the A, and the remaining points split into three spatial groups (single-linkage clustering), ordered left to right as first I, O and last I.
A first baseline
The hinted architecture is a router plus heads. A small MLP classifies each point into one of the five regions; a hard argmax picks the head; each region has its own small head:
- first I:
nn.Linear(2, 1)fitted by least squares (the gradient is exactly linear: residual ); - O: a small MLP on polar features around the centroid of the O points;
- A: the constant ;
- last I: , which is in eval mode and in train mode;
- background: .
The router is an MLP ( 16 random Fourier features , GELU), trained with class-balanced cross-entropy on the 2500 context points. The whole model has 8,118 parameters. Mean over the 8 held-out fields: 76.5. The regions are classified with 97 % accuracy, and the first I scores 0.97, but the background scores only 0.20. The diagnosis (top panel of the next figure): about 30 background points per field are routed to the first I, just outside its strokes, and each one carries a large linear-gradient value.
The key idea
The router should not choose the most probable region. It should choose the output that costs the least score in expectation. Let be the router's probability (with class-balanced training, so it matches the grader's equal number of points per region), the output if we route to , and the value the field would have if the point belonged to (given by the heads: linear gradient, spiral, , ). Route to
where each cost is the normalised error in that region's own units, capped at 1:
The last I adds a cost of 1 for every choice except the last-I head. This is plain Bayes decision theory: minimise the expected loss instead of the expected error rate. The router's probabilities are overconfident, so we soften them with a temperature, ; was chosen on the training fields, never on the held-out ones.
Two consequences, both visible in the figure below.
- Near the middle of the first I, routing to "zero" wins. There is small compared with , so predicting 0 costs almost nothing, while a background point predicted on the gradient would cost a lot. Expected-loss routing gives up the cheap points of the I and protects the background: background points routed to the first I fall from 33 to 7 on this field.
- The last-I head is free for the background. Its eval-mode output is exactly 0, the right background value, and its noise only appears in train mode, which the grader uses only on last-I points. So every uncertain point can go to the noise head: it is correct if it is background and full score if it is the last I. The last-I score jumps to 0.998.

Second idea: an exact formula beats a network when you can find it. We replace the O network with a 6-parameter head . Its centre is learned, and it is fitted by L1 regression (the metric is an MAE) from 96 starting points (, , 4 phases), 300 Adam steps each. On field 2 it finds , , centre , and a query MAE of 0.023 from only 191 points.
The full solution
- Labels: derive the region ids of the 2500 context points from their values (0, , three clusters ordered by ).
- Router: an MLP on fixed random Fourier features (stored as non-trainable
nn.Parameters, because the grader forbids buffers), trained 2000 Adam steps with class-balanced cross-entropy. - First I head: a linear layer set by least squares on the first-I context points.
- O head: the spiral formula, fitted by multi-start L1 regression with a learnable centre.
- A, background, last I: the constant , zero, and .
- Routing at inference: expected-score-loss decision with , using the heads' own outputs as the candidate values.
p = torch.softmax(self.logits(xy) / self.temp, 1)
yi, yo = self.heads(xy) # gradient and spiral at every point
outs = torch.stack([yi, yo, -ones, zeros, zeros], 1) # eval-mode output of each choice
cost = p[:, BG:BG+1] * (outs.abs() / 0.1).clamp(max=1) # background truth
for j, (truth, s) in enumerate([(yi, s_I), (yo, s_O), (-ones, 1.0)]):
cost += p[:, j:j+1] * ((outs - truth[:, None]).abs() / s).clamp(max=1)
cost += p[:, E:E+1] * miss_e # last I: lose 1 unless routed to the noise head
reg = cost.argmin(1)
Everything is plain torch inside one nn.Module with 8,118 parameters, deterministic in eval mode, with randomness only from nn.Dropout. Fitting one field takes about 15 s on two CPU threads.
Results
Mean over the 8 held-out fields, each model trained on that field's 2500 context points and scored on its 6000 query points (official region formulas; query values and regions from private/test_private.npz). Tuning (bias, temperature) was done only on 8 training fields, split 2500/500.
| approach | first I | O | A | bg | last I | total |
|---|---|---|---|---|---|---|
| constant 0 everywhere | 1.00 | 0 | 46.8 | |||
| constant everywhere | 1.00 | 0 | 0 | 42.9 | ||
| router (argmax) + linear I + MLP O | 0.974 | 0.776 | 0.940 | 0.201 | 0.932 | 76.5 |
| same + background logit bias 5 (crude conservatism) | 0.941 | 0.774 | 0.832 | 0.668 | 0.813 | 80.6 |
| first I = convex hull of its points (failed: the I has serifs) | 0.909 | 0.757 | 0.960 | 0.000 | 0.934 | 71.2 (train fields) |
| argmax + spiral O | 0.974 | 0.948 | 0.940 | 0.214 | 0.932 | 80.2 |
| expected-loss routing () + MLP O | 0.862 | 0.764 | 0.913 | 0.811 | 0.998 | 86.9 |
expected-loss routing + spiral O (final, solution.py) | 0.881 | 0.912 | 0.914 | 0.784 | 0.998 | 89.8 |
The two ideas stack up: the spiral helps the O, expected-loss routing helps the background and the last I, and together they turn 76.5 into 89.8. The remaining loss is mostly background points routed to the A or the O near their strokes (46 on field 2): with 2500 random points the strokes' edges are only known to within about 0.04.
How this transfers to the real contest. With unlimited samples from the generator, the router can be trained on millions of points, so its boundaries become far sharper than with 2500 points. With values of in the first I, our capped per-point cost understates the danger: any background point routed to the first I zeroes the whole background region. A contestant should then forbid that routing unless the router is near-certain (in practice, a very low temperature on the first I only, or an eroded I mask), and normalise the linear head by i_grad_min/i_grad_max from the config file. We could not verify these numbers without the official runtime, so we make no claim against the 63.53 of the Scientific Committee.
What to remember
- Decide with the metric, not with the argmax. When errors have very different costs, choose . Here this made the model give up cheap points of the I to protect the expensive background.
- Read how the grader uses each mode. The noise head's output is 0 in eval mode and random only in train mode, which the grader uses only on the last I. Treating it as the "background or last I" head was worth several points, entirely within the rules.
- Look for the formula. A 6-parameter spiral, fitted with a learnable centre and multiple starts, beat a neural head trained on the same 150 points (O score from 0.78 to 0.91). Small parameter budgets reward structure, not width.
- Check geometric assumptions on a picture first. The convex-hull idea looked elegant and failed, because the I has serifs.
- Tune on training fields, report on held-out ones. The temperature was chosen on the training fields; the held-out score came out within 2 points of the training-field estimate (89.8 against 92.0).