dsh-eval-guard 0.7.5 → 0.7.7
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.
- package/ISSUES.md +6 -2
- package/README.md +3 -3
- package/assets/neural-pendulum/SPEC.md +40 -44
- package/assets/neural-pendulum/TASK.md +40 -49
- package/assets/neural-pendulum/sim.cpp +86 -56
- package/assets/neural-pendulum/sim_agent +0 -0
- package/assets/neural-pendulum/sim_eval +0 -0
- package/assets/neural-pendulum/workspace.zip +0 -0
- package/lib/index.js +33 -10
- package/neural-pendulum-eval/README.md +3 -4
- package/neural-pendulum-eval/run_eval.py +132 -71
- package/neural-pendulum-eval/sim_eval +0 -0
- package/package.json +1 -1
package/ISSUES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-eval-guard 问题清单与修复记录
|
|
2
2
|
|
|
3
|
-
> 版本:0.7.
|
|
3
|
+
> 版本:0.7.7 · 审计基线:本仓库工作树(含 0.7.7 的 Neural-Pendulum 0.90 阈值与审核包)
|
|
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,11 @@
|
|
|
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.
|
|
11
|
+
> **0.7.7**:Neural-Pendulum 完成阈值提高到 0.90;prompt/TASK 声明任务已验证可完成、未 completed 前禁止停止;最终 build/control 必须由神经网络输出层直接产生力矩,最终控制程序不得含显式控制律;禁止逆向已编译文件或读取进程内存。另新增审核人员专用包 neural-pendulum-audit(不进入 workspace/npm 任务资产)。
|
|
12
|
+
>
|
|
13
|
+
> **0.7.6**:Neural-Pendulum 切换为 Tier A 评测:每回合随机化隐藏物理参数、初始状态与观测噪声(无摩擦/滞后/延迟),评测改为 100 个隐藏随机回合的成功率,成功率 >= 0.9 完成;计分改为按成功率归一化。训练仿真器与评测仿真器均实现随机化,工作区仍只交付二进制,不提供仿真源码。
|
|
14
|
+
>
|
|
15
|
+
> **0.7.5**:把 0.7.4 误带的开发用 RL 可行性代码从插件中撤除,只保留修正后的 simulator;同时把 agent 训练仿真器与评测仿真器拆开:`assets/neural-pendulum/sim_agent` 只做随机近向下 reset,评测用 `neural-pendulum-eval/sim_eval` 每个 reset 采样新的隐藏物理参数/初始状态/观测噪声;工作区仍只交付二进制,不提供仿真源码。工作区仍只交付二进制,不提供仿真源码。
|
|
12
16
|
> **下文仍有若干条目标记为 ✅ 但实际未完成,不要再按旧状态理解**:arena legacy 的 `std::thread` 静态规则未移除;`cifar_cache.py` 解压仍无 filter、缓存路径仍用 `Path.home()/.dsh`;CIFAR 隐藏标签仍以文件形式落盘;FP32“30 分钟整轮”仍是 op 间检查;无 baseline 时评测仍 fail-open。其余任务相关修复按用户要求暂不动。
|
|
13
17
|
|
|
14
18
|
## 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
|
|
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
|
-
**仿真器分离**:工作区 `tools/sim` 是只接受随机近向下 reset 的**训练/自测**仿真器,不包含固定评测初态;评测使用独立 `neural-pendulum-eval/sim_eval
|
|
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.
|
|
1
|
+
# Neural-Pendulum 任务规格 v1.6
|
|
2
2
|
|
|
3
3
|
本文件是已确定的任务内部规格。实现、评测器、workspace 素材以此为准。
|
|
4
4
|
后续除非明确修改规格,否则不再重新讨论这些规则。
|
|
5
5
|
|
|
6
|
-
v1.
|
|
7
|
-
PyTorch 参考值 + 加权平均相对误差阈值”,不再使用 include/nn/nn.hpp 头文件契约和
|
|
8
|
-
隐藏 C++ 基础设施测试。
|
|
6
|
+
v1.6 变更:完成阈值提高到 0.90;最终 build/control 的力矩必须由神经网络输出层直接产生,最终控制程序不得包含显式控制律;未 completed 不得停止;禁止逆向/进程内存分析。基础设施评测协议不变。
|
|
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.9 完成。
|
|
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
|
-
|
|
40
|
+
每回合隐藏随机物理参数(不向 agent 暴露具体值):
|
|
43
41
|
|
|
44
42
|
~~~
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
51
|
-
tau_max
|
|
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
|
-
|
|
67
|
+
训练和评测的每个回合都重新采样隐藏物理参数、初始状态和观测噪声:
|
|
70
68
|
|
|
71
69
|
~~~
|
|
72
|
-
theta_i ~ Uniform(-0.
|
|
73
|
-
omega_i ~ Uniform(-0.
|
|
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,9 +119,9 @@ score = -1
|
|
|
131
119
|
|
|
132
120
|
## 7. 训练仿真接口
|
|
133
121
|
|
|
134
|
-
工作区提供预编译 tools/sim,不提供源码和内部参数。
|
|
135
|
-
`tools/sim` 是**训练/自测专用**:每次 reset
|
|
136
|
-
评测使用独立程序 sim_eval;它每次 reset
|
|
122
|
+
工作区提供预编译 tools/sim,不提供源码和内部参数。
|
|
123
|
+
`tools/sim` 是**训练/自测专用**:每次 reset 随机化隐藏物理参数、初始状态与观测噪声,不包含评测种子或固定评测初态。
|
|
124
|
+
评测使用独立程序 sim_eval;它每次 reset 采样新的隐藏物理参数、初始状态和观测噪声,并在 100 个隐藏随机回合上统计成功率。工作区不包含 sim_eval,也不包含评测种子或回合序列。
|
|
137
125
|
环境只提供状态转移,不提供 reward、loss、cost 或 done 信号。训练信号由 agent 自行构造。
|
|
138
126
|
|
|
139
127
|
优化目标:
|
|
@@ -141,7 +129,7 @@ score = -1
|
|
|
141
129
|
~~~
|
|
142
130
|
从接近全向下的初始状态起摆
|
|
143
131
|
使三根杆全部进入竖直向上容差并尽可能长时间保持
|
|
144
|
-
评测指标为 t_can
|
|
132
|
+
评测指标为 100 个隐藏随机回合上的成功率(单回合 t_can >= 10 s 记成功)
|
|
145
133
|
~~~
|
|
146
134
|
|
|
147
135
|
不规定损失函数、奖励函数、优化器或训练算法。
|
|
@@ -169,7 +157,7 @@ JSONL 协议:
|
|
|
169
157
|
{"error":"episode_finished"}
|
|
170
158
|
~~~
|
|
171
159
|
|
|
172
|
-
此时不会返回新的观测,也不会继续推进仿真。
|
|
160
|
+
此时不会返回新的观测,也不会继续推进仿真。
|
|
173
161
|
必须发送 reset 才能开始新 episode;reset 之前的所有 step 都返回该错误。
|
|
174
162
|
|
|
175
163
|
## 8. 交付源码与构建
|
|
@@ -259,26 +247,30 @@ dt = 0.01 s
|
|
|
259
247
|
最大仿真 = 30.0 s
|
|
260
248
|
t_tgt = 10.0 s
|
|
261
249
|
全向上容差 = 每根杆与竖直方向夹角 <= 0.05 rad
|
|
250
|
+
评测回合数 = 100
|
|
251
|
+
完成成功率阈值 = 0.9
|
|
262
252
|
~~~
|
|
263
253
|
|
|
264
|
-
t_can 定义:
|
|
254
|
+
单回合 t_can 定义:
|
|
265
255
|
|
|
266
256
|
~~~
|
|
267
257
|
从 reset 开始,三根杆全部处于容差内的最长连续时间
|
|
268
258
|
从未出现全向上则 t_can = 0
|
|
259
|
+
单回合成功 = (t_can >= t_tgt)
|
|
269
260
|
~~~
|
|
270
261
|
|
|
271
|
-
|
|
262
|
+
100 回合统计:
|
|
272
263
|
|
|
273
264
|
~~~
|
|
274
|
-
|
|
265
|
+
success_rate = 成功回合数 / 100
|
|
266
|
+
t_can = 100 回合 t_can 的平均值(仅用于报告)
|
|
275
267
|
~~~
|
|
276
268
|
|
|
277
269
|
完成条件:
|
|
278
270
|
|
|
279
271
|
~~~
|
|
280
272
|
infra_ok == true(全部算子可执行且误差不超阈值)
|
|
281
|
-
且
|
|
273
|
+
且 success_rate >= 0.9
|
|
282
274
|
~~~
|
|
283
275
|
|
|
284
276
|
## 10. 提交与测试事件
|
|
@@ -317,8 +309,8 @@ compile CMake 构建失败,或未产出 build/nn_task / build/control
|
|
|
317
309
|
infra build/nn_task 算子数值评测失败(不可执行/崩溃/超时/输出缺失/误差超阈值)
|
|
318
310
|
controller 控制器启动/协议/崩溃/超时/非法输出
|
|
319
311
|
simulation 仿真异常/NaN/仿真超时
|
|
320
|
-
scoring 正常测试但
|
|
321
|
-
completed
|
|
312
|
+
scoring 正常测试但 success_rate < 0.9
|
|
313
|
+
completed success_rate >= 0.9,任务完成
|
|
322
314
|
~~~
|
|
323
315
|
|
|
324
316
|
### 返回给 agent 的测试结果
|
|
@@ -327,13 +319,15 @@ completed t_can >= t_tgt,任务完成
|
|
|
327
319
|
{
|
|
328
320
|
"ok": true,
|
|
329
321
|
"stage": "scoring",
|
|
330
|
-
"t_can":
|
|
322
|
+
"t_can": 8.1,
|
|
323
|
+
"success_rate": 0.62,
|
|
324
|
+
"episodes": 100,
|
|
331
325
|
"completed": false,
|
|
332
326
|
"error": ""
|
|
333
327
|
}
|
|
334
328
|
~~~
|
|
335
329
|
|
|
336
|
-
返回 stage、t_can、completed,不返回最终分数。
|
|
330
|
+
返回 stage、t_can、success_rate、episodes、completed,不返回最终分数。
|
|
337
331
|
|
|
338
332
|
## 11. 计分
|
|
339
333
|
|
|
@@ -350,7 +344,7 @@ score = -1
|
|
|
350
344
|
有效测试:
|
|
351
345
|
|
|
352
346
|
~~~
|
|
353
|
-
score = (min(
|
|
347
|
+
score = (min(success_rate, 0.9) - 0.9) / 0.9
|
|
354
348
|
~~~
|
|
355
349
|
|
|
356
350
|
范围:
|
|
@@ -364,7 +358,7 @@ score = (min(t_can, t_tgt) - t_tgt) / t_tgt
|
|
|
364
358
|
- 提交事件不改变 score
|
|
365
359
|
- 只有测试请求改变 score
|
|
366
360
|
- 测试失败:从该测试请求时刻起 score = -1
|
|
367
|
-
- 测试成功:从该测试请求时刻起按
|
|
361
|
+
- 测试成功:从该测试请求时刻起按 success_rate 计分
|
|
368
362
|
- 第一次测试请求之前:score = -1
|
|
369
363
|
- 下一次测试请求之前,score 保持不变
|
|
370
364
|
|
|
@@ -386,7 +380,7 @@ T_done 是第一次返回 stage = completed 的测试请求时间。
|
|
|
386
380
|
|
|
387
381
|
~~~
|
|
388
382
|
stage = completed
|
|
389
|
-
|
|
383
|
+
success_rate >= 0.9
|
|
390
384
|
infra_ok = true
|
|
391
385
|
~~~
|
|
392
386
|
|
|
@@ -431,6 +425,8 @@ stage
|
|
|
431
425
|
ok
|
|
432
426
|
infra_ok
|
|
433
427
|
t_can
|
|
428
|
+
success_rate
|
|
429
|
+
episodes
|
|
434
430
|
score
|
|
435
431
|
completed
|
|
436
432
|
~~~
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
目标:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
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,24 +23,18 @@ tools/sim
|
|
|
22
23
|
|
|
23
24
|
不提供仿真器源码和物理参数。
|
|
24
25
|
|
|
25
|
-
工具说明:工作区的 `tools/sim` 是训练/自测专用仿真器,每次 `reset`
|
|
26
|
+
工具说明:工作区的 `tools/sim` 是训练/自测专用仿真器,每次 `reset` 都会随机化物理参数、初始状态和观测噪声;它不是评测器,也没有隐藏评测种子。评测使用工作区之外的独立仿真程序,在 100 个隐藏随机回合上统计成功率。
|
|
26
27
|
|
|
27
|
-
###
|
|
28
|
+
### 初始状态与随机化
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
训练和评测的每个回合:
|
|
30
31
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
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
|
-
- 三个固定角度互不相同
|
|
40
|
-
- 三个固定角度的绝对值也互不相同
|
|
41
|
-
- 初始角速度为 0
|
|
42
|
-
- 不加任何随机扰动
|
|
37
|
+
单回合内部参数不再改变;每次 reset 开始一个新回合。
|
|
43
38
|
|
|
44
39
|
### 驱动结构
|
|
45
40
|
|
|
@@ -66,6 +61,7 @@ obs = [a1, a2, a3] (rad)
|
|
|
66
61
|
- theta_i = 0 表示竖直向下,theta_i = pi 表示竖直向上
|
|
67
62
|
- a_i 是相对竖直向上的角度误差
|
|
68
63
|
- 不提供角速度
|
|
64
|
+
- 每一步返回的三个角度都叠加随机观测噪声,噪声强度每回合随机
|
|
69
65
|
- 不提供第三杆末端位置
|
|
70
66
|
- 不提供杆长、质量、惯量等物理参数
|
|
71
67
|
|
|
@@ -85,29 +81,34 @@ obs = [a1, a2, a3] (rad)
|
|
|
85
81
|
|
|
86
82
|
### 控制与仿真
|
|
87
83
|
|
|
88
|
-
~~~
|
|
89
84
|
控制频率 = 100 Hz
|
|
90
85
|
控制周期 = 0.01 s
|
|
91
86
|
最大仿真时间 = 30.0 s
|
|
92
87
|
t_tgt = 10.0 s
|
|
93
88
|
全向上容差 = 每根杆与竖直方向夹角 <= 0.05 rad
|
|
94
|
-
~~~
|
|
95
89
|
|
|
96
|
-
|
|
90
|
+
每个评测回合计算:
|
|
97
91
|
|
|
98
|
-
~~~
|
|
99
92
|
t_can = 三根杆同时处于全向上容差内的最长连续时间
|
|
100
|
-
|
|
93
|
+
单回合成功 = (t_can >= t_tgt)
|
|
94
|
+
|
|
95
|
+
评测使用 100 个隐藏随机回合:
|
|
96
|
+
|
|
97
|
+
success_rate = 成功回合数 / 100
|
|
101
98
|
|
|
102
99
|
当:
|
|
103
100
|
|
|
104
|
-
|
|
105
|
-
t_can >= t_tgt
|
|
101
|
+
success_rate >= 0.9
|
|
106
102
|
且神经网络基础设施数值评测全部通过
|
|
107
|
-
~~~
|
|
108
103
|
|
|
109
104
|
任务完成,计时停止。
|
|
110
105
|
|
|
106
|
+
该任务已由内部基线验证:100 回合成功率 >= 0.90 是可达的,不要怀疑可行性。在 eval_test 返回 stage=completed 之前,绝对不允许停止、总结或放弃;不要因为成功率接近 0.90、遇到平台期或短期没有提升就收尾。只要没有完成,就必须继续训练、改进、提交和测试,直到完成或外部强制停止。
|
|
107
|
+
|
|
108
|
+
最终提交的 build/control 必须由神经网络输出层直接产生两个力矩;最终控制程序中不得包含任何显式控制律(PID/LQR/MPC/滑模/自抗扰等),也不得在神经网络之外根据状态计算或修正力矩。
|
|
109
|
+
|
|
110
|
+
不得对已编译文件进行任何形式的逆向、反汇编、strings/objdump/readelf/gdb 分析,也不得读取 /proc 或其它进程内存。环境信息只能来自本文件/SPEC.md 和正常使用 tools/sim 的 reset/step 输入输出;违规者成绩无效。
|
|
111
|
+
|
|
111
112
|
## 训练用仿真接口
|
|
112
113
|
|
|
113
114
|
tools/sim 使用 JSONL 协议。
|
|
@@ -136,7 +137,7 @@ tools/sim 使用 JSONL 协议。
|
|
|
136
137
|
此时不返回新观测,也不会继续推进仿真。
|
|
137
138
|
必须 reset 后才能开始新 episode;reset 前的 step 都返回该错误。
|
|
138
139
|
|
|
139
|
-
可以反复 reset / step 进行数据采集、模型训练和自测。该程序只做随机训练 reset
|
|
140
|
+
可以反复 reset / step 进行数据采集、模型训练和自测。该程序只做随机训练 reset,不包含隐藏评测种子;评测器是独立程序。
|
|
140
141
|
|
|
141
142
|
环境只返回状态,不提供 reward、loss、cost 或 done 信号。
|
|
142
143
|
训练信号由你自行构造。
|
|
@@ -336,6 +337,8 @@ eval_test
|
|
|
336
337
|
~~~text
|
|
337
338
|
stage
|
|
338
339
|
t_can
|
|
340
|
+
success_rate
|
|
341
|
+
episodes
|
|
339
342
|
completed
|
|
340
343
|
error
|
|
341
344
|
~~~
|
|
@@ -361,8 +364,8 @@ completed
|
|
|
361
364
|
- infra:build/nn_task 算子数值评测失败(不可执行、崩溃、超时、输出缺失或误差超阈值)
|
|
362
365
|
- controller:控制器启动、协议、崩溃、超时或输出非法
|
|
363
366
|
- simulation:隐藏仿真推进异常、NaN 或超时
|
|
364
|
-
- scoring:测试有效,但 `
|
|
365
|
-
- completed:基础设施和仿真均通过,且 `
|
|
367
|
+
- scoring:测试有效,但 `success_rate < 0.9`
|
|
368
|
+
- completed:基础设施和仿真均通过,且 `success_rate >= 0.9`
|
|
366
369
|
|
|
367
370
|
最终排行分数不会返回给你,只用于赛后排名。
|
|
368
371
|
|
|
@@ -372,7 +375,7 @@ completed
|
|
|
372
375
|
|
|
373
376
|
~~~
|
|
374
377
|
stage = completed
|
|
375
|
-
|
|
378
|
+
success_rate >= 0.9
|
|
376
379
|
~~~
|
|
377
380
|
|
|
378
381
|
即任务完成。
|
|
@@ -409,22 +412,17 @@ t_can >= 10.0
|
|
|
409
412
|
|
|
410
413
|
只有申请测试会更新当前分数:
|
|
411
414
|
|
|
412
|
-
~~~
|
|
413
415
|
无效测试 / 编译失败 / 基础设施失败 / 控制器失败 / 仿真失败 / 无提交:
|
|
414
416
|
score(t) = -1
|
|
415
417
|
|
|
416
418
|
有效测试:
|
|
417
|
-
score(t) = (min(
|
|
418
|
-
~~~
|
|
419
|
+
score(t) = (min(success_rate, 0.9) - 0.9) / 0.9
|
|
419
420
|
|
|
420
421
|
其中:
|
|
421
422
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
score
|
|
425
|
-
t_can = 0 时 score = -1
|
|
426
|
-
t_can >= t_tgt 时 score = 0
|
|
427
|
-
~~~
|
|
423
|
+
success_rate 的范围是 [0, 1]
|
|
424
|
+
success_rate = 0 时 score = -1
|
|
425
|
+
success_rate >= 0.9 时 score = 0
|
|
428
426
|
|
|
429
427
|
分数从测试请求时刻开始生效,直到下一次测试请求。
|
|
430
428
|
|
|
@@ -434,20 +432,11 @@ t_can >= t_tgt 时 score = 0
|
|
|
434
432
|
|
|
435
433
|
最终分数是时间积分,不除以总时长:
|
|
436
434
|
|
|
437
|
-
~~~
|
|
438
435
|
final = integral_0^T_done score(t) dt
|
|
439
|
-
~~~
|
|
440
436
|
|
|
441
437
|
T_done 是第一次返回 completed 的测试请求时间。
|
|
442
438
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
- 越早达到完成条件,负分区间越短,最终分数越高
|
|
446
|
-
- 拖得越久,负分区间越长
|
|
447
|
-
- 完成后停止积分
|
|
448
|
-
- 未完成不参与最终排名
|
|
449
|
-
|
|
450
|
-
### 完成时
|
|
439
|
+
## 完成时
|
|
451
440
|
|
|
452
441
|
当测试返回 completed 时:
|
|
453
442
|
|
|
@@ -461,7 +450,9 @@ T_done 是第一次返回 completed 的测试请求时间。
|
|
|
461
450
|
测试结果会返回:
|
|
462
451
|
|
|
463
452
|
- stage
|
|
464
|
-
- t_can
|
|
453
|
+
- t_can(100 回合平均保持时间,仅用于报告)
|
|
454
|
+
- success_rate(100 个随机隐藏回合的成功率)
|
|
455
|
+
- episodes
|
|
465
456
|
- completed
|
|
466
457
|
- error
|
|
467
458
|
|
|
@@ -473,5 +464,5 @@ T_done 是第一次返回 completed 的测试请求时间。
|
|
|
473
464
|
2. 先实现最小可用的 tensor/算子/反向/优化器/线程池,并让 build/nn_task 跑通
|
|
474
465
|
3. 用独立 double 标量参考、有限差分梯度检查逐算子核对误差,边界形状/广播/groups/stride/dilation 都要覆盖(工作区没有 PyTorch/numpy)
|
|
475
466
|
4. 用 tools/sim 采集数据并训练控制器,导出权重供 build/control 使用
|
|
476
|
-
5. 反复提交和测试,优先把
|
|
467
|
+
5. 反复提交和测试,优先把 100 回合成功率提升到 90%
|
|
477
468
|
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
|
|
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
|
-
|
|
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 +=
|
|
43
|
-
M[i][j] =
|
|
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] =
|
|
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,
|
|
64
|
-
|
|
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 +=
|
|
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])*
|
|
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] = -
|
|
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);
|
|
@@ -90,69 +110,79 @@ static void deriv(const State& s, const std::array<double,N>& tau, State& out) {
|
|
|
90
110
|
|
|
91
111
|
int main(int argc, char** argv) {
|
|
92
112
|
#ifdef DSH_EVAL_SIM
|
|
93
|
-
/* Evaluator-only binary
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
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);
|
|
97
119
|
#else
|
|
98
|
-
/* Agent/training binary
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
int seed=0;
|
|
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;
|
|
102
123
|
for(int i=1;i<argc;i++){
|
|
103
124
|
std::string a=argv[i];
|
|
104
|
-
if(a=="--seed" && i+1<argc) seed=std::
|
|
125
|
+
if(a=="--seed" && i+1<argc) seed=static_cast<uint32_t>(std::strtoul(argv[++i],nullptr,10));
|
|
105
126
|
}
|
|
106
127
|
std::mt19937 rng(seed);
|
|
107
|
-
std::uniform_real_distribution<double> u(-1.0,1.0);
|
|
108
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);
|
|
109
135
|
State s;
|
|
136
|
+
double noise_std = 0.0;
|
|
110
137
|
int steps=0; bool ready=false;
|
|
138
|
+
|
|
111
139
|
auto reset=[&](){
|
|
112
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] << "]";
|
|
113
155
|
#ifdef DSH_EVAL_SIM
|
|
114
|
-
s.theta
|
|
115
|
-
s.omega={0,0,0};
|
|
116
|
-
#else
|
|
117
|
-
s.theta={u(rng)*0.05,u(rng)*0.05,u(rng)*0.05};
|
|
118
|
-
s.omega={u(rng)*0.02,u(rng)*0.02,u(rng)*0.02};
|
|
156
|
+
std::cout << ",\"state\":[" << wrap(s.theta[0]-PI) << "," << wrap(s.theta[1]-PI) << "," << wrap(s.theta[2]-PI) << "]";
|
|
119
157
|
#endif
|
|
158
|
+
std::cout << "}\n" << std::flush;
|
|
120
159
|
};
|
|
121
|
-
|
|
160
|
+
|
|
122
161
|
s.theta={0,0,0}; s.omega={0,0,0};
|
|
123
162
|
std::string line;
|
|
124
163
|
while(std::getline(std::cin,line)){
|
|
125
164
|
if(line.empty()) continue;
|
|
126
|
-
if(line.find("reset")!=std::string::npos){ reset();
|
|
165
|
+
if(line.find("reset")!=std::string::npos){ reset(); emit_state(); continue; }
|
|
127
166
|
if(line.find("step")!=std::string::npos){
|
|
128
167
|
if(!ready || steps>=MAX_STEPS){ std::cout << "{\"error\":\"episode_finished\"}\n" << std::flush; continue; }
|
|
129
|
-
|
|
130
|
-
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); } }
|
|
131
169
|
u1=std::max(-1.0,std::min(1.0,u1)); u3=std::max(-1.0,std::min(1.0,u3));
|
|
132
|
-
|
|
133
|
-
// coordinates its generalized force is (tau1, -tau3, tau3): the reaction
|
|
134
|
-
// on link 2 is part of the same joint torque. (tau1, 0, tau3) would be a
|
|
135
|
-
// reactionless external torque on link 3 and would contradict joint 2
|
|
136
|
-
// being passive.
|
|
137
|
-
std::array<double,N> tau{{u1*TAU_MAX,-u3*TAU_MAX,u3*TAU_MAX}};
|
|
138
|
-
// 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}};
|
|
139
171
|
double h=DT/SUB;
|
|
140
172
|
for(int sub=0;sub<SUB;sub++){
|
|
141
173
|
State k1=s,k2=s,k3=s,k4=s; State d{};
|
|
142
|
-
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]; }
|
|
143
|
-
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]; }
|
|
144
|
-
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]; }
|
|
145
|
-
deriv(k4,tau,d);
|
|
146
|
-
// 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]; }
|
|
147
177
|
State ds1{},ds2{},ds3{},ds4{};
|
|
148
|
-
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);
|
|
149
179
|
for(int i=0;i<N;i++){
|
|
150
180
|
s.theta[i] += h/6.0*(ds1.theta[i]+2*ds2.theta[i]+2*ds3.theta[i]+ds4.theta[i]);
|
|
151
181
|
s.omega[i] += h/6.0*(ds1.omega[i]+2*ds2.omega[i]+2*ds3.omega[i]+ds4.omega[i]);
|
|
152
182
|
}
|
|
153
183
|
}
|
|
154
184
|
steps++;
|
|
155
|
-
|
|
185
|
+
emit_state();
|
|
156
186
|
continue;
|
|
157
187
|
}
|
|
158
188
|
std::cout << "{\"error\":\"bad_command\"}\n" << std::flush;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/index.js
CHANGED
|
@@ -2166,7 +2166,7 @@ const TASKS = [
|
|
|
2166
2166
|
{
|
|
2167
2167
|
id: NEURAL_PENDULUM_TASK,
|
|
2168
2168
|
label: NEURAL_PENDULUM_TASK_LABEL,
|
|
2169
|
-
description: "从零实现纯 C++17 神经网络基础设施(算子数值以评测侧 PyTorch
|
|
2169
|
+
description: "从零实现纯 C++17 神经网络基础设施(算子数值以评测侧 PyTorch 参考校验),用三个关节角度控制预编译三节摆起摆并稳定保持;评测随机隐藏回合,统计成功率。",
|
|
2170
2170
|
timeLimitMinutes: 1440,
|
|
2171
2171
|
timeLimitEnabled: false,
|
|
2172
2172
|
needsSetup: true
|
|
@@ -2482,13 +2482,19 @@ const NEURAL_PENDULUM_DEFINITION = {
|
|
|
2482
2482
|
"",
|
|
2483
2483
|
"请先完整阅读 TASK.md;它是权威规则。硬约束:仅 C++17 标准库、仅 CPU、fp32;不使用 Python/OpenCL/CUDA/BLAS/Eigen/第三方库;必须从提交源码构建出 build/nn_task 与 build/control。",
|
|
2484
2484
|
"",
|
|
2485
|
-
"环境细节:joint1 与 joint3 驱动,joint2 被动;控制频率 100 Hz;每回合最多 3000 步(30 s),达到上限后继续 step 返回 {\"error\":\"episode_finished\"},必须 reset。theta=0 表示竖直向下、theta=pi 表示竖直向上;观测 a_i=wrap(theta_i-pi),即三个关节角度(弧度,不除 pi)。环境不返回 reward/loss/cost/done;不提供杆长、质量、惯量、角速度或 tip
|
|
2485
|
+
"环境细节:joint1 与 joint3 驱动,joint2 被动;控制频率 100 Hz;每回合最多 3000 步(30 s),达到上限后继续 step 返回 {\"error\":\"episode_finished\"},必须 reset。theta=0 表示竖直向下、theta=pi 表示竖直向上;观测 a_i=wrap(theta_i-pi),即三个关节角度(弧度,不除 pi)。环境不返回 reward/loss/cost/done;不提供杆长、质量、惯量、角速度或 tip。训练与评测的每个回合都会随机化物理参数、初始状态和观测噪声;评测在 100 个隐藏随机回合上统计成功率,单回合连续全向上保持 10 s 记成功。",
|
|
2486
2486
|
"",
|
|
2487
|
-
"优化目标:从接近全向下起摆,使三根杆全部进入 0.05 rad
|
|
2487
|
+
"优化目标:从接近全向下起摆,使三根杆全部进入 0.05 rad 的竖直向上容差并保持;评测指标为 100 个隐藏随机回合上的成功率(每回合连续保持 10 s 记成功),成功率 >= 90% 即完成。损失函数、奖励、优化器、网络结构和训练方法由你自行设计,不规定任何训练方案。",
|
|
2488
|
+
"",
|
|
2489
|
+
"该任务已由内部基线验证:100 回合成功率 >= 0.90 是可达的,不要怀疑可行性。在 eval_test 返回 stage=completed 之前,绝对不允许停止、总结或放弃;不要因为成功率接近 0.90、遇到平台期或短期没有提升就收尾。只要没有完成,就必须继续训练、改进、提交和测试,直到完成或外部强制停止。",
|
|
2490
|
+
"",
|
|
2491
|
+
"最终提交的 build/control 必须由神经网络输出层直接产生两个力矩;最终控制程序中不得包含任何显式控制律(PID/LQR/MPC/滑模/自抗扰等),也不得在神经网络之外根据状态计算或修正力矩。",
|
|
2492
|
+
"",
|
|
2493
|
+
"不得对已编译文件进行任何形式的逆向、反汇编、strings/objdump/readelf/gdb 分析,也不得读取 /proc 或其它进程内存。环境信息只能来自 TASK.md/SPEC.md 和正常使用 tools/sim 的 reset/step 输入输出;违规者成绩无效。",
|
|
2488
2494
|
"",
|
|
2489
2495
|
"注意:评测侧用 PyTorch 计算算子参考值,但工作区/候选运行时不提供 PyTorch 或 numpy,也没有网络,无法 pip 安装;开发阶段请用 C++ 或 Python 标准库/独立 double 实现做数值对照。",
|
|
2490
2496
|
"",
|
|
2491
|
-
"评测会从干净副本重新构建提交源码,先运行与 Neural-FP32/Neural-CIFAR 相同的算子数值评测:评测侧用 PyTorch
|
|
2497
|
+
"评测会从干净副本重新构建提交源码,先运行与 Neural-FP32/Neural-CIFAR 相同的算子数值评测:评测侧用 PyTorch 在相同数据上计算参考值,逐操作比较加权平均相对误差并对照阈值,任一算子不可执行、崩溃、超时、输出缺失或误差超阈值即判为基础设施失败;然后在 100 个随机隐藏回合上驱动 build/control,统计成功率(单回合连续全向上保持 10 s 记成功)。不要把任何未提交的二进制、临时构建产物或外部进程当作交付依赖。",
|
|
2492
2498
|
"使用宿主工具 eval_submit 提交当前源码快照,使用 eval_test 请求宿主评测最新提交。提交快照、事件、测试结果和最终分数全部保存在工作区之外的宿主目录;不要尝试读取或修改这些数据,也不要依赖任何工作区内的测试结果文件。最终评测使用最新提交快照,按宿主事件时间积分计算分数。",
|
|
2493
2499
|
"",
|
|
2494
2500
|
"工作方式:先完成最小可构建的基础设施和 control 协议;再持续做控制/训练实验并记录可复现结果;最终保证工作区从零构建、build/nn_task 算子数值评测通过、control 可被评测驱动,并留下 NOTES.md/RESULTS.md。",
|
|
@@ -2680,15 +2686,17 @@ var TaskService = class TaskService {
|
|
|
2680
2686
|
if (text.includes("controller") || text.includes("torque")) return "controller";
|
|
2681
2687
|
return "simulation";
|
|
2682
2688
|
}
|
|
2683
|
-
/**
|
|
2684
|
-
static
|
|
2689
|
+
/** Evaluation success-rate target used by the neural-pendulum scoring integral. */
|
|
2690
|
+
static FINAL_SUCCESS_TARGET = .9;
|
|
2685
2691
|
/** One test event's instantaneous score, mirroring the task specification. */
|
|
2686
2692
|
static eventScore(event) {
|
|
2687
2693
|
const stage = String(event["stage"] ?? "");
|
|
2688
2694
|
if (stage !== "scoring" && stage !== "completed" && event["valid"] !== true) return -1;
|
|
2695
|
+
const rate = Number(event["success_rate"]);
|
|
2696
|
+
if (Number.isFinite(rate)) return (Math.min(Math.max(rate, 0), TaskService.FINAL_SUCCESS_TARGET) - TaskService.FINAL_SUCCESS_TARGET) / TaskService.FINAL_SUCCESS_TARGET;
|
|
2689
2697
|
const value = Number(event["t_can"] ?? 0);
|
|
2690
2698
|
if (!Number.isFinite(value)) return -1;
|
|
2691
|
-
return (Math.min(Math.max(value, 0),
|
|
2699
|
+
return (Math.min(Math.max(value, 0), 10) - 10) / 10;
|
|
2692
2700
|
}
|
|
2693
2701
|
/**
|
|
2694
2702
|
* Host-side authoritative final score: integral of the per-test score over
|
|
@@ -2822,11 +2830,14 @@ var TaskService = class TaskService {
|
|
|
2822
2830
|
const rawStage = String(value.stage ?? "");
|
|
2823
2831
|
const stage = rawStage !== "" ? rawStage : this.inferStage(value, error);
|
|
2824
2832
|
const tCan = Number(value.t_can ?? 0);
|
|
2833
|
+
const successRate = Number(value.success_rate ?? 0);
|
|
2825
2834
|
const finalScore = Number(value.final_score ?? 0);
|
|
2826
2835
|
return {
|
|
2827
2836
|
stage,
|
|
2828
2837
|
t_can: Number.isFinite(tCan) ? tCan : 0,
|
|
2829
|
-
|
|
2838
|
+
success_rate: Number.isFinite(successRate) ? successRate : 0,
|
|
2839
|
+
episodes: Number(value.episodes ?? 0),
|
|
2840
|
+
completed: stage === "completed" && Number.isFinite(successRate) && successRate >= TaskService.FINAL_SUCCESS_TARGET,
|
|
2830
2841
|
error,
|
|
2831
2842
|
final_score: Number.isFinite(finalScore) ? finalScore : 0
|
|
2832
2843
|
};
|
|
@@ -2854,6 +2865,8 @@ var TaskService = class TaskService {
|
|
|
2854
2865
|
submission_id: "",
|
|
2855
2866
|
stage: "no_submission",
|
|
2856
2867
|
t_can: 0,
|
|
2868
|
+
success_rate: 0,
|
|
2869
|
+
episodes: 0,
|
|
2857
2870
|
completed: false,
|
|
2858
2871
|
error: ""
|
|
2859
2872
|
});
|
|
@@ -2869,13 +2882,17 @@ var TaskService = class TaskService {
|
|
|
2869
2882
|
const value = await this.runHostEval(run.workspaceId, startedAt);
|
|
2870
2883
|
const stage = String(value.stage ?? "simulation");
|
|
2871
2884
|
const tCan = Number(value.t_can ?? 0);
|
|
2872
|
-
const
|
|
2885
|
+
const successRate = Number(value.success_rate ?? 0);
|
|
2886
|
+
const episodes = Number(value.episodes ?? 0);
|
|
2887
|
+
const completed = stage === "completed" && Number.isFinite(successRate) && successRate >= TaskService.FINAL_SUCCESS_TARGET;
|
|
2873
2888
|
const event = {
|
|
2874
2889
|
event: "test",
|
|
2875
2890
|
time: requestedAt,
|
|
2876
2891
|
submission_id: latest.id,
|
|
2877
2892
|
stage,
|
|
2878
2893
|
t_can: tCan,
|
|
2894
|
+
success_rate: Number.isFinite(successRate) ? successRate : 0,
|
|
2895
|
+
episodes: Number.isFinite(episodes) ? episodes : 0,
|
|
2879
2896
|
completed,
|
|
2880
2897
|
error: String(value.error ?? "")
|
|
2881
2898
|
};
|
|
@@ -2886,12 +2903,16 @@ var TaskService = class TaskService {
|
|
|
2886
2903
|
at: requestedAt,
|
|
2887
2904
|
stage,
|
|
2888
2905
|
t_can: tCan,
|
|
2906
|
+
success_rate: Number.isFinite(successRate) ? successRate : 0,
|
|
2907
|
+
episodes: Number.isFinite(episodes) ? episodes : 0,
|
|
2889
2908
|
final_score: finalScore
|
|
2890
2909
|
}) + "\n", "utf8");
|
|
2891
2910
|
return {
|
|
2892
2911
|
ok: true,
|
|
2893
2912
|
stage,
|
|
2894
2913
|
t_can: tCan,
|
|
2914
|
+
success_rate: Number.isFinite(successRate) ? successRate : 0,
|
|
2915
|
+
episodes: Number.isFinite(episodes) ? episodes : 0,
|
|
2895
2916
|
completed,
|
|
2896
2917
|
error: String(value.error ?? "")
|
|
2897
2918
|
};
|
|
@@ -3576,6 +3597,8 @@ const OUTPUT_SCHEMA = {
|
|
|
3576
3597
|
files: { type: "number" },
|
|
3577
3598
|
stage: { type: "string" },
|
|
3578
3599
|
t_can: { type: "number" },
|
|
3600
|
+
success_rate: { type: "number" },
|
|
3601
|
+
episodes: { type: "number" },
|
|
3579
3602
|
completed: { type: "boolean" },
|
|
3580
3603
|
error: { type: "string" }
|
|
3581
3604
|
},
|
|
@@ -3613,7 +3636,7 @@ function registerAgentSubmissionTools(agent, guard, tasks) {
|
|
|
3613
3636
|
};
|
|
3614
3637
|
const test = {
|
|
3615
3638
|
name: "eval_test",
|
|
3616
|
-
description: "Run a host-side evaluation of the latest submitted snapshot and return stage, t_can, completed and error. Only successful tests update the score.",
|
|
3639
|
+
description: "Run a host-side evaluation of the latest submitted snapshot and return stage, t_can, success_rate, episodes, completed and error. Only successful tests update the score.",
|
|
3617
3640
|
parameters: {
|
|
3618
3641
|
type: "object",
|
|
3619
3642
|
properties: {}
|
|
@@ -7,15 +7,14 @@ plugin UI.
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
9
|
- `run_eval.py` — builds the latest submission, runs the operator oracle, then
|
|
10
|
-
drives `build/control` against
|
|
10
|
+
drives `build/control` against 100 random hidden episodes and emits a
|
|
11
11
|
staged result (`no_submission` / `compile` / `infra` / `controller` /
|
|
12
12
|
`simulation` / `scoring` / `completed`).
|
|
13
13
|
- `ops_eval.py` — operator oracle: PyTorch references computed in-process,
|
|
14
14
|
weighted mean relative error compared with per-operation thresholds. Same
|
|
15
15
|
operations, cases, seeds and thresholds as Neural-FP32 / Neural-CIFAR.
|
|
16
|
-
- `sim_eval` — trusted evaluator-only simulator binary. Every
|
|
17
|
-
|
|
18
|
-
`--eval` switch. The workspace `tools/sim` is a separate agent/training
|
|
16
|
+
- `sim_eval` — trusted evaluator-only simulator binary. Every reset samples a
|
|
17
|
+
random hidden plant, initial state and observation-noise level; no --eval switch. The workspace `tools/sim` is a separate agent/training
|
|
19
18
|
binary that only does random near-down resets and is not shipped here.
|
|
20
19
|
|
|
21
20
|
## Requirements
|
|
@@ -44,12 +44,14 @@ DT = 0.01
|
|
|
44
44
|
MAX_STEPS = 3000
|
|
45
45
|
T_TGT = 10.0
|
|
46
46
|
UP_TOL = 0.05
|
|
47
|
+
EPISODES = 100
|
|
48
|
+
SUCCESS_TARGET = 0.9
|
|
47
49
|
# The score changes at the test request instant. This process is started for
|
|
48
50
|
# one request, so module import is the closest available request timestamp.
|
|
49
51
|
TEST_TIME = time.time()
|
|
50
52
|
# Hard budgets so a pathological submission cannot stall the evaluation job.
|
|
51
53
|
OPS_DEADLINE_SECONDS = 240.0
|
|
52
|
-
CONTROL_DEADLINE_SECONDS =
|
|
54
|
+
CONTROL_DEADLINE_SECONDS = 900.0
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class LineReader:
|
|
@@ -124,6 +126,13 @@ def score_for(event: dict) -> float:
|
|
|
124
126
|
stage = event.get('stage', '')
|
|
125
127
|
if stage not in ('scoring', 'completed') and event.get('valid') is not True:
|
|
126
128
|
return -1.0
|
|
129
|
+
try:
|
|
130
|
+
success_rate = float(event.get('success_rate'))
|
|
131
|
+
except Exception:
|
|
132
|
+
success_rate = float('nan')
|
|
133
|
+
if math.isfinite(success_rate):
|
|
134
|
+
return (min(max(success_rate, 0.0), SUCCESS_TARGET) - SUCCESS_TARGET) / SUCCESS_TARGET
|
|
135
|
+
# Backward-compatible fallback for old single-episode events.
|
|
127
136
|
try:
|
|
128
137
|
t_can = float(event.get('t_can', 0.0))
|
|
129
138
|
except Exception:
|
|
@@ -185,19 +194,23 @@ def emit(result: dict) -> int:
|
|
|
185
194
|
|
|
186
195
|
|
|
187
196
|
def final_score_with_current(workspace: Path, events: list[dict], stage: str,
|
|
188
|
-
t_can: float, success: bool
|
|
197
|
+
t_can: float, success: bool, success_rate: float,
|
|
198
|
+
episodes: int) -> tuple[float, float | None]:
|
|
189
199
|
"""Score including the test this process is performing at TEST_TIME."""
|
|
190
200
|
current = {
|
|
191
201
|
'event': 'test', 'time': TEST_TIME, 'stage': stage,
|
|
192
|
-
't_can': float(t_can), '
|
|
202
|
+
't_can': float(t_can), 'success_rate': float(success_rate),
|
|
203
|
+
'episodes': int(episodes), 'completed': bool(success),
|
|
193
204
|
}
|
|
194
205
|
return final_score(workspace, [*events, current])
|
|
195
206
|
|
|
196
207
|
|
|
197
208
|
def build_result(stage: str, workspace: Path, events: list[dict], error: str = '',
|
|
198
209
|
ops: dict | None = None, source: str = '', t_can: float = 0.0,
|
|
199
|
-
success: bool = False
|
|
200
|
-
|
|
210
|
+
success: bool = False, success_rate: float = 0.0,
|
|
211
|
+
episodes: int = 0) -> dict:
|
|
212
|
+
integral, done_at = final_score_with_current(workspace, events, stage, t_can, success,
|
|
213
|
+
success_rate, episodes)
|
|
201
214
|
result = {
|
|
202
215
|
'task': 'neural-pendulum',
|
|
203
216
|
'ok': True,
|
|
@@ -205,6 +218,8 @@ def build_result(stage: str, workspace: Path, events: list[dict], error: str = '
|
|
|
205
218
|
'infra_ok': False,
|
|
206
219
|
't_can': float(t_can),
|
|
207
220
|
'success': bool(success),
|
|
221
|
+
'success_rate': float(success_rate),
|
|
222
|
+
'episodes': int(episodes),
|
|
208
223
|
'error': error,
|
|
209
224
|
'final_score': integral,
|
|
210
225
|
'done_at': done_at,
|
|
@@ -266,8 +281,9 @@ def main() -> int:
|
|
|
266
281
|
return emit(fail(ops_eval.infrastructure_error(ops), 'infra', workspace, events, ops=ops))
|
|
267
282
|
|
|
268
283
|
env = child_env()
|
|
269
|
-
# sim_eval is the evaluator-only binary
|
|
270
|
-
#
|
|
284
|
+
# sim_eval is the evaluator-only binary. Every reset samples a fresh
|
|
285
|
+
# hidden plant, initial state and observation-noise level; the success
|
|
286
|
+
# check uses the true state, while the controller only receives obs.
|
|
271
287
|
sim = subprocess.Popen([str(SIM)], stdin=subprocess.PIPE,
|
|
272
288
|
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env)
|
|
273
289
|
control = subprocess.Popen([str(control_path)], cwd=str(source),
|
|
@@ -276,90 +292,135 @@ def main() -> int:
|
|
|
276
292
|
sim_reader = LineReader(sim)
|
|
277
293
|
control_reader = LineReader(control)
|
|
278
294
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
reply = json.loads(sim_reader.readline(10.0))
|
|
282
|
-
obs = reply['obs']
|
|
283
|
-
except Exception as exc:
|
|
284
|
-
return emit(fail('simulator reset failed: ' + str(exc), 'simulation', workspace, events, ops=ops))
|
|
285
|
-
try:
|
|
286
|
-
write_line(control, {'type': 'reset'})
|
|
287
|
-
ready = json.loads(control_reader.readline(10.0))
|
|
288
|
-
except Exception as exc:
|
|
289
|
-
return emit(fail('controller reset failed: ' + str(exc), 'controller', workspace, events, ops=ops))
|
|
290
|
-
if not isinstance(ready, dict) or ready.get('type') != 'ready':
|
|
291
|
-
return emit(fail('controller did not reply ready', 'controller', workspace, events, ops=ops))
|
|
292
|
-
|
|
293
|
-
t_can = 0.0
|
|
294
|
-
up_start = None
|
|
295
|
+
successes = 0
|
|
296
|
+
t_can_total = 0.0
|
|
295
297
|
completed = False
|
|
296
298
|
error = ''
|
|
297
299
|
stage = 'scoring'
|
|
300
|
+
attempted = 0
|
|
298
301
|
control_deadline = time.monotonic() + CONTROL_DEADLINE_SECONDS
|
|
299
|
-
for
|
|
302
|
+
for ep in range(EPISODES):
|
|
300
303
|
if time.monotonic() > control_deadline:
|
|
301
304
|
error = 'control evaluation deadline exceeded'
|
|
302
305
|
stage = 'simulation'
|
|
303
306
|
break
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
stage = 'simulation'
|
|
307
|
-
break
|
|
307
|
+
# A fresh hidden plant, random initial state and fresh observation
|
|
308
|
+
# noise level for every evaluation episode.
|
|
308
309
|
try:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
break
|
|
314
|
-
if not all(math.isfinite(x) for x in angles):
|
|
315
|
-
error = 'simulator returned non-finite observation'
|
|
310
|
+
write_line(sim, {'cmd': 'reset'})
|
|
311
|
+
reply = json.loads(sim_reader.readline(10.0))
|
|
312
|
+
except Exception as exc:
|
|
313
|
+
error = 'simulator reset failed: ' + str(exc)
|
|
316
314
|
stage = 'simulation'
|
|
317
315
|
break
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
up_start = step * DT
|
|
321
|
-
t_can = max(t_can, step * DT - up_start)
|
|
322
|
-
else:
|
|
323
|
-
up_start = None
|
|
324
|
-
if t_can >= T_TGT:
|
|
325
|
-
completed = True
|
|
326
|
-
stage = 'completed'
|
|
327
|
-
break
|
|
328
|
-
|
|
329
|
-
write_line(control, {'type': 'step', 'step': step, 'state': angles})
|
|
316
|
+
obs = reply.get('obs') if isinstance(reply, dict) else None
|
|
317
|
+
true = reply.get('state', obs) if isinstance(reply, dict) else None
|
|
330
318
|
try:
|
|
331
|
-
|
|
319
|
+
write_line(control, {'type': 'reset'})
|
|
320
|
+
ready = json.loads(control_reader.readline(10.0))
|
|
332
321
|
except Exception as exc:
|
|
333
|
-
error = 'controller
|
|
322
|
+
error = 'controller reset failed: ' + str(exc)
|
|
334
323
|
stage = 'controller'
|
|
335
324
|
break
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
or not all(isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
339
|
-
and math.isfinite(v) for v in torque)
|
|
340
|
-
or any(abs(float(v)) > 1.0 for v in torque)):
|
|
341
|
-
error = 'controller returned invalid torque'
|
|
325
|
+
if not isinstance(ready, dict) or ready.get('type') != 'ready':
|
|
326
|
+
error = 'controller did not reply ready'
|
|
342
327
|
stage = 'controller'
|
|
343
328
|
break
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
329
|
+
attempted += 1
|
|
330
|
+
up_start = None
|
|
331
|
+
t_can_ep = 0.0
|
|
332
|
+
failed = False
|
|
333
|
+
for step in range(MAX_STEPS):
|
|
334
|
+
if time.monotonic() > control_deadline:
|
|
335
|
+
error = 'control evaluation deadline exceeded'
|
|
336
|
+
stage = 'simulation'
|
|
337
|
+
failed = True
|
|
338
|
+
break
|
|
339
|
+
if not isinstance(obs, list) or len(obs) != 3:
|
|
340
|
+
error = 'simulator returned bad observation'
|
|
341
|
+
stage = 'simulation'
|
|
342
|
+
failed = True
|
|
343
|
+
break
|
|
344
|
+
try:
|
|
345
|
+
angles = [float(x) for x in obs]
|
|
346
|
+
true_angles = [float(x) for x in true] if isinstance(true, list) and len(true) == 3 else angles
|
|
347
|
+
except Exception:
|
|
348
|
+
error = 'simulator returned non-numeric observation'
|
|
349
|
+
stage = 'simulation'
|
|
350
|
+
failed = True
|
|
351
|
+
break
|
|
352
|
+
if (not all(math.isfinite(x) for x in angles)
|
|
353
|
+
or not all(math.isfinite(x) for x in true_angles)):
|
|
354
|
+
error = 'simulator returned non-finite observation'
|
|
355
|
+
stage = 'simulation'
|
|
356
|
+
failed = True
|
|
357
|
+
break
|
|
358
|
+
# Success is judged on the true state; the controller only sees
|
|
359
|
+
# the noisy angles below.
|
|
360
|
+
if max(abs(x) for x in true_angles) <= UP_TOL:
|
|
361
|
+
if up_start is None:
|
|
362
|
+
up_start = step * DT
|
|
363
|
+
t_can_ep = max(t_can_ep, step * DT - up_start)
|
|
364
|
+
else:
|
|
365
|
+
up_start = None
|
|
366
|
+
if t_can_ep >= T_TGT:
|
|
367
|
+
break
|
|
368
|
+
write_line(control, {'type': 'step', 'step': step, 'state': angles})
|
|
369
|
+
try:
|
|
370
|
+
action = json.loads(control_reader.readline(0.1))
|
|
371
|
+
except Exception as exc:
|
|
372
|
+
error = 'controller did not reply in time: ' + str(exc)
|
|
373
|
+
stage = 'controller'
|
|
374
|
+
failed = True
|
|
375
|
+
break
|
|
376
|
+
torque = action.get('torque') if isinstance(action, dict) else None
|
|
377
|
+
if (not isinstance(torque, list) or len(torque) != 2
|
|
378
|
+
or not all(isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
379
|
+
and math.isfinite(v) for v in torque)
|
|
380
|
+
or any(abs(float(v)) > 1.0 for v in torque)):
|
|
381
|
+
error = 'controller returned invalid torque'
|
|
382
|
+
stage = 'controller'
|
|
383
|
+
failed = True
|
|
384
|
+
break
|
|
385
|
+
write_line(sim, {'cmd': 'step', 'torque': [float(torque[0]), float(torque[1])]})
|
|
386
|
+
try:
|
|
387
|
+
reply = json.loads(sim_reader.readline(10.0))
|
|
388
|
+
except Exception as exc:
|
|
389
|
+
error = 'simulator did not reply: ' + str(exc)
|
|
390
|
+
stage = 'simulation'
|
|
391
|
+
failed = True
|
|
392
|
+
break
|
|
393
|
+
if not isinstance(reply, dict):
|
|
394
|
+
error = 'simulator returned bad reply'
|
|
395
|
+
stage = 'simulation'
|
|
396
|
+
failed = True
|
|
397
|
+
break
|
|
398
|
+
if 'error' in reply:
|
|
399
|
+
error = 'simulator error: ' + str(reply['error'])
|
|
400
|
+
stage = 'simulation'
|
|
401
|
+
failed = True
|
|
402
|
+
break
|
|
403
|
+
obs = reply.get('obs')
|
|
404
|
+
true = reply.get('state', obs)
|
|
405
|
+
if failed:
|
|
354
406
|
break
|
|
355
|
-
if
|
|
356
|
-
|
|
407
|
+
if t_can_ep >= T_TGT:
|
|
408
|
+
successes += 1
|
|
409
|
+
t_can_total += t_can_ep
|
|
410
|
+
if attempted == 0:
|
|
411
|
+
if stage == 'scoring':
|
|
357
412
|
stage = 'simulation'
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
413
|
+
error = error or 'no evaluation episode could be executed'
|
|
414
|
+
success_rate, t_can = 0.0, 0.0
|
|
415
|
+
else:
|
|
416
|
+
success_rate = successes / attempted
|
|
417
|
+
t_can = t_can_total / attempted
|
|
418
|
+
completed = (stage == 'scoring' and attempted == EPISODES
|
|
419
|
+
and success_rate >= SUCCESS_TARGET)
|
|
420
|
+
stage = 'completed' if completed else stage
|
|
361
421
|
result = build_result(stage, workspace, events, error=error, ops=ops, source=source_name,
|
|
362
|
-
t_can=t_can, success=completed
|
|
422
|
+
t_can=t_can, success=completed, success_rate=success_rate,
|
|
423
|
+
episodes=attempted)
|
|
363
424
|
result['infra_ok'] = True
|
|
364
425
|
return emit(result)
|
|
365
426
|
except Exception as exc:
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-eval-guard",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Evaluation guard for DSH: workspace-only filesystem, no network/subagents, wall-clock limits, and task installation for meow-ai-arena and benchmark tasks.",
|
|
6
6
|
"license": "MIT",
|