dsh-eval-guard 0.7.2 → 0.7.4

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 (63) hide show
  1. package/ISSUES.md +239 -0
  2. package/README.md +48 -18
  3. package/assets/neural-cifar/workspace.zip +0 -0
  4. package/assets/neural-cpp/workspace.zip +0 -0
  5. package/assets/neural-fp32/workspace.zip +0 -0
  6. package/assets/neural-pendulum/SPEC.md +43 -35
  7. package/assets/neural-pendulum/TASK.md +139 -33
  8. package/assets/neural-pendulum/rl-feasibility/README.md +60 -0
  9. package/assets/neural-pendulum/rl-feasibility/RESULTS.md +370 -0
  10. package/assets/neural-pendulum/rl-feasibility/evaluate.py +107 -0
  11. package/assets/neural-pendulum/rl-feasibility/finalize_run.py +168 -0
  12. package/assets/neural-pendulum/rl-feasibility/models.py +124 -0
  13. package/assets/neural-pendulum/rl-feasibility/obs.py +205 -0
  14. package/assets/neural-pendulum/rl-feasibility/plot_selected.py +55 -0
  15. package/assets/neural-pendulum/rl-feasibility/reference_controller.py +112 -0
  16. package/assets/neural-pendulum/rl-feasibility/results/curves_selected.png +0 -0
  17. package/assets/neural-pendulum/rl-feasibility/results/reference_controller.json +417 -0
  18. package/assets/neural-pendulum/rl-feasibility/results/summary.json +1599 -0
  19. package/assets/neural-pendulum/rl-feasibility/results/table.md +39 -0
  20. package/assets/neural-pendulum/rl-feasibility/sim.py +324 -0
  21. package/assets/neural-pendulum/rl-feasibility/summarize.py +175 -0
  22. package/assets/neural-pendulum/rl-feasibility/task.py +342 -0
  23. package/assets/neural-pendulum/rl-feasibility/test_contract.py +188 -0
  24. package/assets/neural-pendulum/rl-feasibility/train_ppo.py +403 -0
  25. package/assets/neural-pendulum/rl-feasibility/train_sac.py +406 -0
  26. package/assets/neural-pendulum/sim.cpp +13 -2
  27. package/assets/neural-pendulum/sim_eval +0 -0
  28. package/assets/neural-pendulum/workspace.zip +0 -0
  29. package/eval/README.md +19 -9
  30. package/eval/bomberman.py +5 -3
  31. package/eval/commit0.py +10 -3
  32. package/eval/common.py +169 -5
  33. package/eval/ctf.py +4 -2
  34. package/eval/featurebench.py +12 -8
  35. package/eval/land.py +4 -2
  36. package/eval/legacy/bomberman.py +5 -2
  37. package/eval/legacy/ctf.py +5 -2
  38. package/eval/legacy/featurebench.py +55 -15
  39. package/eval/legacy/land.py +6 -3
  40. package/eval/legacy/swe_bench_pro.py +57 -16
  41. package/eval/legacy/tank.py +2 -2
  42. package/eval/legacy/warehouse.py +4 -4
  43. package/eval/neural-cpp.py +9 -6
  44. package/eval/neural-fp32.py +17 -5
  45. package/eval/swe-bench-pro.py +11 -7
  46. package/eval/tank.py +11 -2
  47. package/eval/warehouse.py +17 -1
  48. package/lib/client/index.js +56 -29
  49. package/lib/client/index.js.map +1 -1
  50. package/lib/index.js +883 -298
  51. package/native/dsh-eval-supervisor +0 -0
  52. package/native/supervisor.c +232 -36
  53. package/neural-cifar-eval/README.md +5 -0
  54. package/neural-cifar-eval/common.py +13 -0
  55. package/neural-cifar-eval/ops_eval.py +17 -5
  56. package/neural-cifar-eval/run_eval.py +42 -32
  57. package/neural-pendulum-eval/README.md +62 -0
  58. package/neural-pendulum-eval/ops_eval.py +603 -0
  59. package/neural-pendulum-eval/run_eval.py +218 -137
  60. package/neural-pendulum-eval/sim_eval +0 -0
  61. package/package.json +7 -11
  62. package/assets/neural-pendulum/hidden/test_infra.cpp +0 -631
  63. package/assets/neural-pendulum/include/nn/nn.hpp +0 -88
@@ -0,0 +1,406 @@
1
+ """SAC + 2x256 tanh MLP + sin/cos H=4 observation, angle-only simulator.
2
+
3
+ Experiments:
4
+ --init rand : theta_i ~ U(-.05,.05), omega_i ~ U(-.02,.02), no bias
5
+ --init fixed : training reset is exactly theta=[0.013,-0.008,0.021], omega=0
6
+
7
+ 32 vectorised environments by default, replay buffer, auto entropy tuning,
8
+ shared-trunk twin Q, UTD = 0.25 (8 gradient steps per 32-env vector step).
9
+ The simulator exposes angles only; observation, reward, termination, success
10
+ and t_can all live in trainer-side code (obs.py / task.py).
11
+
12
+ TensorBoard scalars (tb/<exp>/):
13
+ train/episode_return, train/episode_length, train/t_can, train/t_can_max,
14
+ train/success_rate, train/ever_up_frac, train/first_up_time, train/up_frac,
15
+ train/action_abs_mean, train/replay_size, train/steps_per_s, train/alpha,
16
+ train/entropy, train/q_mean, train/q_target_mean,
17
+ loss/actor, loss/critic, loss/alpha, loss/policy, loss/value, loss/entropy,
18
+ eval/t_can, eval/success, eval/ep_return, eval/ever_up, eval/final_ang_err,
19
+ eval/first_up_t, eval/up_frac, eval_final/*, time/elapsed_min
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import copy
25
+ import json
26
+ import os
27
+ import time
28
+
29
+ import numpy as np
30
+ import torch
31
+ import torch.nn as nn
32
+ import torch.nn.functional as F
33
+ from torch.utils.tensorboard import SummaryWriter
34
+
35
+ torch.set_num_threads(int(os.environ.get("TORCH_THREADS", "4")))
36
+
37
+ from evaluate import evaluate, evaluate_starts, make_sac_policy
38
+ from models import SquashedGaussianActor, TwinQ
39
+ from obs import OBS_MODES, obs_spec
40
+ from sim import EPISODE_STEPS, Params, TorchVecSim
41
+ from task import (TORCH_REWARD_NOTE, TorchTask, get_reward_preset, make_init,
42
+ sample_train_init)
43
+
44
+
45
+ class Replay:
46
+ def __init__(self, cap, dim, act_dim=2):
47
+ self.cap = int(cap)
48
+ self.obs = np.zeros((cap, dim), np.float32)
49
+ self.act = np.zeros((cap, act_dim), np.float32)
50
+ self.rew = np.zeros((cap, 1), np.float32)
51
+ self.nobs = np.zeros((cap, dim), np.float32)
52
+ self.done = np.zeros((cap, 1), np.float32)
53
+ self.ptr = 0
54
+ self.size = 0
55
+
56
+ def push(self, obs, act, rew, nobs, done):
57
+ n = obs.shape[0]
58
+ idx = (self.ptr + np.arange(n)) % self.cap
59
+ self.obs[idx] = obs
60
+ self.act[idx] = act
61
+ self.rew[idx] = np.asarray(rew).reshape(n, 1)
62
+ self.nobs[idx] = nobs
63
+ self.done[idx] = np.asarray(done).reshape(n, 1)
64
+ self.ptr = (self.ptr + n) % self.cap
65
+ self.size = min(self.cap, self.size + n)
66
+
67
+ def sample(self, batch):
68
+ idx = np.random.randint(0, self.size, size=batch)
69
+ return (torch.from_numpy(self.obs[idx]),
70
+ torch.from_numpy(self.act[idx]),
71
+ torch.from_numpy(self.rew[idx]),
72
+ torch.from_numpy(self.nobs[idx]),
73
+ torch.from_numpy(self.done[idx]))
74
+
75
+
76
+ def soft_update(tgt, src, tau):
77
+ with torch.no_grad():
78
+ for a, b in zip(tgt.parameters(), src.parameters()):
79
+ a.mul_(1 - tau).add_(tau * b)
80
+
81
+
82
+ def sac_update(actor, q, qt, act_opt, q_opt, log_alpha, alpha_opt, replay, args):
83
+ o, ac, r, no, d = replay.sample(args.batch)
84
+ with torch.no_grad():
85
+ na, nlogp = actor.sample(no)
86
+ q_next = qt(no, na).min(dim=1).values
87
+ target = r.squeeze(-1) + args.gamma * (1.0 - d.squeeze(-1)) * (
88
+ q_next - log_alpha.exp() * nlogp)
89
+ q_pred = q(o, ac)
90
+ critic_loss = 0.5 * F.mse_loss(q_pred, target.unsqueeze(1).expand(-1, 2))
91
+ q_opt.zero_grad()
92
+ critic_loss.backward()
93
+ nn.utils.clip_grad_norm_(q.parameters(), 10.0)
94
+ q_opt.step()
95
+
96
+ for p_ in q.parameters():
97
+ p_.requires_grad_(False)
98
+ pa, logp = actor.sample(o)
99
+ q_pi = q(o, pa).min(dim=1).values
100
+ actor_loss = (log_alpha.exp().detach() * logp - q_pi).mean()
101
+ act_opt.zero_grad()
102
+ actor_loss.backward()
103
+ nn.utils.clip_grad_norm_(actor.parameters(), 10.0)
104
+ act_opt.step()
105
+ for p_ in q.parameters():
106
+ p_.requires_grad_(True)
107
+
108
+ alpha_loss = -(log_alpha.exp() * (logp.detach() + args.target_entropy)).mean()
109
+ alpha_opt.zero_grad()
110
+ alpha_loss.backward()
111
+ alpha_opt.step()
112
+
113
+ soft_update(qt, q, args.tau)
114
+ return dict(actor=float(actor_loss), critic=float(critic_loss),
115
+ alpha=float(alpha_loss),
116
+ q=float(q_pred.mean()), qt=float(target.mean()),
117
+ entropy=float(-logp.detach().mean()))
118
+
119
+
120
+ def main():
121
+ ap = argparse.ArgumentParser()
122
+ ap.add_argument("--exp", type=str, required=True)
123
+ ap.add_argument("--seed", type=int, default=0)
124
+ ap.add_argument("--init", type=str, default="rand",
125
+ choices=["rand", "fixed", "mix"],
126
+ help="rand=down noise, fixed=eval state, "
127
+ "mix=curriculum with up_frac near upright")
128
+ ap.add_argument("--up-frac", type=float, default=0.5,
129
+ help="fraction of resets near upright when --init mix")
130
+ ap.add_argument("--obs", type=str, default="full",
131
+ choices=sorted(OBS_MODES),
132
+ help="observation mode (see obs.py)")
133
+ ap.add_argument("--reward", type=str, default="base",
134
+ choices=["base", "sparse", "energy"],
135
+ help="reward preset (see task.py REWARD_PRESETS)")
136
+ ap.add_argument("--envs", type=int, default=32)
137
+ ap.add_argument("--minutes", type=float, default=30.0)
138
+ ap.add_argument("--steps", type=int, default=10 ** 12)
139
+ ap.add_argument("--hidden", type=int, default=256)
140
+ ap.add_argument("--depth", type=int, default=2)
141
+ ap.add_argument("--lr", type=float, default=3e-4)
142
+ ap.add_argument("--gamma", type=float, default=0.99)
143
+ ap.add_argument("--tau", type=float, default=0.005)
144
+ ap.add_argument("--batch", type=int, default=256)
145
+ ap.add_argument("--warmup", type=int, default=5000)
146
+ ap.add_argument("--buffer", type=int, default=300000)
147
+ ap.add_argument("--utd", type=float, default=0.25,
148
+ help="gradient steps per environment step")
149
+ ap.add_argument("--target-entropy", type=float, default=-2.0)
150
+ ap.add_argument("--alpha-init", type=float, default=0.2)
151
+ ap.add_argument("--episode-steps", type=int, default=EPISODE_STEPS)
152
+ ap.add_argument("--eval-every", type=int, default=20000)
153
+ ap.add_argument("--eval-start", type=int, default=20000)
154
+ ap.add_argument("--tb-root", type=str, default="tb")
155
+ ap.add_argument("--ckpt-dir", type=str, default="checkpoints")
156
+ ap.add_argument("--out", type=str, default="results")
157
+ ap.add_argument("--tau-max", type=float, default=18.0)
158
+ ap.add_argument("--damping", type=float, default=0.01)
159
+ ap.add_argument("--log-every", type=int, default=10,
160
+ help="log every N vector steps")
161
+ ap.add_argument("--robust-starts", type=int, default=8,
162
+ help="deterministic extra rollouts from the training "
163
+ "reset distribution at the end (0 disables)")
164
+ args = ap.parse_args()
165
+
166
+ torch.manual_seed(args.seed)
167
+ np.random.seed(args.seed)
168
+ for d in (args.tb_root, args.ckpt_dir, args.out):
169
+ os.makedirs(d, exist_ok=True)
170
+ exp = args.exp
171
+ writer = SummaryWriter(os.path.join(args.tb_root, exp))
172
+ p = Params(tau_max=args.tau_max, b=args.damping)
173
+ spec = obs_spec(args.obs)
174
+ obs_dim = spec["dim"]
175
+ reward_w = get_reward_preset(args.reward)
176
+ sim = TorchVecSim(p, n=args.envs, episode_steps=args.episode_steps)
177
+ task = TorchTask(p, n=args.envs, reward_w=reward_w, obs_mode=args.obs)
178
+ gen = torch.Generator().manual_seed(args.seed * 1000 + 11)
179
+ updates_per_vec = max(1, int(round(args.utd * args.envs)))
180
+ eff_utd = updates_per_vec / args.envs
181
+
182
+ init_meta = dict(mode=args.init,
183
+ training=("theta_i ~ U(-0.05,0.05), omega_i ~ U(-0.02,0.02); no bias"
184
+ if args.init == "rand" else
185
+ ("theta=[0.013,-0.008,0.021], omega=0 (same as eval)"
186
+ if args.init == "fixed" else
187
+ f"curriculum: {args.up_frac:.2f} near upright "
188
+ f"(pi + U(-0.05,0.05)), rest near down")),
189
+ eval="theta=[0.013,-0.008,0.021], omega=0 (fixed, no noise)")
190
+ meta = dict(algo="sac", exp=exp, seed=args.seed, envs=args.envs,
191
+ obs=spec, obs_dim=obs_dim, init=init_meta,
192
+ reward=reward_w, reward_preset=args.reward,
193
+ reward_note=TORCH_REWARD_NOTE,
194
+ updates_per_vector=updates_per_vec, effective_utd=eff_utd,
195
+ args=vars(args))
196
+ writer.add_text("config", json.dumps(meta, indent=2), 0)
197
+ print(json.dumps(meta, indent=2), flush=True)
198
+
199
+ actor = SquashedGaussianActor(obs_dim, 2, hidden=args.hidden, depth=args.depth)
200
+ q = TwinQ(obs_dim, 2, hidden=args.hidden, depth=args.depth)
201
+ qt = copy.deepcopy(q)
202
+ act_opt = torch.optim.Adam(actor.parameters(), lr=args.lr)
203
+ q_opt = torch.optim.Adam(q.parameters(), lr=args.lr)
204
+ log_alpha = torch.log(torch.tensor(args.alpha_init)).clone().detach().requires_grad_(True)
205
+ alpha_opt = torch.optim.Adam([log_alpha], lr=args.lr)
206
+
207
+ replay = Replay(args.buffer, obs_dim, 2)
208
+ th, om = make_init(args.envs, args.init, gen, up_frac=args.up_frac)
209
+ theta = sim.reset(th, om)
210
+ obs = task.reset(theta)
211
+
212
+ t_start = time.time()
213
+ total = 0
214
+ n_updates = 0
215
+ vec_steps = 0
216
+ next_eval = args.eval_start
217
+ best_t_can = -1.0
218
+ best_eval = None
219
+ best_path = None
220
+ last_path = None
221
+ eval_curve = []
222
+ last = dict(actor=float("nan"), critic=float("nan"), alpha=float("nan"),
223
+ q=float("nan"), qt=float("nan"), entropy=float("nan"))
224
+ ep = {k: [] for k in ("return", "t_can", "ever_up", "first_up", "up_frac", "length")}
225
+
226
+ def save_ckpt(path, ev):
227
+ torch.save(dict(model=actor.state_dict(), q=q.state_dict(),
228
+ log_alpha=log_alpha.detach(), obs=spec,
229
+ obs_dim=obs_dim, init=init_meta, reward=reward_w,
230
+ reward_preset=args.reward,
231
+ args=vars(args), global_steps=total,
232
+ n_updates=n_updates, eval=ev, algo="sac"),
233
+ os.path.join(args.ckpt_dir, path))
234
+
235
+ def log_eval(ev):
236
+ nonlocal best_t_can, best_eval, best_path, last_path
237
+ eval_curve.append(dict(global_steps=total, n_updates=n_updates, **ev))
238
+ for k, v in ev.items():
239
+ if isinstance(v, (int, float)):
240
+ writer.add_scalar(f"eval/{k}", v, total)
241
+ if (ev["t_can"] > best_t_can
242
+ or (ev["t_can"] == best_t_can and best_eval is not None
243
+ and ev["ep_return"] > best_eval["ep_return"])):
244
+ best_t_can = ev["t_can"]
245
+ best_eval = dict(ev)
246
+ best_path = exp + "_best.pt"
247
+ save_ckpt(best_path, ev)
248
+ last_path = exp + "_last.pt"
249
+ save_ckpt(last_path, ev)
250
+
251
+ while total < args.steps:
252
+ if (time.time() - t_start) / 60.0 > args.minutes:
253
+ print("time budget reached", flush=True)
254
+ break
255
+ if replay.size < args.warmup:
256
+ u = torch.rand(args.envs, 2) * 2.0 - 1.0
257
+ else:
258
+ with torch.no_grad():
259
+ u, _ = actor.sample(obs)
260
+ u = torch.clamp(u, -1.0, 1.0)
261
+ theta = sim.step(u)
262
+ nobs, rew = task.step(theta, u)
263
+ done = sim.steps >= sim.episode_steps
264
+ replay.push(obs.detach().cpu().numpy(), u.detach().cpu().numpy(),
265
+ rew.detach().cpu().numpy(), nobs.detach().cpu().numpy(),
266
+ done.detach().cpu().numpy())
267
+ if bool(done.any()):
268
+ idx = done.nonzero().flatten()
269
+ summ = task.summary(idx)
270
+ ep["return"] += summ["ep_return"].tolist()
271
+ ep["t_can"] += summ["t_can"].tolist()
272
+ ep["ever_up"] += summ["ever_up"].tolist()
273
+ ep["first_up"] += summ["first_up_t"].tolist()
274
+ ep["up_frac"] += summ["up_frac"].tolist()
275
+ ep["length"] += summ["length"].tolist()
276
+ th, om = make_init(idx.numel(), args.init, gen, up_frac=args.up_frac)
277
+ reset_theta = sim.reset(th, om, idx)
278
+ task.reset(reset_theta, idx)
279
+ nobs = task.obs
280
+ obs = nobs
281
+ total += args.envs
282
+ vec_steps += 1
283
+
284
+ if replay.size >= args.warmup:
285
+ for _ in range(updates_per_vec):
286
+ last = sac_update(actor, q, qt, act_opt, q_opt, log_alpha,
287
+ alpha_opt, replay, args)
288
+ n_updates += 1
289
+
290
+ if vec_steps % args.log_every == 0:
291
+ elapsed = time.time() - t_start
292
+ if ep["return"]:
293
+ writer.add_scalar("train/episode_return",
294
+ float(np.mean(ep["return"])), total)
295
+ writer.add_scalar("train/episode_length",
296
+ float(np.mean(ep["length"])), total)
297
+ writer.add_scalar("train/t_can", float(np.mean(ep["t_can"])), total)
298
+ writer.add_scalar("train/t_can_max", float(np.max(ep["t_can"])), total)
299
+ writer.add_scalar("train/success_rate",
300
+ float(np.mean(np.asarray(ep["t_can"]) >= 10.0)), total)
301
+ writer.add_scalar("train/ever_up_frac",
302
+ float(np.mean(ep["ever_up"])), total)
303
+ writer.add_scalar("train/up_frac",
304
+ float(np.mean(ep["up_frac"])), total)
305
+ fu = [x for x in ep["first_up"] if x > 0]
306
+ if fu:
307
+ writer.add_scalar("train/first_up_time", float(np.mean(fu)), total)
308
+ ep = {k: [] for k in ep}
309
+ writer.add_scalar("train/replay_size", replay.size, total)
310
+ writer.add_scalar("train/steps_per_s", total / max(elapsed, 1e-9), total)
311
+ writer.add_scalar("train/alpha", float(log_alpha.detach().exp()), total)
312
+ writer.add_scalar("train/action_abs_mean", float(u.abs().mean()), total)
313
+ writer.add_scalar("train/action_std", float(u.std()), total)
314
+ writer.add_scalar("loss/actor", last["actor"], total)
315
+ writer.add_scalar("loss/critic", last["critic"], total)
316
+ writer.add_scalar("loss/alpha", last["alpha"], total)
317
+ writer.add_scalar("loss/policy", last["actor"], total)
318
+ writer.add_scalar("loss/value", last["critic"], total)
319
+ writer.add_scalar("loss/entropy", last["entropy"], total)
320
+ writer.add_scalar("train/entropy", last["entropy"], total)
321
+ writer.add_scalar("train/q_mean", last["q"], total)
322
+ writer.add_scalar("train/q_target_mean", last["qt"], total)
323
+ writer.add_scalar("train/global_steps", total, total)
324
+ writer.add_scalar("time/elapsed_min", elapsed / 60.0, total)
325
+ writer.flush()
326
+
327
+ if total >= next_eval:
328
+ next_eval += args.eval_every
329
+ ev = evaluate(make_sac_policy(actor), p, args.episode_steps,
330
+ reward_w=reward_w, obs_mode=args.obs)
331
+ log_eval(ev)
332
+ print(f"steps {total/1e3:7.1f}k upd {n_updates:6d} "
333
+ f"EVAL tc {ev['t_can']:6.2f} succ {ev['success']:.0f} "
334
+ f"everup {ev['ever_up']:.0f} angerr {ev['final_ang_err']:.3f} "
335
+ f"ret {ev['ep_return']:8.1f} q {last['q']:8.2f} "
336
+ f"a {float(log_alpha.detach().exp()):.2f} "
337
+ f"({time.time()-t_start:.0f}s)", flush=True)
338
+ writer.flush()
339
+
340
+ ev_final = evaluate(make_sac_policy(actor), p, args.episode_steps,
341
+ reward_w=reward_w, obs_mode=args.obs)
342
+ eval_curve.append(dict(global_steps=total, n_updates=n_updates, **ev_final))
343
+ for k, v in ev_final.items():
344
+ if isinstance(v, (int, float)):
345
+ writer.add_scalar(f"eval_final/{k}", v, total)
346
+ final_path = exp + "_final.pt"
347
+ save_ckpt(final_path, ev_final)
348
+ last_path = exp + "_last.pt"
349
+ save_ckpt(last_path, ev_final)
350
+
351
+ # Extra deterministic rollouts from the training reset distribution. This
352
+ # is NOT the mandated fixed-state eval; it checks generalisation.
353
+ rob = None
354
+ rob_best = None
355
+ if args.robust_starts > 0:
356
+ g = torch.Generator().manual_seed(args.seed * 1000 + 123)
357
+ th_r, om_r = sample_train_init(args.robust_starts, gen=g)
358
+ starts = list(zip(th_r.numpy(), om_r.numpy()))
359
+ rob = evaluate_starts(make_sac_policy(actor), p, starts,
360
+ args.episode_steps, reward_w=reward_w,
361
+ obs_mode=args.obs)
362
+ for k, v in rob.items():
363
+ if isinstance(v, (int, float)):
364
+ writer.add_scalar(f"robust/{k}", v, total)
365
+ if best_path is not None:
366
+ ck = torch.load(os.path.join(args.ckpt_dir, best_path),
367
+ map_location="cpu")
368
+ actor_best = SquashedGaussianActor(obs_dim, 2, hidden=args.hidden,
369
+ depth=args.depth)
370
+ actor_best.load_state_dict(ck["model"])
371
+ rob_best = evaluate_starts(make_sac_policy(actor_best), p, starts,
372
+ args.episode_steps, reward_w=reward_w,
373
+ obs_mode=args.obs)
374
+ for k, v in rob_best.items():
375
+ if isinstance(v, (int, float)):
376
+ writer.add_scalar(f"robust_best/{k}", v, total)
377
+
378
+ wall = time.time() - t_start
379
+ out = dict(algo="sac", exp=exp, seed=args.seed, envs=args.envs,
380
+ obs=spec, obs_dim=obs_dim, init=init_meta,
381
+ reward=reward_w, reward_preset=args.reward,
382
+ reward_note=TORCH_REWARD_NOTE,
383
+ args=vars(args), updates_per_vector=updates_per_vec,
384
+ effective_utd=eff_utd, minutes_budget=args.minutes,
385
+ global_steps=total, gradient_steps=n_updates,
386
+ wall_time_s=wall, steps_per_s=total / max(wall, 1e-9),
387
+ updates_per_s=n_updates / max(wall, 1e-9),
388
+ final_eval=ev_final, best_eval=best_eval, best_t_can=best_t_can,
389
+ eval_curve=eval_curve, robust=rob, robust_best=rob_best,
390
+ final_train=last,
391
+ checkpoints=dict(best=best_path, last=last_path,
392
+ final=final_path))
393
+ with open(os.path.join(args.out, exp + ".json"), "w") as f:
394
+ json.dump(out, f, indent=2)
395
+ writer.flush()
396
+ writer.close()
397
+ print("DONE " + json.dumps(dict(
398
+ exp=exp, init=args.init, seed=args.seed, envs=args.envs,
399
+ global_steps=total, gradient_steps=n_updates, wall_s=round(wall, 1),
400
+ steps_per_s=round(out["steps_per_s"], 1),
401
+ eval_t_can=ev_final["t_can"], best_t_can=best_t_can,
402
+ eval_return=ev_final["ep_return"])), flush=True)
403
+
404
+
405
+ if __name__ == "__main__":
406
+ main()
@@ -65,7 +65,13 @@ static void deriv(const State& s, const std::array<double,N>& tau, State& out) {
65
65
  std::array<double,N> C{}, G{}, Q{}, rhs{};
66
66
  for(int i=0;i<N;i++){
67
67
  double ci=0;
68
- for(int j=0;j<N;j++) if(j!=i) ci += M[i][j] * std::sin(s.theta[i]-s.theta[j]) * s.omega[j]*s.omega[j];
68
+ 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
+ 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];
74
+ }
69
75
  C[i]=ci;
70
76
  G[i]=GRAV*std::sin(s.theta[i])*GCOEF[i];
71
77
  }
@@ -101,7 +107,12 @@ int main(int argc, char** argv) {
101
107
  // parse torque array
102
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); } }
103
109
  u1=std::max(-1.0,std::min(1.0,u1)); u3=std::max(-1.0,std::min(1.0,u3));
104
- std::array<double,N> tau{{u1*TAU_MAX,0.0,u3*TAU_MAX}};
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}};
105
116
  // one control step with 4 RK4 substeps
106
117
  double h=DT/SUB;
107
118
  for(int sub=0;sub<SUB;sub++){
Binary file
package/eval/README.md CHANGED
@@ -4,15 +4,25 @@
4
4
 
5
5
  | 任务 | 脚本 | 评分 |
6
6
  |---|---|---|
7
- | tank | eval/tank.py | 官方引擎 + BT,对基线 |
8
- | warehouse | eval/warehouse.py | 正确性 + 相对基线加速比 |
9
- | land | eval/land.py | 官方 round_robin/rate |
10
- | bomberman | eval/bomberman.py | 官方 tournament/run |
11
- | ctf | eval/ctf.py | 官方 arena plan/run/rate |
12
- | commit0 | eval/commit0.py | 单元测试通过率 |
13
- | swe-bench-pro | eval/swe-bench-pro.py | 隐藏 F2P/P2P 测试 |
14
- | featurebench | eval/featurebench.py | 隐藏 F2P/P2P 测试;全部通过才 resolved |
7
+ | tank | eval/tank.py | 官方引擎 + BT,对基线(quick 20 局) |
8
+ | warehouse | eval/warehouse.py | 正确性 + 相对基线加速比(quick 仅 pub_small) |
9
+ | land | eval/land.py | 官方 round_robin/rate(quick 仅单挑 2 图) |
10
+ | bomberman | eval/bomberman.py | 官方 tournament/run(quick 仅 2 人 1 种子) |
11
+ | ctf | eval/ctf.py | 官方 arena plan/run/rate(quick 仅 2/3 方) |
12
+ | commit0 | eval/commit0.py | 单元测试通过率(测试来自安装快照) |
13
+ | swe-bench-pro | eval/swe-bench-pro.py | 隐藏 F2P/P2P 节点全部通过才 resolved |
14
+ | featurebench | eval/featurebench.py | 隐藏 feature 测试 + 全部回归文件通过才 resolved |
15
+ | neural-cpp | eval/neural-cpp.py | 隐藏 C++ 测试;accuracy / matrixAccuracy / speedGeomean / score |
16
+ | neural-fp32 | eval/neural-fp32.py | 逐操作 MRE/阈值/速度;无统一 score |
17
+ | neural-cifar | eval/neural-cifar.py | FP32 算子指标 + 隐藏验证集准确率/FPS(独立目录) |
18
+ | neural-pendulum | eval/neural-pendulum.py | FP32 算子指标 + 固定初态控制 t_can(独立目录) |
15
19
 
16
20
  eval/legacy/ 保存从独立评测目录迁移过来的评测器;eval/arena/ 保存官方引擎、裁判和历史对手。common.py 负责临时工作区、结果解析和结果文件写入。
17
21
 
18
- 后台任务由 host 的 JobManager 执行,日志和结果保存在 $DSH_HOME/eval-guard/jobs/<job-id>/。
22
+ ## 完整性约束(宿主注入)
23
+
24
+ - `EVAL_BASELINE_DIR`:安装成功时快照的测试/配置目录。commit0/SWE/FeatureBench 的 wrapper 会在评测前用它覆盖候选副本,并删除候选新增的 `conftest.py`;同时校验 `.venv` 指纹(`venv.json`)。
25
+ - `EVAL_RESULT_FILE` / `EVAL_JOB_DIR`:只由顶层 evaluator 使用;所有 evaluator 再启动子进程时会剥掉这些变量,候选二进制拿不到结果文件路径。
26
+ - 隐藏资产(neural-cpp 的隐藏测试、CIFAR 的验证标签)在候选构建/推理开始前读入内存;CIFAR 的图像副本会放到独立临时目录,标签不落盘到候选可见路径。
27
+
28
+ 后台任务由 host 的 JobManager 执行,日志和结果保存在 $DSH_HOME/eval-guard/jobs/<job-id>/。Job 子进程的环境变量已收敛为白名单(PATH/HOME/LANG/LC_*/TMPDIR/SSL_* 等)。
package/eval/bomberman.py CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env python3
2
- """Single-workspace tank evaluator wrapper."""
2
+ """Single-workspace bomberman evaluator wrapper."""
3
3
  from __future__ import annotations
4
4
  import shutil, tempfile
5
5
  from pathlib import Path
6
6
  from common import EVAL_ROOT, candidate_row, emit, fail, parse_workspace, run_legacy
7
7
 
8
8
  workspace = parse_workspace()
9
- with tempfile.TemporaryDirectory(prefix='bomber-eval-') as td:
9
+ with tempfile.TemporaryDirectory(prefix='bomberman-eval-') as td:
10
10
  temp = Path(td)
11
11
  bot = workspace / 'submission' / 'bot.cpp'
12
12
  if not bot.is_file():
@@ -17,4 +17,6 @@ with tempfile.TemporaryDirectory(prefix='bomber-eval-') as td:
17
17
  if not result.get('ok'):
18
18
  raise SystemExit(fail({'task': 'bomberman', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
19
19
  row = candidate_row(result['summary'])
20
- emit({'task': 'bomberman', 'ok': True, 'score': row.get('score', row.get('rating', row.get('theta', 0))), 'rating': row.get('rating', row.get('theta', 0)), 'wins': row.get('wins', 0), 'losses': row.get('losses', 0), 'draws': row.get('draws', 0)})
20
+ if not row:
21
+ raise SystemExit(fail({'task': 'bomberman', 'ok': False, 'error': 'candidate row missing from evaluator summary'}))
22
+ emit({'task': 'bomberman', 'ok': True, 'score': row.get('score', row.get('rating', 0)), 'rating': row.get('rating', row.get('theta', 0)), 'theta': row.get('theta', 0)})
package/eval/commit0.py CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
  """Single-workspace Commit0 evaluator wrapper."""
3
3
  from __future__ import annotations
4
- import os, shutil, tempfile
4
+ import os, tempfile
5
5
  from pathlib import Path
6
- from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, run_legacy
6
+ from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, restore_baseline, run_legacy, verify_venv
7
7
 
8
8
  workspace = parse_workspace()
9
+ problem = verify_venv(workspace)
10
+ if problem is not None:
11
+ raise SystemExit(fail({'task': 'commit0', 'ok': False, 'error': problem}))
9
12
  with tempfile.TemporaryDirectory(prefix='commit0-eval-') as td:
10
13
  temp = Path(td)
11
14
  candidate = temp / 'candidate' / 'workspace'
@@ -13,7 +16,11 @@ with tempfile.TemporaryDirectory(prefix='commit0-eval-') as td:
13
16
  clean_copy(workspace, candidate, ignore_venv=True)
14
17
  venv = workspace / '.venv'
15
18
  if venv.is_dir():
16
- os.symlink(venv, candidate / '.venv')
19
+ try:
20
+ os.symlink(venv, candidate / '.venv')
21
+ except FileExistsError:
22
+ pass
23
+ restore_baseline(candidate)
17
24
  result = run_legacy(EVAL_ROOT / 'legacy' / 'commit0.py', ['--submissions', str(temp)], temp / 'out')
18
25
  if not result.get('ok'):
19
26
  raise SystemExit(fail({'task': 'commit0', 'ok': False, 'error': result.get('error', 'evaluation failed')}))