anhtld commited on
Commit
de5b6bd
·
verified ·
1 Parent(s): b82415b

Auto-sync: 2026-06-27 15:04:36 (part 3)

Browse files
scripts/eval_maniskill_policy_rollout.py CHANGED
@@ -38,12 +38,22 @@ def main(argv: list[str] | None = None) -> int:
38
  )
39
  parser.add_argument(
40
  "--selection-mode",
41
- choices=("policy", "field", "lattice", "retrieval_lattice"),
 
 
 
 
 
 
 
42
  default="policy",
43
  help="'policy' executes the deterministic policy mean; 'field' scores model-generated "
44
- "candidates with the learned interventional field; 'lattice' scores the current "
45
- "state's CIL action lattice without reading rewards; 'retrieval_lattice' scores "
46
- "the nearest train-state lattice for the current state.",
 
 
 
47
  )
48
  parser.add_argument(
49
  "--num-candidates",
@@ -63,6 +73,36 @@ def main(argv: list[str] | None = None) -> int:
63
  default=0,
64
  help="Base RNG seed for candidate perturbations.",
65
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  parser.add_argument(
67
  "--lattice-exclude-types",
68
  default="",
@@ -87,6 +127,11 @@ def main(argv: list[str] | None = None) -> int:
87
  num_candidates=args.num_candidates,
88
  candidate_sigma=args.candidate_sigma,
89
  selection_seed=args.selection_seed,
 
 
 
 
 
90
  lattice_exclude_types=lattice_exclude_types,
91
  )
92
  print(json.dumps({key: value for key, value in result.items() if key != "rows"}, indent=2))
 
38
  )
39
  parser.add_argument(
40
  "--selection-mode",
41
+ choices=(
42
+ "policy",
43
+ "field",
44
+ "field_optim",
45
+ "lattice",
46
+ "retrieval_lattice",
47
+ "retrieval_residual",
48
+ ),
49
  default="policy",
50
  help="'policy' executes the deterministic policy mean; 'field' scores model-generated "
51
+ "candidates with the learned interventional field; 'field_optim' additionally "
52
+ "optimizes model-generated candidates with projected action-space gradient ascent; "
53
+ "'lattice' scores the current state's CIL action lattice without reading rewards; "
54
+ "'retrieval_lattice' scores the nearest train-state lattice for the current state; "
55
+ "'retrieval_residual' translates nearest train-state counterfactual residuals around "
56
+ "the current policy mean before field scoring.",
57
  )
58
  parser.add_argument(
59
  "--num-candidates",
 
73
  default=0,
74
  help="Base RNG seed for candidate perturbations.",
75
  )
76
+ parser.add_argument(
77
+ "--field-optim-steps",
78
+ type=int,
79
+ default=0,
80
+ help="Projected gradient-ascent steps when selection-mode=field_optim.",
81
+ )
82
+ parser.add_argument(
83
+ "--field-optim-step-size",
84
+ type=float,
85
+ default=0.05,
86
+ help="Action-space step size for selection-mode=field_optim.",
87
+ )
88
+ parser.add_argument(
89
+ "--field-optim-trust-radius",
90
+ type=float,
91
+ default=0.5,
92
+ help="L-infinity trust radius around the policy proposal for field_optim.",
93
+ )
94
+ parser.add_argument(
95
+ "--field-optim-l2-penalty",
96
+ type=float,
97
+ default=0.0,
98
+ help="Mean squared action penalty around the policy proposal for field_optim.",
99
+ )
100
+ parser.add_argument(
101
+ "--retrieval-neighbors",
102
+ type=int,
103
+ default=1,
104
+ help="Nearest train states to use for retrieval_lattice/retrieval_residual proposals.",
105
+ )
106
  parser.add_argument(
107
  "--lattice-exclude-types",
108
  default="",
 
127
  num_candidates=args.num_candidates,
128
  candidate_sigma=args.candidate_sigma,
129
  selection_seed=args.selection_seed,
130
+ field_optim_steps=args.field_optim_steps,
131
+ field_optim_step_size=args.field_optim_step_size,
132
+ field_optim_trust_radius=args.field_optim_trust_radius,
133
+ field_optim_l2_penalty=args.field_optim_l2_penalty,
134
+ retrieval_neighbors=args.retrieval_neighbors,
135
  lattice_exclude_types=lattice_exclude_types,
136
  )
137
  print(json.dumps({key: value for key, value in result.items() if key != "rows"}, indent=2))
scripts/slurm/eval_h16_field_sweep.sbatch CHANGED
@@ -14,8 +14,9 @@
14
  set -euo pipefail
15
 
16
  # Field-guided h=16 rollout sweep.
17
- # Each job evaluates one seed/config pair by sampling model-generated action chunks,
18
- # scoring them with DoVLA's learned interventional field, and executing only the best.
 
19
 
20
  PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
21
  SCRATCH_ROOT="/scratch/$USER/dovla"
@@ -43,6 +44,11 @@ DATASET="${DATASET:-$SCRATCH_ROOT/experiments/six_task_h16_collection}"
43
  SOURCE_RUN_ROOT="${SOURCE_RUN_ROOT:-$SCRATCH_ROOT/experiments/dovla_h16_rollout_runs}"
44
  SOURCE_OBJECTIVE="${SOURCE_OBJECTIVE:-}"
45
  CHECKPOINT_NAME="${CHECKPOINT_NAME:-best.pt}"
 
 
 
 
 
46
  if [[ -z "${CHECKPOINT:-}" ]]; then
47
  if [[ -n "$SOURCE_OBJECTIVE" ]]; then
48
  CHECKPOINT="$SOURCE_RUN_ROOT/$SOURCE_OBJECTIVE/seed_$SEED/$CHECKPOINT_NAME"
@@ -75,6 +81,11 @@ echo "Seed: $SEED"
75
  echo "Candidates: $NUM_CANDIDATES"
76
  echo "Candidate sigma: $CANDIDATE_SIGMA"
77
  echo "Selection seed: $SELECTION_SEED"
 
 
 
 
 
78
  echo "Checkpoint: $CHECKPOINT"
79
  echo "Dataset: $DATASET"
80
  echo "Out: $OUT"
@@ -92,10 +103,14 @@ apptainer exec --nv --env "$ENVS" \
92
  --group-batch-size "$GROUP_BATCH_SIZE" \
93
  --sim-backend physx_cuda:0 \
94
  --render-backend cpu \
95
- --selection-mode field \
96
  --num-candidates "$NUM_CANDIDATES" \
97
  --candidate-sigma "$CANDIDATE_SIGMA" \
98
- --selection-seed "$SELECTION_SEED"
 
 
 
 
99
 
100
  echo ""
101
  echo "Field-guided rollout complete"
 
14
  set -euo pipefail
15
 
16
  # Field-guided h=16 rollout sweep.
17
+ # Each job evaluates one seed/config pair by generating deploy-clean action chunks,
18
+ # optionally optimizing them in action space with DoVLA's learned interventional field,
19
+ # and executing only the best.
20
 
21
  PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
22
  SCRATCH_ROOT="/scratch/$USER/dovla"
 
44
  SOURCE_RUN_ROOT="${SOURCE_RUN_ROOT:-$SCRATCH_ROOT/experiments/dovla_h16_rollout_runs}"
45
  SOURCE_OBJECTIVE="${SOURCE_OBJECTIVE:-}"
46
  CHECKPOINT_NAME="${CHECKPOINT_NAME:-best.pt}"
47
+ SELECTION_MODE="${SELECTION_MODE:-field}"
48
+ FIELD_OPTIM_STEPS="${FIELD_OPTIM_STEPS:-0}"
49
+ FIELD_OPTIM_STEP_SIZE="${FIELD_OPTIM_STEP_SIZE:-0.05}"
50
+ FIELD_OPTIM_TRUST_RADIUS="${FIELD_OPTIM_TRUST_RADIUS:-0.5}"
51
+ FIELD_OPTIM_L2_PENALTY="${FIELD_OPTIM_L2_PENALTY:-0.0}"
52
  if [[ -z "${CHECKPOINT:-}" ]]; then
53
  if [[ -n "$SOURCE_OBJECTIVE" ]]; then
54
  CHECKPOINT="$SOURCE_RUN_ROOT/$SOURCE_OBJECTIVE/seed_$SEED/$CHECKPOINT_NAME"
 
81
  echo "Candidates: $NUM_CANDIDATES"
82
  echo "Candidate sigma: $CANDIDATE_SIGMA"
83
  echo "Selection seed: $SELECTION_SEED"
84
+ echo "Selection mode: $SELECTION_MODE"
85
+ echo "Field optim steps: $FIELD_OPTIM_STEPS"
86
+ echo "Field optim step size: $FIELD_OPTIM_STEP_SIZE"
87
+ echo "Field optim trust radius: $FIELD_OPTIM_TRUST_RADIUS"
88
+ echo "Field optim L2 penalty: $FIELD_OPTIM_L2_PENALTY"
89
  echo "Checkpoint: $CHECKPOINT"
90
  echo "Dataset: $DATASET"
91
  echo "Out: $OUT"
 
103
  --group-batch-size "$GROUP_BATCH_SIZE" \
104
  --sim-backend physx_cuda:0 \
105
  --render-backend cpu \
106
+ --selection-mode "$SELECTION_MODE" \
107
  --num-candidates "$NUM_CANDIDATES" \
108
  --candidate-sigma "$CANDIDATE_SIGMA" \
109
+ --selection-seed "$SELECTION_SEED" \
110
+ --field-optim-steps "$FIELD_OPTIM_STEPS" \
111
+ --field-optim-step-size "$FIELD_OPTIM_STEP_SIZE" \
112
+ --field-optim-trust-radius "$FIELD_OPTIM_TRUST_RADIUS" \
113
+ --field-optim-l2-penalty "$FIELD_OPTIM_L2_PENALTY"
114
 
115
  echo ""
116
  echo "Field-guided rollout complete"
scripts/slurm/eval_maniskill_policy_rollout.sbatch CHANGED
@@ -44,6 +44,11 @@ SELECTION_MODE="${SELECTION_MODE:-policy}"
44
  NUM_CANDIDATES="${NUM_CANDIDATES:-1}"
45
  CANDIDATE_SIGMA="${CANDIDATE_SIGMA:-0.2}"
46
  SELECTION_SEED="${SELECTION_SEED:-0}"
 
 
 
 
 
47
  LATTICE_EXCLUDE_TYPES="${LATTICE_EXCLUDE_TYPES:-}"
48
  if [[ -n "${LATTICE_EXCLUDE_TYPES_COLON:-}" ]]; then
49
  LATTICE_EXCLUDE_TYPES="${LATTICE_EXCLUDE_TYPES_COLON//:/,}"
@@ -87,5 +92,10 @@ apptainer exec --nv \
87
  --num-candidates "$NUM_CANDIDATES" \
88
  --candidate-sigma "$CANDIDATE_SIGMA" \
89
  --selection-seed "$SELECTION_SEED" \
 
 
 
 
 
90
  --lattice-exclude-types "$LATTICE_EXCLUDE_TYPES" \
91
  "${EXTRA_ARGS[@]}"
 
44
  NUM_CANDIDATES="${NUM_CANDIDATES:-1}"
45
  CANDIDATE_SIGMA="${CANDIDATE_SIGMA:-0.2}"
46
  SELECTION_SEED="${SELECTION_SEED:-0}"
47
+ FIELD_OPTIM_STEPS="${FIELD_OPTIM_STEPS:-0}"
48
+ FIELD_OPTIM_STEP_SIZE="${FIELD_OPTIM_STEP_SIZE:-0.05}"
49
+ FIELD_OPTIM_TRUST_RADIUS="${FIELD_OPTIM_TRUST_RADIUS:-0.5}"
50
+ FIELD_OPTIM_L2_PENALTY="${FIELD_OPTIM_L2_PENALTY:-0.0}"
51
+ RETRIEVAL_NEIGHBORS="${RETRIEVAL_NEIGHBORS:-1}"
52
  LATTICE_EXCLUDE_TYPES="${LATTICE_EXCLUDE_TYPES:-}"
53
  if [[ -n "${LATTICE_EXCLUDE_TYPES_COLON:-}" ]]; then
54
  LATTICE_EXCLUDE_TYPES="${LATTICE_EXCLUDE_TYPES_COLON//:/,}"
 
92
  --num-candidates "$NUM_CANDIDATES" \
93
  --candidate-sigma "$CANDIDATE_SIGMA" \
94
  --selection-seed "$SELECTION_SEED" \
95
+ --field-optim-steps "$FIELD_OPTIM_STEPS" \
96
+ --field-optim-step-size "$FIELD_OPTIM_STEP_SIZE" \
97
+ --field-optim-trust-radius "$FIELD_OPTIM_TRUST_RADIUS" \
98
+ --field-optim-l2-penalty "$FIELD_OPTIM_L2_PENALTY" \
99
+ --retrieval-neighbors "$RETRIEVAL_NEIGHBORS" \
100
  --lattice-exclude-types "$LATTICE_EXCLUDE_TYPES" \
101
  "${EXTRA_ARGS[@]}"
scripts/slurm/summarize_h16_field_sweep.sbatch CHANGED
@@ -12,13 +12,7 @@ set -euo pipefail
12
  PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
13
  RUN_ROOT="${RUN_ROOT:-/scratch/$USER/dovla/experiments/dovla_h16_field_sweep}"
14
  SUMMARY_TAG="${SUMMARY_TAG:-field_sweep}"
15
- if [[ -z "${PYTHON:-}" ]]; then
16
- if [[ -x "$PROJECT_DIR/.venv/bin/python" ]]; then
17
- PYTHON="$PROJECT_DIR/.venv/bin/python"
18
- else
19
- PYTHON="python3"
20
- fi
21
- fi
22
  export RUN_ROOT
23
  export SUMMARY_TAG
24
 
@@ -52,6 +46,11 @@ for result_path in sorted(run_root.glob("k*_sigma*/seed_*/online_rollout.json"))
52
  "selection_mode": data.get("selection_mode"),
53
  "num_candidates": data.get("num_candidates"),
54
  "candidate_sigma": data.get("candidate_sigma"),
 
 
 
 
 
55
  "num_groups": data.get("num_groups"),
56
  "policy_rollout_success_rate": data.get("policy_rollout_success_rate", 0.0),
57
  "policy_rollout_progress": data.get("policy_rollout_progress", 0.0),
 
12
  PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
13
  RUN_ROOT="${RUN_ROOT:-/scratch/$USER/dovla/experiments/dovla_h16_field_sweep}"
14
  SUMMARY_TAG="${SUMMARY_TAG:-field_sweep}"
15
+ PYTHON="${PYTHON:-python3}"
 
 
 
 
 
 
16
  export RUN_ROOT
17
  export SUMMARY_TAG
18
 
 
46
  "selection_mode": data.get("selection_mode"),
47
  "num_candidates": data.get("num_candidates"),
48
  "candidate_sigma": data.get("candidate_sigma"),
49
+ "field_optim_steps": data.get("field_optim_steps", 0),
50
+ "field_optim_step_size": data.get("field_optim_step_size", 0.0),
51
+ "field_optim_trust_radius": data.get("field_optim_trust_radius", 0.0),
52
+ "field_optim_l2_penalty": data.get("field_optim_l2_penalty", 0.0),
53
+ "retrieval_neighbors": data.get("retrieval_neighbors", 0),
54
  "num_groups": data.get("num_groups"),
55
  "policy_rollout_success_rate": data.get("policy_rollout_success_rate", 0.0),
56
  "policy_rollout_progress": data.get("policy_rollout_progress", 0.0),
scripts/slurm/summarize_h16_lattice_rollout.sbatch CHANGED
@@ -12,13 +12,7 @@ set -euo pipefail
12
  PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
13
  RUN_ROOT="${RUN_ROOT:-/scratch/$USER/dovla/experiments/dovla_h16_rollout_runs}"
14
  OUT_NAME="${OUT_NAME:-lattice_rollout.json}"
15
- if [[ -z "${PYTHON:-}" ]]; then
16
- if [[ -x "$PROJECT_DIR/.venv/bin/python" ]]; then
17
- PYTHON="$PROJECT_DIR/.venv/bin/python"
18
- else
19
- PYTHON="python3"
20
- fi
21
- fi
22
  export RUN_ROOT
23
  export OUT_NAME
24
 
 
12
  PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
13
  RUN_ROOT="${RUN_ROOT:-/scratch/$USER/dovla/experiments/dovla_h16_rollout_runs}"
14
  OUT_NAME="${OUT_NAME:-lattice_rollout.json}"
15
+ PYTHON="${PYTHON:-python3}"
 
 
 
 
 
 
16
  export RUN_ROOT
17
  export OUT_NAME
18
 
scripts/slurm/summarize_h16_policy_ckpt.sbatch CHANGED
@@ -14,13 +14,7 @@ RUN_ROOT="${RUN_ROOT:-/scratch/$USER/dovla/experiments/dovla_h16_policy_ckpt_run
14
  OBJECTIVE="${OBJECTIVE:-base}"
15
  OUT_NAME="${OUT_NAME:-policy_rollout.json}"
16
  SUMMARY_TAG="${SUMMARY_TAG:-}"
17
- if [[ -z "${PYTHON:-}" ]]; then
18
- if [[ -x "$PROJECT_DIR/.venv/bin/python" ]]; then
19
- PYTHON="$PROJECT_DIR/.venv/bin/python"
20
- else
21
- PYTHON="python3"
22
- fi
23
- fi
24
  export RUN_ROOT
25
  export OBJECTIVE
26
  export OUT_NAME
@@ -61,6 +55,11 @@ for result_path in sorted(base_dir.glob(f"seed_*/{out_name}")):
61
  "selection_mode": data.get("selection_mode"),
62
  "num_candidates": data.get("num_candidates"),
63
  "candidate_sigma": data.get("candidate_sigma"),
 
 
 
 
 
64
  "policy_rollout_success_rate": data.get("policy_rollout_success_rate", 0.0),
65
  "policy_rollout_progress": data.get("policy_rollout_progress", 0.0),
66
  "oracle_success_rate": data.get("oracle_success_rate", 0.0),
@@ -121,17 +120,20 @@ lines = [
121
  f"Mean progress: {summary['mean_progress']:.2%}",
122
  f"Mean action MSE to best: {summary['mean_action_mse_to_best']:.3f}",
123
  "",
124
- "| seed | mode | k | sigma | success | progress | oracle | action MSE |",
125
- "|---:|---|---:|---:|---:|---:|---:|---:|",
126
  ]
127
  for row in rows:
128
  lines.append(
129
- "| {seed} | {mode} | {k} | {sigma:.2f} | {success:.2%} | "
130
- "{progress:.2%} | {oracle:.2%} | {mse:.3f} |".format(
131
  seed=row["seed"],
132
  mode=row.get("selection_mode") or "policy",
133
  k=row.get("num_candidates") or 1,
 
134
  sigma=row.get("candidate_sigma") or 0.0,
 
 
135
  success=row["policy_rollout_success_rate"],
136
  progress=row["policy_rollout_progress"],
137
  oracle=row["oracle_success_rate"],
 
14
  OBJECTIVE="${OBJECTIVE:-base}"
15
  OUT_NAME="${OUT_NAME:-policy_rollout.json}"
16
  SUMMARY_TAG="${SUMMARY_TAG:-}"
17
+ PYTHON="${PYTHON:-python3}"
 
 
 
 
 
 
18
  export RUN_ROOT
19
  export OBJECTIVE
20
  export OUT_NAME
 
55
  "selection_mode": data.get("selection_mode"),
56
  "num_candidates": data.get("num_candidates"),
57
  "candidate_sigma": data.get("candidate_sigma"),
58
+ "field_optim_steps": data.get("field_optim_steps", 0),
59
+ "field_optim_step_size": data.get("field_optim_step_size", 0.0),
60
+ "field_optim_trust_radius": data.get("field_optim_trust_radius", 0.0),
61
+ "field_optim_l2_penalty": data.get("field_optim_l2_penalty", 0.0),
62
+ "retrieval_neighbors": data.get("retrieval_neighbors", 0),
63
  "policy_rollout_success_rate": data.get("policy_rollout_success_rate", 0.0),
64
  "policy_rollout_progress": data.get("policy_rollout_progress", 0.0),
65
  "oracle_success_rate": data.get("oracle_success_rate", 0.0),
 
120
  f"Mean progress: {summary['mean_progress']:.2%}",
121
  f"Mean action MSE to best: {summary['mean_action_mse_to_best']:.3f}",
122
  "",
123
+ "| seed | mode | k | retrieval K | sigma | opt steps | trust | success | progress | oracle | action MSE |",
124
+ "|---:|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
125
  ]
126
  for row in rows:
127
  lines.append(
128
+ "| {seed} | {mode} | {k} | {retrieval} | {sigma:.2f} | {steps} | {trust:.2f} | "
129
+ "{success:.2%} | {progress:.2%} | {oracle:.2%} | {mse:.3f} |".format(
130
  seed=row["seed"],
131
  mode=row.get("selection_mode") or "policy",
132
  k=row.get("num_candidates") or 1,
133
+ retrieval=row.get("retrieval_neighbors") or 0,
134
  sigma=row.get("candidate_sigma") or 0.0,
135
+ steps=row.get("field_optim_steps") or 0,
136
+ trust=row.get("field_optim_trust_radius") or 0.0,
137
  success=row["policy_rollout_success_rate"],
138
  progress=row["policy_rollout_progress"],
139
  oracle=row["oracle_success_rate"],
tests/test_maniskill_policy_rollout.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
 
3
  import pickle
4
  from pathlib import Path
 
5
 
6
  import numpy as np
7
 
@@ -14,7 +15,9 @@ from dovla_cil.data.schema import (
14
  StructuredEffect,
15
  )
16
  from dovla_cil.eval.maniskill_policy_rollout import (
 
17
  _adapt_action_dim,
 
18
  _load_state_archive,
19
  _numeric_action_values,
20
  _select_action_chunk,
@@ -220,6 +223,60 @@ def test_field_mode_scores_clamped_candidates() -> None:
220
  assert index.tolist()[0] != 0
221
 
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  def test_lattice_mode_selects_best_scored_candidate() -> None:
224
  import torch
225
 
@@ -288,3 +345,96 @@ def test_retrieval_lattice_mode_uses_candidate_tensor() -> None:
288
 
289
  assert torch.allclose(actions, mean + offset)
290
  assert index.tolist() == [1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import pickle
4
  from pathlib import Path
5
+ from types import SimpleNamespace
6
 
7
  import numpy as np
8
 
 
15
  StructuredEffect,
16
  )
17
  from dovla_cil.eval.maniskill_policy_rollout import (
18
+ _RolloutCase,
19
  _adapt_action_dim,
20
+ _attach_retrieved_residual_candidates,
21
  _load_state_archive,
22
  _numeric_action_values,
23
  _select_action_chunk,
 
223
  assert index.tolist()[0] != 0
224
 
225
 
226
+ def test_field_optim_mode_improves_policy_mean() -> None:
227
+ import torch
228
+
229
+ mean = torch.zeros(1, 1, 3)
230
+ offset = torch.full_like(mean, 0.4)
231
+ model = _StubModel(torch, mean, best_offset=offset)
232
+ actions, index = _select_action_chunk(
233
+ model,
234
+ observations=torch.zeros(1, 3),
235
+ instructions=["a"],
236
+ torch=torch,
237
+ selection_mode="field_optim",
238
+ num_candidates=1,
239
+ candidate_sigma=0.0,
240
+ selection_seed=7,
241
+ field_optim_steps=8,
242
+ field_optim_step_size=0.1,
243
+ field_optim_trust_radius=0.5,
244
+ field_optim_l2_penalty=0.0,
245
+ )
246
+
247
+ assert float(((actions - (mean + offset)) ** 2).sum()) < float(
248
+ ((mean - (mean + offset)) ** 2).sum()
249
+ )
250
+ assert index.tolist() == [0]
251
+
252
+
253
+ def test_field_optim_mode_respects_trust_region_and_bounds() -> None:
254
+ import torch
255
+
256
+ mean = torch.zeros(1, 1, 3)
257
+ offset = torch.full_like(mean, 10.0)
258
+ model = _StubModel(torch, mean, best_offset=offset)
259
+ actions, _index = _select_action_chunk(
260
+ model,
261
+ observations=torch.zeros(1, 3),
262
+ instructions=["a"],
263
+ torch=torch,
264
+ selection_mode="field_optim",
265
+ num_candidates=4,
266
+ candidate_sigma=1.0,
267
+ selection_seed=7,
268
+ field_optim_steps=8,
269
+ field_optim_step_size=0.2,
270
+ field_optim_trust_radius=0.25,
271
+ field_optim_l2_penalty=0.0,
272
+ action_low=torch.full_like(mean, -0.5),
273
+ action_high=torch.full_like(mean, 0.5),
274
+ )
275
+
276
+ assert float(actions.max()) <= 0.25
277
+ assert float(actions.min()) >= -0.25
278
+
279
+
280
  def test_lattice_mode_selects_best_scored_candidate() -> None:
281
  import torch
282
 
 
345
 
346
  assert torch.allclose(actions, mean + offset)
347
  assert index.tolist() == [1]
348
+
349
+
350
+ def test_retrieval_residual_mode_translates_residuals_around_policy_mean() -> None:
351
+ import torch
352
+
353
+ mean = torch.full((1, 1, 3), 0.1)
354
+ offset = torch.full_like(mean, 0.4)
355
+ model = _StubModel(torch, mean, best_offset=offset)
356
+ residuals = torch.stack([torch.zeros_like(mean), offset], dim=1)
357
+ actions, index = _select_action_chunk(
358
+ model,
359
+ observations=torch.zeros(1, 3),
360
+ instructions=["a"],
361
+ torch=torch,
362
+ selection_mode="retrieval_residual",
363
+ num_candidates=1,
364
+ candidate_sigma=0.0,
365
+ selection_seed=0,
366
+ action_candidates=residuals,
367
+ )
368
+
369
+ assert torch.allclose(actions, mean + offset)
370
+ assert index.tolist() == [1]
371
+
372
+
373
+ def test_retrieval_residual_candidates_use_knn_train_residuals() -> None:
374
+ def record(group_id: str, candidate_type: str, action_value: float, feature: float):
375
+ return SimpleNamespace(
376
+ group_id=group_id,
377
+ task_id="PickCube-v1",
378
+ candidate_type=candidate_type,
379
+ record_id=f"{group_id}-{candidate_type}-{action_value}",
380
+ observation_inline={"features": [feature, 0.0]},
381
+ action_chunk=ActionChunk(
382
+ representation="continuous",
383
+ horizon=1,
384
+ values=[[action_value, 0.0]],
385
+ ),
386
+ )
387
+
388
+ dataset = SimpleNamespace(
389
+ group_ids=["train_a", "train_b", "heldout"],
390
+ get_group=lambda group_id: {
391
+ "train_a": [
392
+ record("train_a", "expert", 1.0, 0.0),
393
+ record("train_a", "near_miss", 1.2, 0.0),
394
+ ],
395
+ "train_b": [
396
+ record("train_b", "expert", -1.0, 0.4),
397
+ record("train_b", "wrong_direction", -1.3, 0.4),
398
+ ],
399
+ "heldout": [
400
+ record("heldout", "expert", 9.0, 0.1),
401
+ record("heldout", "near_miss", 9.9, 0.1),
402
+ ],
403
+ }[group_id],
404
+ )
405
+ case = _RolloutCase(
406
+ group_id="heldout",
407
+ task_id="PickCube-v1",
408
+ source_dataset=Path("."),
409
+ state={},
410
+ observation={"features": [0.1, 0.0]},
411
+ instruction="pick",
412
+ oracle_score=1.0,
413
+ oracle_success=True,
414
+ expert_score=1.0,
415
+ expert_success=True,
416
+ best_action_values=[[9.9, 0.0]],
417
+ candidate_action_values=[],
418
+ candidate_types=[],
419
+ )
420
+
421
+ [attached] = _attach_retrieved_residual_candidates(
422
+ dataset,
423
+ [case],
424
+ heldout_group_ids=["heldout"],
425
+ obs_dim=2,
426
+ observation_mode="state",
427
+ retrieval_neighbors=2,
428
+ )
429
+
430
+ assert attached.candidate_source_group_id == "train_a;train_b"
431
+ assert attached.candidate_types == [
432
+ "policy_residual",
433
+ "residual_near_miss",
434
+ "policy_residual",
435
+ "residual_wrong_direction",
436
+ ]
437
+ assert np.allclose(
438
+ np.asarray(attached.candidate_action_values, dtype=np.float32),
439
+ np.asarray([[[0.0, 0.0]], [[0.2, 0.0]], [[0.0, 0.0]], [[-0.3, 0.0]]]),
440
+ )