Files
InfoRSI/tasks/circle_packing/TASK.md
T
iomgaa 09c44031a0 feat: 任务书 10,submissions 模块与 materials 修改
- 新增 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>
2026-10-03 06:08:34 -04:00

297 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 填圆任务(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` 拉取,换机器时可能要改这一行。)