09c44031a0
- 新增 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>
297 lines
17 KiB
Markdown
297 lines
17 KiB
Markdown
# 填圆任务(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` 根本写不出来,
|
||
这次运行连产物都没有。返回一个普通的有限数就行,哪怕它是错的。
|
||
|
||
## 评测会收下什么
|
||
|
||
判断代码里一段检查、修复或兜底"挡掉了谁、改了什么",要先知道评测本身会收下什么。
|
||
**评测收下一次运行,按下面的次序:**
|
||
|
||
1. 你交的 `solution.json` 形状对(26 个圆心、26 个半径)、全是有限数、半径不为负、圆心都在正方形里。
|
||
这几条任何一条不成立,评测**不收**,也不修。
|
||
2. 原产物按**容差 0** 判:任意两圆不重叠、每个圆不越出正方形边。成立就收下,成绩是原产物的半径和。
|
||
3. 不成立时,先按下一节"只缩不移合法化"修一次,修后再按容差 0 判。修得好就收下,成绩是修后的半径和
|
||
(只比原产物低一点);修不好才不收。
|
||
|
||
所以"差一点不合法"(重叠或越界只有浮点零头那么大)的产物,**评测本来会收**,只扣掉零头。
|
||
候选代码里比这更严的检查——例如容差 0 判不合法就把这个起点整个丢掉、或换成另一份解——挡掉的正是评测会收的产物,
|
||
它改的是交出去的答案,不是"让代码合法"。反过来,只在评测确实不收的情形(形状不对、非有限数、圆心出界)
|
||
才动手修或换的代码,在评测会收的运行上不改答案。
|
||
|
||
## 差一点不合法时:只缩不移合法化
|
||
|
||
校验器先按容差 0 判你交的原产物。原产物不合法时,宿主按下面这条写死的规则修一次,
|
||
**只缩半径、不挪圆心**,修好了就用修后的半径和当成绩:
|
||
|
||
1. **只缩参与违反的圆。**每个圆的半径减去它参与的最大违反量——跟别的圆重叠的量、
|
||
越出正方形边的量,取最大的那个。没参与违反的圆一个都不动。
|
||
2. 缩完仍不合法(浮点舍入留下的零头),把这些圆的半径各往 0 挪一个最小的浮点单位,
|
||
再判,直到合法。
|
||
3. 同一个校验器按容差 0 重判修后的产物。
|
||
4. **修不了**:有圆心在正方形外面;参与违反的圆半径都缩到 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` 必须定义:
|
||
|
||
```python
|
||
def run_packing():
|
||
...
|
||
return centers, radii, sum_of_radii # (26,2)、(26,)、float
|
||
```
|
||
|
||
正式运行由程序启动,在任务镜像里、断网、1 核、120 秒时限下执行(工作目录就是你的代码目录):
|
||
|
||
```bash
|
||
python -B harness.py --program initial.py --output solution.json
|
||
```
|
||
|
||
跑完以后,宿主把 `solution.json` 复制到另一个目录,用自己保管的校验器判分:
|
||
|
||
```bash
|
||
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` 里**照抄**,不要自己拼。宿主按编号取值,键对不上,
|
||
> 这条指标就记成"测不到"加一句原因——**而成绩完全正常**,报告上看不出任何异常,
|
||
> 只是研究者分析机制时悄悄少了一块证据。
|
||
|
||
四条硬要求:
|
||
|
||
1. **键照抄 `id`**,见上。
|
||
2. **值是有限数或 `null`。测不到就写 `null`,不要写 0。**
|
||
写 0 之后宿主分不出"测到了一个 0"和"没测到",而这种错事后从证据里查不出来。
|
||
算出 `NaN` / `inf`(退化布局里零半径参与的比值最常见)也一样,那是"没测到"。
|
||
harness 会把非有限值原样改写成 `null` 并在 stderr 记一行,退出码仍是 0——
|
||
成绩不受影响,但那条指标这一次就是没有数据。
|
||
3. **必须在 `run_packing()` 返回之前写完。** harness 在候选返回后立刻整理这个文件;
|
||
之后再写进去的内容不会被整理(非有限值不会变成 `null`,裸 `NaN` 会让宿主整份读不出来,
|
||
**这一次所有假设专用指标一起丢**,不只是坏的那一条)。
|
||
4. 键多了无所谓,宿主只按 `metrics-spec.json` 里的编号取值;键少了,缺的那条记"测不到"加一句原因。
|
||
|
||
文件不存在表示这一次的假设专用指标一个都没测到——这是一个明确状态,不是错误,
|
||
但它和"写错了键名"在报告里看起来一样,所以别靠它。
|
||
|
||
**测不到的后果看 `missing`**:选了 `invalidateRun` 的指标测不到,这次运行整次作废、不能被采纳,
|
||
运行次数照扣;选了 `recordMissing` 的只记缺失。
|
||
|
||
### 两个保留键:答案出自哪条路径
|
||
|
||
研究者不能运行代码,"最终交的答案到底是哪段代码给的"只能读代码猜,猜错了没人发现。
|
||
所以每次运行由你的代码自己说出来,写在同一个 `metrics.json` 里:
|
||
|
||
| 键 | 值 | 写什么 |
|
||
| --- | --- | --- |
|
||
| `answer-path` | 字符串 | 最终交的答案出自哪里:`main`(正常主流程),或冻结协议因子清单里某个因子的编号(`p1`、`p2`……),或保障清单里某条保障的短名 |
|
||
| `safeguard-triggers` | 对象 | 键是冻结协议保障清单里的**每一条**保障短名,值是这次运行里它动手了几次(非负整数,没动手写 0);保障清单为空时写 `{}` |
|
||
|
||
- 这两个键**不是指标**,不参与成绩判定;写错、没写都不作废运行,宿主照实记"候选没写答案路径"
|
||
或"不认识的路径:原文"。但研究者下一阶段就靠它看"这一格量到的是因子还是保障",所以要如实写。
|
||
- 消融格(关掉某个因子的版本)同样要写:关掉的因子不会是答案路径;如果关掉之后答案改由某条保障给出,就写那条保障。
|
||
- harness 不改写这两个键(指标值才会把非有限数改写成 `null`)。同样要在 `run_packing()` 返回之前写完。
|
||
|
||
### 一小段示例
|
||
|
||
```python
|
||
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` 拉取,换机器时可能要改这一行。)
|