dsh-eval-guard 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/ISSUES.md +4 -2
  2. package/README.md +3 -3
  3. package/assets/neural-pendulum/SPEC.md +40 -42
  4. package/assets/neural-pendulum/TASK.md +34 -47
  5. package/assets/neural-pendulum/sim.cpp +97 -45
  6. package/assets/neural-pendulum/sim_agent +0 -0
  7. package/assets/neural-pendulum/sim_eval +0 -0
  8. package/assets/neural-pendulum/workspace.zip +0 -0
  9. package/lib/index.js +27 -10
  10. package/neural-pendulum-eval/README.md +4 -3
  11. package/neural-pendulum-eval/run_eval.py +133 -70
  12. package/neural-pendulum-eval/sim_eval +0 -0
  13. package/package.json +1 -1
  14. package/assets/neural-pendulum/rl-feasibility/README.md +0 -60
  15. package/assets/neural-pendulum/rl-feasibility/RESULTS.md +0 -370
  16. package/assets/neural-pendulum/rl-feasibility/evaluate.py +0 -107
  17. package/assets/neural-pendulum/rl-feasibility/finalize_run.py +0 -168
  18. package/assets/neural-pendulum/rl-feasibility/models.py +0 -124
  19. package/assets/neural-pendulum/rl-feasibility/obs.py +0 -205
  20. package/assets/neural-pendulum/rl-feasibility/plot_selected.py +0 -55
  21. package/assets/neural-pendulum/rl-feasibility/reference_controller.py +0 -112
  22. package/assets/neural-pendulum/rl-feasibility/results/curves_selected.png +0 -0
  23. package/assets/neural-pendulum/rl-feasibility/results/reference_controller.json +0 -417
  24. package/assets/neural-pendulum/rl-feasibility/results/summary.json +0 -1599
  25. package/assets/neural-pendulum/rl-feasibility/results/table.md +0 -39
  26. package/assets/neural-pendulum/rl-feasibility/sim.py +0 -324
  27. package/assets/neural-pendulum/rl-feasibility/summarize.py +0 -175
  28. package/assets/neural-pendulum/rl-feasibility/task.py +0 -342
  29. package/assets/neural-pendulum/rl-feasibility/test_contract.py +0 -188
  30. package/assets/neural-pendulum/rl-feasibility/train_ppo.py +0 -403
  31. package/assets/neural-pendulum/rl-feasibility/train_sac.py +0 -406
package/ISSUES.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-eval-guard 问题清单与修复记录
2
2
 
3
- > 版本:0.7.4 · 审计基线:本仓库工作树(含 0.7.4 修复与 Neural-Pendulum 修正环境)
3
+ > 版本:0.7.6 · 审计基线:本仓库工作树(含 0.7.6 的 Neural-Pendulum Tier A 评测)
4
4
  > 对照 DSH:0.2.1-alpha.1 源码(并核对 git tag 0.1.2 / 0.1.5 / 0.1.7 / 0.2.0-rc.1 / rc.2 / 0.2.1-alpha.1)
5
5
  > 本文件先记录问题,再记录修复状态。状态:✅ 已修复 / 🟡 部分修复 / ⛔ 保留(有明确原因或需要架构级改动)
6
6
  >
@@ -8,7 +8,9 @@
8
8
  >
9
9
  > **2026-10-23 会话回归修复(未发版)**:修复 Neural-Pendulum 仿真器 Coriolis 项多乘 cos 导致高转速下能量注入并产生 NaN;修正关节 3 在绝对角坐标下的广义力矩映射,由 (tau1,0,tau3) 改为 (tau1,-tau3,tau3);TASK/SPEC/prompt 明确 theta 约定并说明工作区无 PyTorch/numpy;网络 syscall 改为 AF_INET/AF_INET6 的 EPERM 软拒绝,避免 pip/NSS 本地检查被误杀。
10
10
 
11
- > **0.7.4**:把上述修复连同固定物理的 RL 可行性环境一起发布。开发参考环境在 `assets/neural-pendulum/rl-feasibility/`(Python + CPU PyTorch,仅开发参考,不进入候选工作区);其中报告/汇总/曲线已验证 full 36D + base reward + random init 可学、去掉手动差分 da 或 H=1 会失败、PPO/SAC 应交付 best checkpoint。
11
+ > **0.7.6**:Neural-Pendulum 切换为 Tier A 评测:每回合随机化隐藏物理参数、初始状态与观测噪声(无摩擦/滞后/延迟),评测改为 100 个隐藏随机回合的成功率,成功率 >= 0.8 完成;计分改为按成功率归一化。训练仿真器与评测仿真器均实现随机化,工作区仍只交付二进制,不提供仿真源码。
12
+ >
13
+ > **0.7.5**:把 0.7.4 误带的开发用 RL 可行性代码从插件中撤除,只保留修正后的 simulator;同时把 agent 训练仿真器与评测仿真器拆开:`assets/neural-pendulum/sim_agent` 只做随机近向下 reset,评测用 `neural-pendulum-eval/sim_eval` 每个 reset 采样新的隐藏物理参数/初始状态/观测噪声;工作区仍只交付二进制,不提供仿真源码。工作区仍只交付二进制,不提供仿真源码。
12
14
  > **下文仍有若干条目标记为 ✅ 但实际未完成,不要再按旧状态理解**:arena legacy 的 `std::thread` 静态规则未移除;`cifar_cache.py` 解压仍无 filter、缓存路径仍用 `Path.home()/.dsh`;CIFAR 隐藏标签仍以文件形式落盘;FP32“30 分钟整轮”仍是 op 间检查;无 baseline 时评测仍 fail-open。其余任务相关修复按用户要求暂不动。
13
15
 
14
16
  ## 0. 修复状态总览
package/README.md CHANGED
@@ -57,11 +57,11 @@ CIFAR-10 下载缓存固定在 DSH home 下(默认 `~/.dsh/eval-guard/cache/ci
57
57
 
58
58
  所有任务的后台安装被取消或失败时,会删除本次安装已搬入工作区的文件,并清空工作区。清理只会在安装开始前工作区确实为空时执行,原工作区有内容时绝不触发。Neural-CIFAR 还会删除本次安装拥有的 CIFAR 下载锁,但不会删除其他 DSH 实例的锁。
59
59
 
60
- Neural-Pendulum 要求从零实现纯 C++17、仅 CPU、fp32 的神经网络基础设施,并用它训练控制器驱动预编译三节摆从接近全向下起摆、保持全向上 10 秒。后端评测与 Neural-FP32/Neural-CIFAR 使用同一套算子/阈值(单次调用 60 秒)。算子评测全部通过后才驱动 build/control,在固定初始状态上统计连续全向上时间 t_can。评测脚本在独立目录 `dsh-eval-guard/neural-pendulum-eval/`;宿主侧提交与测试工具为 eval_submit / eval_test,工具的注册依赖会话级 run 绑定。
60
+ Neural-Pendulum 要求从零实现纯 C++17、仅 CPU、fp32 的神经网络基础设施,并用它训练控制器驱动预编译三节摆从接近全向下起摆、保持全向上 10 秒。后端评测与 Neural-FP32/Neural-CIFAR 使用同一套算子/阈值(单次调用 60 秒)。算子评测全部通过后才驱动 build/control,在 100 个随机隐藏回合上统计成功率(单回合连续全向上 10 秒记成功)。评测脚本在独立目录 `dsh-eval-guard/neural-pendulum-eval/`;宿主侧提交与测试工具为 eval_submit / eval_test,工具的注册依赖会话级 run 绑定。
61
61
 
62
- **开发参考环境**:修正后的 RL 可行性环境(Python + CPU PyTorch,仅开发参考,不进入候选工作区、不是提交依赖)放在 `assets/neural-pendulum/rl-feasibility/`。其 `sim.py` 与评测用 `sim_eval` 使用相同物理和物理关节力矩映射 `(tau1, -tau3, tau3)`;报告、汇总表和曲线在 `RESULTS.md` / `results/` 中。15 组实验确认:full 36D(cos/sin + 手动差分 da/5,H=4)+ base reward + random init 可学;去掉 da 或把 H 缩到 1 会失败;PPO/SAC 都应交付 best checkpoint。
62
+ **仿真器分离**:工作区 `tools/sim` 是只接受随机近向下 reset 的**训练/自测**仿真器,不包含固定评测初态;评测使用独立 `neural-pendulum-eval/sim_eval`,每个 reset 采样新的隐藏物理参数、初始状态和观测噪声。工作区不提供仿真器源码或评测种子。
63
63
 
64
- **分数**:eval_test 返回 stage / t_can / completed / error;时间积分 `final_score` 写入宿主侧事件与完成标记(`~/.dsh/eval-guard/neural-pendulum/<workspace>/done`),用于赛后排行,不返回给 agent。分数在**测试请求时刻**切换,当前测试结果也计入本次积分;宿主按事件重新计算最终值,而不是使用“评测完成时刻”的旧值。起算时间取首次提交写入的 meta.json。
64
+ **分数**:eval_test 返回 stage / t_can / success_rate / episodes / completed / error;时间积分 `final_score` 写入宿主侧事件与完成标记(`~/.dsh/eval-guard/neural-pendulum/<workspace>/done`),用于赛后排行,不返回给 agent。分数在**测试请求时刻**切换,当前测试结果也计入本次积分;宿主按事件重新计算最终值,而不是使用“评测完成时刻”的旧值。起算时间取首次提交写入的 meta.json。
65
65
 
66
66
  评测时在临时目录独立构建,然后从 assets/neural-cpp/hidden/ 注入 C++ 测试源码;隐藏测试在候选构建**开始前**读入内存,构建完成后再落盘编译,测试数据由固定种子在进程内生成,不依赖或复制外部数据文件。
67
67
 
@@ -1,11 +1,9 @@
1
- # Neural-Pendulum 任务规格 v1.4
1
+ # Neural-Pendulum 任务规格 v1.5
2
2
 
3
3
  本文件是已确定的任务内部规格。实现、评测器、workspace 素材以此为准。
4
4
  后续除非明确修改规格,否则不再重新讨论这些规则。
5
5
 
6
- v1.4 变更:后端基础设施评测与 Neural-FP32 / Neural-CIFAR 统一为“固定 CLI +
7
- PyTorch 参考值 + 加权平均相对误差阈值”,不再使用 include/nn/nn.hpp 头文件契约和
8
- 隐藏 C++ 基础设施测试。
6
+ v1.5 变更:Tier A 控制任务。每回合随机化隐藏物理参数、初始状态与观测噪声;评测改为 100 个隐藏随机回合上的成功率,成功率 >= 0.8 完成。基础设施评测协议不变。
9
7
 
10
8
  ## 1. 任务标识
11
9
 
@@ -21,7 +19,7 @@ agent 必须:
21
19
 
22
20
  1. 从零实现一套神经网络基础设施(张量、算子、反向传播、优化器、线程池;控制器权重的打包方式自定,评测不要求特定序列化格式)。
23
21
  2. 使用该基础设施训练一个神经网络控制器。
24
- 3. 控制预编译双驱动三节摆:第一、第三关节施加有限力矩,中间关节被动,从全向下起摆,并保持三根杆全部向上至少 t_tgt = 10.0 s。
22
+ 3. 控制预编译双驱动三节摆:第一、第三关节施加有限力矩,中间关节被动,从全向下起摆,并保持三根杆全部向上至少 t_tgt = 10.0 s;评测在 100 个随机隐藏回合上统计成功率,成功率 >= 0.8 完成。
25
23
  4. 保证基础设施通过 `build/nn_task` 固定 CLI 的数值评测:评测侧用 PyTorch 在相同数据上计算参考值,逐操作比较加权平均相对误差并对照阈值。
26
24
  5. 提交的源码中不得出现 torch;候选运行时不提供 Python/torch。
27
25
 
@@ -39,16 +37,16 @@ agent 必须:
39
37
  - 每个控制周期内部用 4 个 RK4 子步
40
38
  - 最大仿真时长: 30.0 s
41
39
 
42
- 固定物理参数(环境内部隐藏,不向 agent 暴露):
40
+ 每回合隐藏随机物理参数(不向 agent 暴露具体值):
43
41
 
44
42
  ~~~
45
- m = [1.0, 1.0, 1.0] kg
46
- l = [0.5, 0.5, 0.5] m
47
- r = [0.25, 0.25, 0.25] m
48
- I = m * l^2 / 12
43
+ m_i ~ Uniform(0.7, 1.3) kg
44
+ l_i ~ Uniform(0.45, 0.55) m
45
+ r_i = l_i / 2
46
+ I_i = m_i * l_i^2 / 12
49
47
  g = 9.81 m/s^2
50
- joint damping = 0.01
51
- tau_max = 18.0 N m(每个驱动关节独立限制)
48
+ joint damping ~ Uniform(0.005, 0.02)
49
+ tau_max ~ Uniform(15.0, 21.0) N m(每个驱动关节独立限制)
52
50
  ~~~
53
51
 
54
52
  角度约定:
@@ -64,27 +62,16 @@ x_tip = sum_i l_i * sin(theta_i)
64
62
  y_tip = -sum_i l_i * cos(theta_i)
65
63
  ~~~
66
64
 
67
- ## 4. 初始状态
65
+ ## 4. 初始状态与随机化
68
66
 
69
- 训练仿真环境每次 reset 不加固定偏差,只加噪声:
67
+ 训练和评测的每个回合都重新采样隐藏物理参数、初始状态和观测噪声:
70
68
 
71
69
  ~~~
72
- theta_i ~ Uniform(-0.05, 0.05) rad i = 1,2,3,独立采样
73
- omega_i ~ Uniform(-0.02, 0.02) rad/s i = 1,2,3,独立采样
70
+ theta_i ~ Uniform(-0.15, 0.15) rad i = 1,2,3,独立采样
71
+ omega_i ~ Uniform(-0.2, 0.2) rad/s i = 1,2,3,独立采样
74
72
  ~~~
75
73
 
76
- 即三根杆接近全向下,初始角度以全向下为中心随机扰动。
77
-
78
- 评测环境使用固定初始状态:
79
-
80
- ~~~
81
- theta = [0.013, -0.008, 0.021] rad
82
- omega = [0, 0, 0]
83
- ~~~
84
-
85
- 三个固定角互不相同,且绝对值互不相同。
86
-
87
- 评测期间不加入任何随机扰动。
74
+ 单回合内部参数不再改变;每次 reset 开始新回合。评测初始状态与训练同分布,但评测种子和回合序列不暴露给 agent。
88
75
 
89
76
  ## 5. Agent 观测
90
77
 
@@ -100,6 +87,7 @@ obs = [a1, a2, a3] (rad)
100
87
  - theta_i 为绝对角度
101
88
  - a_i 为相对竖直向上的角度误差
102
89
  - 不提供角速度
90
+ - 每一步返回的三个角度叠加随机观测噪声;噪声标准差每回合独立采样自 Uniform(0.002, 0.008) rad
103
91
  - 不提供第三杆末端位置
104
92
  - 不提供杆长/质量/惯量/真实物理参数
105
93
 
@@ -131,7 +119,9 @@ score = -1
131
119
 
132
120
  ## 7. 训练仿真接口
133
121
 
134
- 工作区提供预编译 tools/sim,不提供源码和内部参数。
122
+ 工作区提供预编译 tools/sim,不提供源码和内部参数。
123
+ `tools/sim` 是**训练/自测专用**:每次 reset 随机化隐藏物理参数、初始状态与观测噪声,不包含评测种子或固定评测初态。
124
+ 评测使用独立程序 sim_eval;它每次 reset 采样新的隐藏物理参数、初始状态和观测噪声,并在 100 个隐藏随机回合上统计成功率。工作区不包含 sim_eval,也不包含评测种子或回合序列。
135
125
  环境只提供状态转移,不提供 reward、loss、cost 或 done 信号。训练信号由 agent 自行构造。
136
126
 
137
127
  优化目标:
@@ -139,7 +129,7 @@ score = -1
139
129
  ~~~
140
130
  从接近全向下的初始状态起摆
141
131
  使三根杆全部进入竖直向上容差并尽可能长时间保持
142
- 评测指标为 t_can
132
+ 评测指标为 100 个隐藏随机回合上的成功率(单回合 t_can >= 10 s 记成功)
143
133
  ~~~
144
134
 
145
135
  不规定损失函数、奖励函数、优化器或训练算法。
@@ -167,7 +157,7 @@ JSONL 协议:
167
157
  {"error":"episode_finished"}
168
158
  ~~~
169
159
 
170
- 此时不会返回新的观测,也不会继续推进仿真。
160
+ 此时不会返回新的观测,也不会继续推进仿真。
171
161
  必须发送 reset 才能开始新 episode;reset 之前的所有 step 都返回该错误。
172
162
 
173
163
  ## 8. 交付源码与构建
@@ -257,26 +247,30 @@ dt = 0.01 s
257
247
  最大仿真 = 30.0 s
258
248
  t_tgt = 10.0 s
259
249
  全向上容差 = 每根杆与竖直方向夹角 <= 0.05 rad
250
+ 评测回合数 = 100
251
+ 完成成功率阈值 = 0.8
260
252
  ~~~
261
253
 
262
- t_can 定义:
254
+ 单回合 t_can 定义:
263
255
 
264
256
  ~~~
265
257
  从 reset 开始,三根杆全部处于容差内的最长连续时间
266
258
  从未出现全向上则 t_can = 0
259
+ 单回合成功 = (t_can >= t_tgt)
267
260
  ~~~
268
261
 
269
- 计分用的截断:
262
+ 100 回合统计:
270
263
 
271
264
  ~~~
272
- capped_t_can = min(t_can, t_tgt)
265
+ success_rate = 成功回合数 / 100
266
+ t_can = 100 回合 t_can 的平均值(仅用于报告)
273
267
  ~~~
274
268
 
275
269
  完成条件:
276
270
 
277
271
  ~~~
278
272
  infra_ok == true(全部算子可执行且误差不超阈值)
279
- 且 t_can >= t_tgt
273
+ 且 success_rate >= 0.8
280
274
  ~~~
281
275
 
282
276
  ## 10. 提交与测试事件
@@ -315,8 +309,8 @@ compile CMake 构建失败,或未产出 build/nn_task / build/control
315
309
  infra build/nn_task 算子数值评测失败(不可执行/崩溃/超时/输出缺失/误差超阈值)
316
310
  controller 控制器启动/协议/崩溃/超时/非法输出
317
311
  simulation 仿真异常/NaN/仿真超时
318
- scoring 正常测试但 t_can < t_tgt
319
- completed t_can >= t_tgt,任务完成
312
+ scoring 正常测试但 success_rate < 0.8
313
+ completed success_rate >= 0.8,任务完成
320
314
  ~~~
321
315
 
322
316
  ### 返回给 agent 的测试结果
@@ -325,13 +319,15 @@ completed t_can >= t_tgt,任务完成
325
319
  {
326
320
  "ok": true,
327
321
  "stage": "scoring",
328
- "t_can": 4.2,
322
+ "t_can": 8.1,
323
+ "success_rate": 0.62,
324
+ "episodes": 100,
329
325
  "completed": false,
330
326
  "error": ""
331
327
  }
332
328
  ~~~
333
329
 
334
- 返回 stage、t_can、completed,不返回最终分数。
330
+ 返回 stage、t_can、success_rate、episodes、completed,不返回最终分数。
335
331
 
336
332
  ## 11. 计分
337
333
 
@@ -348,7 +344,7 @@ score = -1
348
344
  有效测试:
349
345
 
350
346
  ~~~
351
- score = (min(t_can, t_tgt) - t_tgt) / t_tgt
347
+ score = (min(success_rate, 0.8) - 0.8) / 0.8
352
348
  ~~~
353
349
 
354
350
  范围:
@@ -362,7 +358,7 @@ score = (min(t_can, t_tgt) - t_tgt) / t_tgt
362
358
  - 提交事件不改变 score
363
359
  - 只有测试请求改变 score
364
360
  - 测试失败:从该测试请求时刻起 score = -1
365
- - 测试成功:从该测试请求时刻起按 t_can 计分
361
+ - 测试成功:从该测试请求时刻起按 success_rate 计分
366
362
  - 第一次测试请求之前:score = -1
367
363
  - 下一次测试请求之前,score 保持不变
368
364
 
@@ -384,7 +380,7 @@ T_done 是第一次返回 stage = completed 的测试请求时间。
384
380
 
385
381
  ~~~
386
382
  stage = completed
387
- t_can >= t_tgt
383
+ success_rate >= 0.8
388
384
  infra_ok = true
389
385
  ~~~
390
386
 
@@ -429,6 +425,8 @@ stage
429
425
  ok
430
426
  infra_ok
431
427
  t_can
428
+ success_rate
429
+ episodes
432
430
  score
433
431
  completed
434
432
  ~~~
@@ -6,8 +6,9 @@
6
6
 
7
7
  目标:
8
8
 
9
- - 训练时从随机小扰动、评测时从固定初始状态起摆
10
- - 保持三根杆全部向上至少 t_tgt = 10.0 秒
9
+ - 训练与评测的每个回合都随机化物理参数、初始状态与观测噪声
10
+ - 单回合保持三根杆全部向上至少 t_tgt = 10.0 秒
11
+ - 评测在 100 个隐藏随机回合上统计成功率
11
12
  - 同时保证神经网络基础设施通过 build/nn_task 固定 CLI 的数值评测
12
13
 
13
14
  评测侧会用 PyTorch 计算算子参考值,但**工作区/候选运行时不提供 PyTorch 或 numpy**,也没有网络,无法 pip 安装。开发阶段请用 C++,或 Python 标准库/独立 double 实现做数值对照;最终提交的基础设施和控制器源码必须是纯 C++,不得出现 torch。
@@ -22,22 +23,18 @@ tools/sim
22
23
 
23
24
  不提供仿真器源码和物理参数。
24
25
 
25
- ### 初始状态
26
+ 工具说明:工作区的 `tools/sim` 是训练/自测专用仿真器,每次 `reset` 都会随机化物理参数、初始状态和观测噪声;它不是评测器,也没有隐藏评测种子。评测使用工作区之外的独立仿真程序,在 100 个隐藏随机回合上统计成功率。
26
27
 
27
- 训练时每次 reset:
28
+ ### 初始状态与随机化
28
29
 
29
- - 三根杆接近全向下
30
- - 角度以全向下为中心,只加小范围随机噪声
31
- - 不添加固定角度偏差
32
- - 带小范围随机初始角速度
30
+ 训练和评测的每个回合:
33
31
 
34
- 评测时:
32
+ - 三根杆接近全向下:theta_i ~ Uniform(-0.15, 0.15) rad
33
+ - 角速度:omega_i ~ Uniform(-0.2, 0.2) rad/s
34
+ - 物理参数(质量、长度、关节阻尼、力矩上限)每回合独立随机采样
35
+ - 观测噪声强度每回合独立随机采样
35
36
 
36
- - 使用固定初始状态
37
- - 三个固定角度互不相同
38
- - 三个固定角度的绝对值也互不相同
39
- - 初始角速度为 0
40
- - 不加任何随机扰动
37
+ 单回合内部参数不再改变;每次 reset 开始一个新回合。
41
38
 
42
39
  ### 驱动结构
43
40
 
@@ -64,6 +61,7 @@ obs = [a1, a2, a3] (rad)
64
61
  - theta_i = 0 表示竖直向下,theta_i = pi 表示竖直向上
65
62
  - a_i 是相对竖直向上的角度误差
66
63
  - 不提供角速度
64
+ - 每一步返回的三个角度都叠加随机观测噪声,噪声强度每回合随机
67
65
  - 不提供第三杆末端位置
68
66
  - 不提供杆长、质量、惯量等物理参数
69
67
 
@@ -83,26 +81,25 @@ obs = [a1, a2, a3] (rad)
83
81
 
84
82
  ### 控制与仿真
85
83
 
86
- ~~~
87
84
  控制频率 = 100 Hz
88
85
  控制周期 = 0.01 s
89
86
  最大仿真时间 = 30.0 s
90
87
  t_tgt = 10.0 s
91
88
  全向上容差 = 每根杆与竖直方向夹角 <= 0.05 rad
92
- ~~~
93
89
 
94
- 评测会计算:
90
+ 每个评测回合计算:
95
91
 
96
- ~~~
97
92
  t_can = 三根杆同时处于全向上容差内的最长连续时间
98
- ~~~
93
+ 单回合成功 = (t_can >= t_tgt)
94
+
95
+ 评测使用 100 个隐藏随机回合:
96
+
97
+ success_rate = 成功回合数 / 100
99
98
 
100
99
  当:
101
100
 
102
- ~~~
103
- t_can >= t_tgt
101
+ success_rate >= 0.8
104
102
  且神经网络基础设施数值评测全部通过
105
- ~~~
106
103
 
107
104
  任务完成,计时停止。
108
105
 
@@ -134,7 +131,7 @@ tools/sim 使用 JSONL 协议。
134
131
  此时不返回新观测,也不会继续推进仿真。
135
132
  必须 reset 后才能开始新 episode;reset 前的 step 都返回该错误。
136
133
 
137
- 可以反复 reset / step 进行数据采集、模型训练和自测。
134
+ 可以反复 reset / step 进行数据采集、模型训练和自测。该程序只做随机训练 reset,不包含隐藏评测种子;评测器是独立程序。
138
135
 
139
136
  环境只返回状态,不提供 reward、loss、cost 或 done 信号。
140
137
  训练信号由你自行构造。
@@ -334,6 +331,8 @@ eval_test
334
331
  ~~~text
335
332
  stage
336
333
  t_can
334
+ success_rate
335
+ episodes
337
336
  completed
338
337
  error
339
338
  ~~~
@@ -359,8 +358,8 @@ completed
359
358
  - infra:build/nn_task 算子数值评测失败(不可执行、崩溃、超时、输出缺失或误差超阈值)
360
359
  - controller:控制器启动、协议、崩溃、超时或输出非法
361
360
  - simulation:隐藏仿真推进异常、NaN 或超时
362
- - scoring:测试有效,但 `t_can < 10.0`
363
- - completed:基础设施和仿真均通过,且 `t_can >= 10.0`
361
+ - scoring:测试有效,但 `success_rate < 0.8`
362
+ - completed:基础设施和仿真均通过,且 `success_rate >= 0.8`
364
363
 
365
364
  最终排行分数不会返回给你,只用于赛后排名。
366
365
 
@@ -370,7 +369,7 @@ completed
370
369
 
371
370
  ~~~
372
371
  stage = completed
373
- t_can >= 10.0
372
+ success_rate >= 0.8
374
373
  ~~~
375
374
 
376
375
  即任务完成。
@@ -407,22 +406,17 @@ t_can >= 10.0
407
406
 
408
407
  只有申请测试会更新当前分数:
409
408
 
410
- ~~~
411
409
  无效测试 / 编译失败 / 基础设施失败 / 控制器失败 / 仿真失败 / 无提交:
412
410
  score(t) = -1
413
411
 
414
412
  有效测试:
415
- score(t) = (min(t_can, t_tgt) - t_tgt) / t_tgt
416
- ~~~
413
+ score(t) = (min(success_rate, 0.8) - 0.8) / 0.8
417
414
 
418
415
  其中:
419
416
 
420
- ~~~
421
- t_tgt = 10.0 s
422
- score(t) 的范围是 [-1, 0]
423
- t_can = 0 时 score = -1
424
- t_can >= t_tgt 时 score = 0
425
- ~~~
417
+ success_rate 的范围是 [0, 1]
418
+ success_rate = 0 时 score = -1
419
+ success_rate >= 0.8 时 score = 0
426
420
 
427
421
  分数从测试请求时刻开始生效,直到下一次测试请求。
428
422
 
@@ -432,20 +426,11 @@ t_can >= t_tgt 时 score = 0
432
426
 
433
427
  最终分数是时间积分,不除以总时长:
434
428
 
435
- ~~~
436
429
  final = integral_0^T_done score(t) dt
437
- ~~~
438
430
 
439
431
  T_done 是第一次返回 completed 的测试请求时间。
440
432
 
441
- 因此:
442
-
443
- - 越早达到完成条件,负分区间越短,最终分数越高
444
- - 拖得越久,负分区间越长
445
- - 完成后停止积分
446
- - 未完成不参与最终排名
447
-
448
- ### 完成时
433
+ ## 完成时
449
434
 
450
435
  当测试返回 completed 时:
451
436
 
@@ -459,7 +444,9 @@ T_done 是第一次返回 completed 的测试请求时间。
459
444
  测试结果会返回:
460
445
 
461
446
  - stage
462
- - t_can
447
+ - t_can(100 回合平均保持时间,仅用于报告)
448
+ - success_rate(100 个随机隐藏回合的成功率)
449
+ - episodes
463
450
  - completed
464
451
  - error
465
452
 
@@ -471,5 +458,5 @@ T_done 是第一次返回 completed 的测试请求时间。
471
458
  2. 先实现最小可用的 tensor/算子/反向/优化器/线程池,并让 build/nn_task 跑通
472
459
  3. 用独立 double 标量参考、有限差分梯度检查逐算子核对误差,边界形状/广播/groups/stride/dilation 都要覆盖(工作区没有 PyTorch/numpy)
473
460
  4. 用 tools/sim 采集数据并训练控制器,导出权重供 build/control 使用
474
- 5. 反复提交和测试,优先把 t_can 提升到 10 秒
461
+ 5. 反复提交和测试,优先把 100 回合成功率提升到 80%
475
462
  6. 完成后立即总结
@@ -2,6 +2,7 @@
2
2
  #include <array>
3
3
  #include <cmath>
4
4
  #include <cstdio>
5
+ #include <cstdlib>
5
6
  #include <cstring>
6
7
  #include <iostream>
7
8
  #include <random>
@@ -16,17 +17,17 @@ constexpr int SUB = 4;
16
17
  constexpr int MAX_STEPS = 3000;
17
18
  constexpr double PI = 3.14159265358979323846;
18
19
  constexpr double GRAV = 9.81;
19
- constexpr double DAMP = 0.01;
20
- constexpr double TAU_MAX = 18.0;
21
- const std::array<double, N> MASS{{1.0, 1.0, 1.0}};
22
- const std::array<double, N> LEN{{0.5, 0.5, 0.5}};
23
- const std::array<double, N> R{{0.25, 0.25, 0.25}};
24
- const std::array<double, N> INERTIA{{MASS[0]*LEN[0]*LEN[0]/12.0, MASS[1]*LEN[1]*LEN[1]/12.0, MASS[2]*LEN[2]*LEN[2]/12.0}};
25
- const std::array<double, N> GCOEF{{MASS[0]*R[0] + LEN[0]*(MASS[1]+MASS[2]),
26
- MASS[1]*R[1] + LEN[1]*MASS[2],
27
- MASS[2]*R[2]}};
28
20
 
29
- struct Vec { std::array<double,N> v{}; };
21
+ struct Params {
22
+ std::array<double, N> mass{};
23
+ std::array<double, N> len{};
24
+ std::array<double, N> rad{};
25
+ std::array<double, N> inertia{};
26
+ std::array<double, N> gcoef{};
27
+ double damp = 0.01;
28
+ double tau_max = 18.0;
29
+ };
30
+
30
31
  struct State { std::array<double,N> theta{}; std::array<double,N> omega{}; };
31
32
 
32
33
  double wrap(double x) {
@@ -35,15 +36,37 @@ double wrap(double x) {
35
36
  return x;
36
37
  }
37
38
 
38
- static std::array<std::array<double,N>,N> mass_matrix(const std::array<double,N>& q) {
39
+ Params sample_params(std::mt19937& rng) {
40
+ std::uniform_real_distribution<double> um(0.7, 1.3);
41
+ std::uniform_real_distribution<double> ul(0.45, 0.55);
42
+ std::uniform_real_distribution<double> ud(0.005, 0.02);
43
+ std::uniform_real_distribution<double> ut(15.0, 21.0);
44
+ Params p;
45
+ for (int i=0;i<N;i++) {
46
+ p.mass[i] = um(rng);
47
+ p.len[i] = ul(rng);
48
+ p.rad[i] = p.len[i] * 0.5;
49
+ p.inertia[i] = p.mass[i]*p.len[i]*p.len[i]/12.0;
50
+ }
51
+ p.gcoef[0] = p.mass[0]*p.rad[0] + p.len[0]*(p.mass[1]+p.mass[2]);
52
+ p.gcoef[1] = p.mass[1]*p.rad[1] + p.len[1]*p.mass[2];
53
+ p.gcoef[2] = p.mass[2]*p.rad[2];
54
+ p.damp = ud(rng);
55
+ p.tau_max = ut(rng);
56
+ return p;
57
+ }
58
+
59
+ static std::array<std::array<double,N>,N> mass_matrix(const std::array<double,N>& q,
60
+ const Params& p) {
39
61
  std::array<std::array<double,N>,N> M{};
40
62
  for (int i=0;i<N;i++) for (int j=0;j<N;j++) {
41
63
  if (i==j) {
42
- double tail=0; for (int k=i+1;k<N;k++) tail += MASS[k];
43
- M[i][j] = INERTIA[i] + MASS[i]*R[i]*R[i] + LEN[i]*LEN[i]*tail;
64
+ double tail=0; for (int k=i+1;k<N;k++) tail += p.mass[k];
65
+ M[i][j] = p.inertia[i] + p.mass[i]*p.rad[i]*p.rad[i]
66
+ + p.len[i]*p.len[i]*tail;
44
67
  } else {
45
68
  int a=std::min(i,j), b=std::max(i,j);
46
- M[i][j] = LEN[a] * GCOEF[b] * std::cos(q[i]-q[j]);
69
+ M[i][j] = p.len[a] * p.gcoef[b] * std::cos(q[i]-q[j]);
47
70
  }
48
71
  }
49
72
  return M;
@@ -60,27 +83,24 @@ static void solve3(const std::array<std::array<double,N>,N>& A, const std::array
60
83
  }
61
84
  for(int i=0;i<3;i++) x[i]=a[i][3];
62
85
  }
63
- static void deriv(const State& s, const std::array<double,N>& tau, State& out) {
64
- auto M = mass_matrix(s.theta);
86
+ static void deriv(const State& s, const std::array<double,N>& tau,
87
+ const Params& p, State& out) {
88
+ auto M = mass_matrix(s.theta, p);
65
89
  std::array<double,N> C{}, G{}, Q{}, rhs{};
66
90
  for(int i=0;i<N;i++){
67
91
  double ci=0;
68
92
  for(int j=0;j<N;j++) if(j!=i) {
69
- // Coriolis coefficient: d(M_ij)/d(theta_j) = LEN[min] * GCOEF[max] * sin(...);
70
- // M_ij itself already contains cos(...), so multiplying M_ij by sin would
71
- // add an extra cos factor and inject energy at high speed (NaN after ~7 s).
72
93
  const int a=std::min(i,j), b=std::max(i,j);
73
- ci += LEN[a] * GCOEF[b] * std::sin(s.theta[i]-s.theta[j]) * s.omega[j]*s.omega[j];
94
+ ci += p.len[a] * p.gcoef[b] * std::sin(s.theta[i]-s.theta[j]) * s.omega[j]*s.omega[j];
74
95
  }
75
96
  C[i]=ci;
76
- G[i]=GRAV*std::sin(s.theta[i])*GCOEF[i];
97
+ G[i]=GRAV*std::sin(s.theta[i])*p.gcoef[i];
77
98
  }
78
- // joint damping: b * D^T D omega, D lower bidiagonal with -1 previous
79
99
  std::array<double,N> edge{};
80
100
  for(int i=0;i<N;i++) edge[i] = s.omega[i] - (i>0?s.omega[i-1]:0.0);
81
101
  for(int i=0;i<N;i++){
82
102
  double e = edge[i] - (i+1<N ? edge[i+1] : 0.0);
83
- Q[i] = -DAMP*e;
103
+ Q[i] = -p.damp*e;
84
104
  }
85
105
  for(int i=0;i<N;i++) rhs[i]=tau[i]-C[i]-G[i]+Q[i];
86
106
  solve3(M, rhs, out.omega);
@@ -89,48 +109,80 @@ static void deriv(const State& s, const std::array<double,N>& tau, State& out) {
89
109
  } // namespace
90
110
 
91
111
  int main(int argc, char** argv) {
92
- bool eval=false; int seed=0;
93
- for(int i=1;i<argc;i++){ std::string a=argv[i]; if(a=="--eval") eval=true; else if(a=="--seed" && i+1<argc) seed=std::atoi(argv[++i]); }
112
+ #ifdef DSH_EVAL_SIM
113
+ /* Evaluator-only binary. Every reset samples a fresh hidden plant and a
114
+ * fresh hidden observation-noise level from a deterministic internal RNG.
115
+ * It also reports the true joint state for the evaluator's success check;
116
+ * the candidate controller only receives the noisy observation. */
117
+ uint32_t seed = 20261024u;
118
+ std::mt19937 rng(seed);
119
+ #else
120
+ /* Agent/training binary. Every reset samples a fresh hidden plant, random
121
+ * near-down initial state and fresh observation-noise level. */
122
+ uint32_t seed=0;
123
+ for(int i=1;i<argc;i++){
124
+ std::string a=argv[i];
125
+ if(a=="--seed" && i+1<argc) seed=static_cast<uint32_t>(std::strtoul(argv[++i],nullptr,10));
126
+ }
94
127
  std::mt19937 rng(seed);
95
- std::uniform_real_distribution<double> u(-1.0,1.0);
128
+ #endif
129
+ std::uniform_real_distribution<double> uinit(-0.15, 0.15);
130
+ std::uniform_real_distribution<double> uvel(-0.2, 0.2);
131
+ std::uniform_real_distribution<double> unoise(0.002, 0.008);
132
+ std::normal_distribution<double> gauss(0.0, 1.0);
133
+
134
+ Params p = sample_params(rng);
96
135
  State s;
136
+ double noise_std = 0.0;
97
137
  int steps=0; bool ready=false;
98
- auto reset=[&](){ steps=0; ready=true; if(eval){ s.theta={0.013,-0.008,0.021}; s.omega={0,0,0}; } else { s.theta={u(rng)*0.05,u(rng)*0.05,u(rng)*0.05}; s.omega={u(rng)*0.02,u(rng)*0.02,u(rng)*0.02}; } };
99
- // zero state
138
+
139
+ auto reset=[&](){
140
+ steps=0; ready=true;
141
+ p = sample_params(rng);
142
+ noise_std = unoise(rng);
143
+ for (int i=0;i<N;i++) {
144
+ s.theta[i] = uinit(rng);
145
+ s.omega[i] = uvel(rng);
146
+ }
147
+ };
148
+ auto noisy_obs=[&](){
149
+ std::array<double,N> o{};
150
+ for (int i=0;i<N;i++) o[i] = wrap(s.theta[i]-PI) + gauss(rng)*noise_std;
151
+ return o;
152
+ };
153
+ auto emit_state=[&](){
154
+ std::cout << "{\"obs\":[" << noisy_obs()[0] << "," << noisy_obs()[1] << "," << noisy_obs()[2] << "]";
155
+ #ifdef DSH_EVAL_SIM
156
+ std::cout << ",\"state\":[" << wrap(s.theta[0]-PI) << "," << wrap(s.theta[1]-PI) << "," << wrap(s.theta[2]-PI) << "]";
157
+ #endif
158
+ std::cout << "}\n" << std::flush;
159
+ };
160
+
100
161
  s.theta={0,0,0}; s.omega={0,0,0};
101
162
  std::string line;
102
163
  while(std::getline(std::cin,line)){
103
164
  if(line.empty()) continue;
104
- if(line.find("reset")!=std::string::npos){ reset(); std::cout << "{\"obs\":[" << wrap(s.theta[0]-PI) << "," << wrap(s.theta[1]-PI) << "," << wrap(s.theta[2]-PI) << "]}\n" << std::flush; continue; }
165
+ if(line.find("reset")!=std::string::npos){ reset(); emit_state(); continue; }
105
166
  if(line.find("step")!=std::string::npos){
106
167
  if(!ready || steps>=MAX_STEPS){ std::cout << "{\"error\":\"episode_finished\"}\n" << std::flush; continue; }
107
- // parse torque array
108
- double u1=0,u3=0; auto p=line.find('['); if(p!=std::string::npos){ char* end=nullptr; u1=std::strtod(line.c_str()+p+1,&end); if(end){ const char* q=end; while(*q && (*q==','||*q==' ')) q++; u3=std::strtod(q,&end); } }
168
+ double u1=0,u3=0; auto pos=line.find('['); if(pos!=std::string::npos){ char* end=nullptr; u1=std::strtod(line.c_str()+pos+1,&end); if(end){ const char* q=end; while(*q && (*q==','||*q==' ')) q++; u3=std::strtod(q,&end); } }
109
169
  u1=std::max(-1.0,std::min(1.0,u1)); u3=std::max(-1.0,std::min(1.0,u3));
110
- // Joint 3 is an internal motor between link 2 and link 3. In absolute
111
- // coordinates its generalized force is (tau1, -tau3, tau3): the reaction
112
- // on link 2 is part of the same joint torque. (tau1, 0, tau3) would be a
113
- // reactionless external torque on link 3 and would contradict joint 2
114
- // being passive.
115
- std::array<double,N> tau{{u1*TAU_MAX,-u3*TAU_MAX,u3*TAU_MAX}};
116
- // one control step with 4 RK4 substeps
170
+ std::array<double,N> tau{{u1*p.tau_max,-u3*p.tau_max,u3*p.tau_max}};
117
171
  double h=DT/SUB;
118
172
  for(int sub=0;sub<SUB;sub++){
119
173
  State k1=s,k2=s,k3=s,k4=s; State d{};
120
- deriv(k1,tau,d); for(int i=0;i<N;i++){ k2.theta[i]=s.theta[i]+0.5*h*d.theta[i]; k2.omega[i]=s.omega[i]+0.5*h*d.omega[i]; }
121
- deriv(k2,tau,d); for(int i=0;i<N;i++){ k3.theta[i]=s.theta[i]+0.5*h*d.theta[i]; k3.omega[i]=s.omega[i]+0.5*h*d.omega[i]; }
122
- deriv(k3,tau,d); for(int i=0;i<N;i++){ k4.theta[i]=s.theta[i]+h*d.theta[i]; k4.omega[i]=s.omega[i]+h*d.omega[i]; }
123
- deriv(k4,tau,d);
124
- // need derivatives at all; use helper recompute arrays
174
+ deriv(k1,tau,p,d); for(int i=0;i<N;i++){ k2.theta[i]=s.theta[i]+0.5*h*d.theta[i]; k2.omega[i]=s.omega[i]+0.5*h*d.omega[i]; }
175
+ deriv(k2,tau,p,d); for(int i=0;i<N;i++){ k3.theta[i]=s.theta[i]+0.5*h*d.theta[i]; k3.omega[i]=s.omega[i]+0.5*h*d.omega[i]; }
176
+ deriv(k3,tau,p,d); for(int i=0;i<N;i++){ k4.theta[i]=s.theta[i]+h*d.theta[i]; k4.omega[i]=s.omega[i]+h*d.omega[i]; }
125
177
  State ds1{},ds2{},ds3{},ds4{};
126
- deriv(k1,tau,ds1); deriv(k2,tau,ds2); deriv(k3,tau,ds3); deriv(k4,tau,ds4);
178
+ deriv(k1,tau,p,ds1); deriv(k2,tau,p,ds2); deriv(k3,tau,p,ds3); deriv(k4,tau,p,ds4);
127
179
  for(int i=0;i<N;i++){
128
180
  s.theta[i] += h/6.0*(ds1.theta[i]+2*ds2.theta[i]+2*ds3.theta[i]+ds4.theta[i]);
129
181
  s.omega[i] += h/6.0*(ds1.omega[i]+2*ds2.omega[i]+2*ds3.omega[i]+ds4.omega[i]);
130
182
  }
131
183
  }
132
184
  steps++;
133
- std::cout << "{\"obs\":[" << wrap(s.theta[0]-PI) << "," << wrap(s.theta[1]-PI) << "," << wrap(s.theta[2]-PI) << "]}\n" << std::flush;
185
+ emit_state();
134
186
  continue;
135
187
  }
136
188
  std::cout << "{\"error\":\"bad_command\"}\n" << std::flush;
Binary file
Binary file