- 新增 src/submissions:先程序检查(14 条跨字段规则、不许改的文件、代码存不了、指标编号占用、提案数上限),全过再问 Jev;退回原因列出该写的全部编号;回应/统一拼冻结协议主体只在这一处;核验、消融核验通过没有。 - materials:挑刺单写"现在交什么";退回按阶段、作者、种类、对象找(ledger 退回记录加 target,表结构版本 4);核验材料去掉 Jev 预扫;信念本写合法化;删"本该关着";消融核验不通过可重做,不跑的格从 cellNotRun 事件读原因。 - 用户 2026-10-03 定:冻结协议去掉 replies、replyBody,第二次预测的答复从已公开的回应或统一读;takenMetricIds 加两个保留键;解释一律交 cells;Jev 退回只列判"是"的几问、用英文问题名。 - jev 判重材料带 keySettings;读代码工具删 offset;三处旧说法改正。 - 文档:结构.md 第四、七、八部分,WORK_LOG。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
17 KiB
填圆任务(n=26,单位正方形)
这份文件是给实验员看的任务契约:要做什么、产物写在哪、键名怎么写。 读它的时候假定你手上已经有一份冻结的提案,本文件不重复提案里的内容。
目标
在单位正方形 [0,1]² 内放置 26 个互不重叠、不越过边界的圆,最大化半径之和。
最终指标:sum-of-radii,越大越好。
量级参照:容差 0 的口径下,26 个圆公开的最好半径和约 2.635983(AlphaEvolve 续作公开的构造, 按它自己的严格校验在本机重算得 2.6359830849)。常见的"2.635"是白皮书截到三位小数的数,不是另一个纪录。 别处报告的更高的数要先看容差:容差放到 1e-6,把每个半径加 5e-7 也判合格,和就能到 2.635996——那是口径不同,不是更好的摆法。
成绩是从你交出来的产物算的,不是从你返回的那个数读的。
validate.py 读 solution.json 里的 26 个半径,用 math.fsum 求和,那就是成绩。
你在 run_packing() 里返回的第三个值(sum_of_radii)不是成绩。
harness.py 照常把它写进 solution.json 的 reported_sum 字段,但
validate.py 根本不读这个字段:它不进判定、不进诊断,也不跟任何数对账。
所以这个数写过期了、跟半径对不上,产物照样合法,成绩照样是算出来的那一个。
只有一件事要注意,而且跟对账无关:harness.py 落盘时不接受非有限数,
所以这个值要是 NaN 或无穷大,整份 solution.json 根本写不出来,
这次运行连产物都没有。返回一个普通的有限数就行,哪怕它是错的。
评测会收下什么
判断代码里一段检查、修复或兜底"挡掉了谁、改了什么",要先知道评测本身会收下什么。 评测收下一次运行,按下面的次序:
- 你交的
solution.json形状对(26 个圆心、26 个半径)、全是有限数、半径不为负、圆心都在正方形里。 这几条任何一条不成立,评测不收,也不修。 - 原产物按容差 0 判:任意两圆不重叠、每个圆不越出正方形边。成立就收下,成绩是原产物的半径和。
- 不成立时,先按下一节"只缩不移合法化"修一次,修后再按容差 0 判。修得好就收下,成绩是修后的半径和 (只比原产物低一点);修不好才不收。
所以"差一点不合法"(重叠或越界只有浮点零头那么大)的产物,评测本来会收,只扣掉零头。 候选代码里比这更严的检查——例如容差 0 判不合法就把这个起点整个丢掉、或换成另一份解——挡掉的正是评测会收的产物, 它改的是交出去的答案,不是"让代码合法"。反过来,只在评测确实不收的情形(形状不对、非有限数、圆心出界) 才动手修或换的代码,在评测会收的运行上不改答案。
差一点不合法时:只缩不移合法化
校验器先按容差 0 判你交的原产物。原产物不合法时,宿主按下面这条写死的规则修一次, 只缩半径、不挪圆心,修好了就用修后的半径和当成绩:
- **只缩参与违反的圆。**每个圆的半径减去它参与的最大违反量——跟别的圆重叠的量、 越出正方形边的量,取最大的那个。没参与违反的圆一个都不动。
- 缩完仍不合法(浮点舍入留下的零头),把这些圆的半径各往 0 挪一个最小的浮点单位, 再判,直到合法。
- 同一个校验器按容差 0 重判修后的产物。
- 修不了:有圆心在正方形外面;参与违反的圆半径都缩到 0 仍不合法; 形状不对、含非有限值或负半径(这些不是"差一点",缩半径解决不了)。 修不了的运行照旧记失败,运行次数照扣——跟跑崩、超时、没产物一样。
缩小只会降分,所以这条规则不会让不合法的摆法比合法的更高分。它管的是"浮点上差一点": 例如两个圆重叠 2.8e-17,修后扣掉约 2.2e-16。要一分不扣,就在代码里给每个圆留一点空隙。
每次运行多记三个数,跟成绩一起进运行记录:原违反量(raw-violation)、
原分数(raw-score)、合法化扣掉的分(legalization-deduction,原产物合法时是 0)。
什么样的结果才算数
下面五条不是禁令,是这次实验要成立需要的条件。写出来是为了让你知道该往哪边走, 不是为了防谁。
一 · 成绩必须是算出来的,不能是写死的
代码里可以有常数:半径比例、松弛步长、网格怎么划分、迭代多少轮,都可以写死。 不能有的是一张 26 行的答案表——把某次跑出来的圆心和半径直接抄进源码返回。
为什么:研究要找的是方法,不是一个数。一份写死的答案没有机制可以解释—— 问它"为什么这个改法有效",答案是"因为答案是抄的",而这句话不会出现在提案里。 一个没有方法的高分会让整轮研究的结论是假的:后面几轮会建立在 "某某方向有效"这个不存在的发现之上。
二 · 只读工作目录里的东西,不联网
你可以带自己的数据文件,它们会跟代码一起冻结、一起放进工作目录。 但运行时不要去读工作目录外面的文件。正式运行的容器是断网的,网络请求一律失败。
为什么:冻结的代码要能完整说清"这次跑的是什么"。 从网上取一份东西进来,那份东西明天可能就变了,这次运行就再也重现不了; 读工作目录外面的文件也一样,那个文件不在冻结的代码里。
三 · 在执行限额之内跑完
每次正式运行的墙钟时限是 120 秒、1 核,同一批候选都一样。到时限还没结束,进程被直接杀掉, 这次运行记为超时失败,运行次数照扣。
为什么:跟基线比的前提是同样的条件。用一百倍时间换来的成绩, 说明的是算力,不是方法。
四 · 用了多少算力要说出来
每次运行都记墙钟时间(wall-seconds)、CPU 秒(cpu-seconds)和峰值内存(peak-memory-bytes),
材料里同时写"是基线的几倍"和"占单次时限的几成"。这些只记录,不影响采纳。
加大搜索预算本身是正当的做法,不需要藏着。但用远多于基线的算力换来的提升, 说明的是算力,不是方法——所以要在提案里写明加了多少、为什么值得。
为什么:跟基线比的前提是同样的条件。一次多花一百五十倍时间、换来百分之七提升的运行, 如果被记成"某某改法有效",后面几轮会去改那个改法,而真正起作用的是预算。
五 · 说了什么就要真的做什么
代码和注释里写了"迭代到收敛",就要真的迭代到收敛;写了"最多 1000 步", 就不能实际只跑 2 步。
为什么:这一条跟前面几条不一样,它坏的不是成绩,是解释。 成绩可能完全是真的、可复现的,但"松弛到收敛有效"这条结论会进记录, 下一轮建立在一个没有发生过的机制之上。对不上的时候, 该作废的是那条结论,不是这次运行。
这也是为什么第一条说"没有方法的高分会让整轮研究的结论是假的"—— 证据链上的错,后面每一轮都看不见。
运行方法
候选可以是多文件 Python 项目,普通相对数据资源会保留;入口 initial.py 必须定义:
def run_packing():
...
return centers, radii, sum_of_radii # (26,2)、(26,)、float
正式运行由程序启动,在任务镜像里、断网、1 核、120 秒时限下执行(工作目录就是你的代码目录):
python -B harness.py --program initial.py --output solution.json
跑完以后,宿主把 solution.json 复制到另一个目录,用自己保管的校验器判分:
python -B validate.py --circles 26 --solution solution.json --atol 0 --legalize --verdict verdict.json
harness.py 在 import 候选前,用环境变量 INFORSI_SEED 给的种子字符串(取 SHA256 前 4 字节大端整数)
初始化 Python 的 random 和 numpy 的 random,执行候选并原子落盘原始解,不做任何有效性判断。
import 之前它还会删掉工作目录里已有的 solution.json 和 metrics.json,宿主读到的只会是这一次写的。
你要用随机,用这两个。同一份研究的全部运行用同一个种子,所以同一份代码跑两次,结果逐位相同。
task.json 的 unversionedNames 列的名字(__pycache__、.git、solution.json、metrics.json)在任何一层目录都不会存进代码版本:程序冻结你的工作目录时跳过它们,所以不要把要交的代码或数据放在这些名字下面。
validate.py 是独立校验器,重算半径和并检查形状 / 有限 / 非负 / 边界 / 重叠;
加 --legalize 时按上面"只缩不移合法化"一节修差一点不合法的产物(正式评测就是这样调的)。
写代码的过程中可以做轻量检查:由宿主在同一个镜像里跑,只看语法和入口函数, 不执行候选的任何一行代码。它有次数和时长上限(每次几秒),不算一次正式运行。
不要自己跑正式规模的求解。 正式运行要先登记、再启动、再扣次数,由程序统一做; 正式规模的求解哪怕叫"调试",也是正式运行,不能借轻量检查或自己的 shell 绕开预算。
指标怎么交给宿主(这一节最容易写错,先读完再动手)
工作目录里有两个指标文件,一进一出。路径都相对工作目录,也就是 harness.py
运行时的 cwd,不要写绝对路径。
metrics-spec.json:宿主写给你的
宿主在准备运行目录时写好它,内容是这次运行登记的假设专用指标定义, 一个 JSON 数组,每个元素是一条指标:
| 字段 | 含义 |
|---|---|
id |
指标编号,宿主按它取值 |
meaning |
这个量测的是什么 |
unit |
单位 |
direction |
maximize 越大越好,minimize 越小越好 |
howMeasured |
研究者写的测法:在什么时刻测、测几次、怎么合成一个数。你照着它实现 |
missing |
测不到时这次运行怎么算:recordMissing 只记缺失、成绩照常;invalidateRun 整次作废 |
没有假设专用指标时,这个文件是一个空数组 []。
metrics.json:你写给宿主的
一个 JSON 对象:键必须是 metrics-spec.json 里那条指标的 id 字段原文,
值是有限数或 null。另外每次都要写两个保留键(下面"两个保留键"一节),它们不是指标、值不是数。
编号从
metrics-spec.json里照抄,不要自己拼。宿主按编号取值,键对不上, 这条指标就记成"测不到"加一句原因——而成绩完全正常,报告上看不出任何异常, 只是研究者分析机制时悄悄少了一块证据。
四条硬要求:
- 键照抄
id,见上。 - 值是有限数或
null。测不到就写null,不要写 0。 写 0 之后宿主分不出"测到了一个 0"和"没测到",而这种错事后从证据里查不出来。 算出NaN/inf(退化布局里零半径参与的比值最常见)也一样,那是"没测到"。 harness 会把非有限值原样改写成null并在 stderr 记一行,退出码仍是 0—— 成绩不受影响,但那条指标这一次就是没有数据。 - 必须在
run_packing()返回之前写完。 harness 在候选返回后立刻整理这个文件; 之后再写进去的内容不会被整理(非有限值不会变成null,裸NaN会让宿主整份读不出来, 这一次所有假设专用指标一起丢,不只是坏的那一条)。 - 键多了无所谓,宿主只按
metrics-spec.json里的编号取值;键少了,缺的那条记"测不到"加一句原因。
文件不存在表示这一次的假设专用指标一个都没测到——这是一个明确状态,不是错误, 但它和"写错了键名"在报告里看起来一样,所以别靠它。
测不到的后果看 missing:选了 invalidateRun 的指标测不到,这次运行整次作废、不能被采纳,
运行次数照扣;选了 recordMissing 的只记缺失。
两个保留键:答案出自哪条路径
研究者不能运行代码,"最终交的答案到底是哪段代码给的"只能读代码猜,猜错了没人发现。
所以每次运行由你的代码自己说出来,写在同一个 metrics.json 里:
| 键 | 值 | 写什么 |
|---|---|---|
answer-path |
字符串 | 最终交的答案出自哪里:main(正常主流程),或冻结协议因子清单里某个因子的编号(p1、p2……),或保障清单里某条保障的短名 |
safeguard-triggers |
对象 | 键是冻结协议保障清单里的每一条保障短名,值是这次运行里它动手了几次(非负整数,没动手写 0);保障清单为空时写 {} |
- 这两个键不是指标,不参与成绩判定;写错、没写都不作废运行,宿主照实记"候选没写答案路径" 或"不认识的路径:原文"。但研究者下一阶段就靠它看"这一格量到的是因子还是保障",所以要如实写。
- 消融格(关掉某个因子的版本)同样要写:关掉的因子不会是答案路径;如果关掉之后答案改由某条保障给出,就写那条保障。
- harness 不改写这两个键(指标值才会把非有限数改写成
null)。同样要在run_packing()返回之前写完。
一小段示例
import json
import math
SPEC_FILE = "metrics-spec.json"
METRICS_FILE = "metrics.json"
def run_packing():
with open(SPEC_FILE, encoding="utf-8") as handle:
spec = json.load(handle)
# spec[i]["id"] 就是宿主认的编号,spec[i]["howMeasured"] 是照着实现的测法。
# 这里按这次实际条数对号入座,下面只演示一条。
spread_id = spec[0]["id"]
centers, radii, reported_sum = solve()
spread = float(max(radii) - min(radii))
measured = {
spread_id: spread if math.isfinite(spread) else None,
# 两个保留键:答案出自主流程;协议里的一条保障 "shrink-repair" 这次没动手。
"answer-path": "main",
"safeguard-triggers": {"shrink-repair": 0},
}
# 在 return 之前写完,路径相对工作目录。
with open(METRICS_FILE, "w", encoding="utf-8") as handle:
json.dump(measured, handle)
return centers, radii, reported_sum
最终指标 sum-of-radii 和合法性判定不经过这条链:它们由 validate.py
从 solution.json 独立重算,你不用也不该在 metrics.json 里写它们。
允许修改范围
候选只能修改自己代码目录里的 *.py 和自带的数据文件(基线的入口是 initial.py,其中
EVOLVE-BLOCK-START / EVOLVE-BLOCK-END 之间是官方预留的进化区域)。
不许改的文件见同目录 task.json 的 protectedFiles;程序会检查有没有改它们。
harness.py 和 metrics-spec.json 在准备运行目录时由宿主放进去,你目录里的同名文件会被盖掉;
校验器和轻量检查脚本由宿主保管,不在你的目录里。也不要改校验容差或执行限额。
基线与许可
baseline/ 为 SakanaAI/ShinkaEvolve 官方代码,Apache-2.0(见 baseline/LICENSE),
固定提交与 sha256 见 baseline/SOURCES.md。基线的嵌套缩半径循环与退化布局是
刻意保留的原始事实,不得"修复"——任何改进必须作为正式候选经过研究流程。
依赖
Python 3.13。正式实验环境里装的第三方库只有下面这些,版本与 requirements.txt 逐行一致:
| 包 | 版本 | 用途 |
|---|---|---|
| numpy | 2.3.1 | 数组与矩阵运算 |
| scipy | 1.18.1 | 数值优化、线性规划、空间距离计算等 |
| numba | 0.67.0 | 把 Python 循环编译成机器码 |
| llvmlite | 0.49.0 | numba 的编译后端 |
| sympy | 1.14.0 | 符号与精确计算 |
| mpmath | 1.3.0 | 任意精度浮点运算(sympy 依赖它) |
| networkx | 3.7 | 图结构分析 |
**依赖只有镜像里的这几个包。**运行时不能再装别的包:正式运行在固定的镜像里跑、容器断网, 你在工作目录里装的包进不了正式运行。
**每次运行只有 1 核。**容器按 --cpus=1 限总量,环境里 PYTHON_CPU_COUNT=1,
所以 os.cpu_count()、multiprocessing.cpu_count() 都报 1。多开进程或线程不会更快,
只会互相抢这 1 个核、少干活。数学库(OMP_NUM_THREADS)和 numba(NUMBA_NUM_THREADS)的线程数都固定为 1。numba 首次编译函数的时间算在这次运行的墙钟里。
**环境由镜像钉死。**任务跑在 Dockerfile 建出来的镜像里,这些库都烧在那个镜像里。
镜像在研究开始时建一次,镜像编号冻结进账本;一份研究不能自己改依赖。
(给维护者:Dockerfile 的基础镜像经 dockerproxy.net 拉取,换机器时可能要改这一行。)