anhtld commited on
Commit
13c469c
·
verified ·
1 Parent(s): 828bff4

auto-sync 2026-07-02T19:07:26Z workspace (part 7)

Browse files
workspace/scripts/export_positive_tangent_targets.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
10
+ if str(PROJECT_ROOT) not in sys.path:
11
+ sys.path.insert(0, str(PROJECT_ROOT))
12
+
13
+ from dovla_cil.data.datasets import CILDataset # noqa: E402
14
+ from dovla_cil.generation.tangent_targets import ( # noqa: E402
15
+ DEFAULT_BASE_CANDIDATE_TYPES,
16
+ DEFAULT_LABELS,
17
+ export_positive_tangent_targets,
18
+ )
19
+
20
+
21
+ def main(argv: list[str] | None = None) -> int:
22
+ parser = argparse.ArgumentParser(
23
+ description=(
24
+ "Export positive/negative same-state residual tangents for the learned "
25
+ "CIL-Atlas Generator V2/V3 training path."
26
+ )
27
+ )
28
+ parser.add_argument("--dataset", type=Path, required=True)
29
+ parser.add_argument("--out", type=Path, required=True)
30
+ parser.add_argument("--epsilon", type=float, default=0.0)
31
+ parser.add_argument(
32
+ "--base-candidate-types",
33
+ default=",".join(DEFAULT_BASE_CANDIDATE_TYPES),
34
+ help="Comma-separated candidate_type priority used as the base action.",
35
+ )
36
+ parser.add_argument(
37
+ "--labels",
38
+ default=",".join(DEFAULT_LABELS),
39
+ help="Comma-separated tangent labels to export: positive, negative, neutral.",
40
+ )
41
+ parser.add_argument("--max-groups", type=int, default=None)
42
+ parser.add_argument(
43
+ "--summary-out",
44
+ type=Path,
45
+ default=None,
46
+ help="Optional summary JSON path. Defaults to OUT with _summary suffix.",
47
+ )
48
+ parser.add_argument("--no-summary", action="store_true")
49
+ args = parser.parse_args(argv)
50
+
51
+ if args.max_groups is not None and args.max_groups <= 0:
52
+ parser.error("--max-groups must be positive when provided")
53
+ if args.epsilon < 0:
54
+ parser.error("--epsilon must be non-negative")
55
+
56
+ dataset = CILDataset(args.dataset)
57
+ payload = export_positive_tangent_targets(
58
+ dataset.iter_groups(),
59
+ epsilon=args.epsilon,
60
+ base_candidate_types=_parse_csv(args.base_candidate_types),
61
+ labels=_parse_csv(args.labels),
62
+ max_groups=args.max_groups,
63
+ )
64
+ payload["dataset"] = str(args.dataset)
65
+
66
+ args.out.parent.mkdir(parents=True, exist_ok=True)
67
+ args.out.write_text(json.dumps(payload, indent=2) + "\n")
68
+ summary = {key: value for key, value in payload.items() if key != "targets"}
69
+ if not args.no_summary:
70
+ summary_out = args.summary_out or args.out.with_name(f"{args.out.stem}_summary.json")
71
+ summary_out.parent.mkdir(parents=True, exist_ok=True)
72
+ summary_out.write_text(json.dumps(summary, indent=2) + "\n")
73
+ print(json.dumps(summary, indent=2))
74
+ print(f"Wrote {args.out}")
75
+ return 0
76
+
77
+
78
+ def _parse_csv(value: str) -> tuple[str, ...]:
79
+ return tuple(item.strip() for item in value.split(",") if item.strip())
80
+
81
+
82
+ if __name__ == "__main__":
83
+ raise SystemExit(main())
workspace/scripts/slurm/advance_v1_generator.sbatch ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #SBATCH --job-name=advance_v1
3
+ #SBATCH --account=def-yalda
4
+ #SBATCH --time=00:20:00
5
+ #SBATCH --cpus-per-task=1
6
+ #SBATCH --mem=2G
7
+ #SBATCH --output=outputs/hpc/logs/%x_%j.out
8
+ #SBATCH --error=outputs/hpc/logs/%x_%j.err
9
+
10
+ set -euo pipefail
11
+
12
+ PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
13
+ RUN_ROOT="${RUN_ROOT:-/scratch/$USER/dovla/experiments/dovla_h16_policy_ckpt_runs}"
14
+ DATASET="${DATASET:-/scratch/$USER/dovla/experiments/six_task_h16_collection}"
15
+ OBJECTIVE="${OBJECTIVE:-transport_field_reground_fieldonly_k6clean_dropnoopwg_b12_v1}"
16
+ SUBMIT_NEXT="${SUBMIT_NEXT:-0}"
17
+ ADVANCE_ROUND="${ADVANCE_ROUND:-0}"
18
+ PYTHON="${PYTHON:-python3}"
19
+
20
+ cd "$PROJECT_DIR"
21
+ mkdir -p results outputs/hpc/logs
22
+
23
+ ARGS=(
24
+ --project-dir "$PROJECT_DIR"
25
+ --run-root "$RUN_ROOT"
26
+ --dataset "$DATASET"
27
+ --objective "$OBJECTIVE"
28
+ --round "$ADVANCE_ROUND"
29
+ )
30
+ if [[ "$SUBMIT_NEXT" == "1" ]]; then
31
+ ARGS+=(--submit-next)
32
+ fi
33
+
34
+ "$PYTHON" scripts/advance_v1_generator.py "${ARGS[@]}"
workspace/scripts/slurm/export_positive_tangent_targets.sbatch ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #SBATCH --job-name=export_pos_tangents
3
+ #SBATCH --account=def-yalda
4
+ #SBATCH --time=00:20:00
5
+ #SBATCH --cpus-per-task=1
6
+ #SBATCH --mem=4G
7
+ #SBATCH --output=outputs/hpc/logs/%x_%j.out
8
+ #SBATCH --error=outputs/hpc/logs/%x_%j.err
9
+
10
+ set -euo pipefail
11
+
12
+ PROJECT_DIR="${PROJECT_DIR:-$SLURM_SUBMIT_DIR}"
13
+ SCRATCH_ROOT="/scratch/$USER/dovla"
14
+ PYTHON="${PYTHON:-python3}"
15
+ DATASET="${DATASET:-$SCRATCH_ROOT/experiments/six_task_h16_collection}"
16
+ OUT="${OUT:-$PROJECT_DIR/results/generator_v2_positive_tangent_targets.json}"
17
+ EPSILON="${EPSILON:-0.05}"
18
+ BASE_CANDIDATE_TYPES="${BASE_CANDIDATE_TYPES:-policy,policy_residual,anchor,base,expert}"
19
+ LABELS="${LABELS:-positive,negative,neutral}"
20
+ MAX_GROUPS="${MAX_GROUPS:-}"
21
+
22
+ cd "$PROJECT_DIR"
23
+ mkdir -p outputs/hpc/logs "$(dirname "$OUT")"
24
+
25
+ ARGS=(
26
+ --dataset "$DATASET"
27
+ --out "$OUT"
28
+ --epsilon "$EPSILON"
29
+ --base-candidate-types "$BASE_CANDIDATE_TYPES"
30
+ --labels "$LABELS"
31
+ )
32
+ if [[ -n "$MAX_GROUPS" ]]; then
33
+ ARGS+=(--max-groups "$MAX_GROUPS")
34
+ fi
35
+
36
+ "$PYTHON" scripts/export_positive_tangent_targets.py "${ARGS[@]}"
workspace/tests/test_positive_tangent_targets.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ from dovla_cil.data.schema import (
6
+ CIL_VERSION,
7
+ ActionChunk,
8
+ CILRecord,
9
+ FailureInfo,
10
+ RewardInfo,
11
+ StructuredEffect,
12
+ make_record_id,
13
+ )
14
+ from dovla_cil.generation.tangent_targets import (
15
+ choose_base_record,
16
+ export_positive_tangent_targets,
17
+ spline_tangent_summary,
18
+ )
19
+
20
+
21
+ def _record(
22
+ group_id: str,
23
+ action_id: str,
24
+ candidate_type: str,
25
+ action: list[list[float]],
26
+ progress: float,
27
+ *,
28
+ success: bool = False,
29
+ failure: FailureInfo | None = None,
30
+ ) -> CILRecord:
31
+ return CILRecord(
32
+ version=CIL_VERSION,
33
+ record_id=make_record_id(group_id, action_id, seed=3),
34
+ group_id=group_id,
35
+ state_hash="same-state",
36
+ task_id="PickCube-v1",
37
+ scene_id=None,
38
+ instruction="pick the cube",
39
+ instruction_family={"family": "pick"},
40
+ observation_ref=None,
41
+ observation_inline={"state": [0.0, 1.0]},
42
+ action_chunk=ActionChunk(action_id=action_id, horizon=len(action), values=action),
43
+ next_observation_ref=None,
44
+ next_observation_inline={"state": [progress]},
45
+ structured_effect=StructuredEffect(),
46
+ reward=RewardInfo(
47
+ progress=progress,
48
+ success=success,
49
+ terminal_success=success,
50
+ dense_components={"progress": progress},
51
+ ),
52
+ regret=None,
53
+ rank_within_group=None,
54
+ candidate_type=candidate_type,
55
+ failure=failure,
56
+ )
57
+
58
+
59
+ def test_export_positive_tangent_targets_labels_same_state_utility_contrasts() -> None:
60
+ base = _record("g", "base", "policy", [[0.0, 0.0], [0.0, 0.0]], 0.4)
61
+ positive = _record("g", "repair", "near_miss", [[0.1, 0.0], [0.2, 0.0]], 0.8)
62
+ negative = _record(
63
+ "g",
64
+ "collide",
65
+ "random_negative",
66
+ [[-0.1, 0.0], [-0.2, 0.0]],
67
+ 0.2,
68
+ failure=FailureInfo(type="collision"),
69
+ )
70
+
71
+ payload = export_positive_tangent_targets([[base, positive, negative]], epsilon=0.05)
72
+
73
+ assert payload["num_usable_groups"] == 1
74
+ assert payload["label_counts"] == {"negative": 1, "positive": 1}
75
+ by_type = {item["candidate_type"]: item for item in payload["targets"]}
76
+ assert by_type["near_miss"]["tangent_label"] == "positive"
77
+ assert by_type["random_negative"]["tangent_label"] == "negative"
78
+ assert by_type["near_miss"]["delta_action"] == [[0.1, 0.0], [0.2, 0.0]]
79
+ assert by_type["near_miss"]["tangent_summary"]["endpoint_delta"] == [0.2, 0.0]
80
+
81
+
82
+ def test_choose_base_record_uses_priority_order() -> None:
83
+ expert = _record("g", "expert", "expert", [[1.0]], 1.0)
84
+ policy = _record("g", "policy", "policy", [[0.0]], 0.3)
85
+
86
+ assert choose_base_record([expert, policy]).record_id == policy.record_id
87
+ assert choose_base_record([expert], base_candidate_types=("expert",)).record_id == expert.record_id
88
+
89
+
90
+ def test_spline_tangent_summary_exposes_low_dimensional_code() -> None:
91
+ summary = spline_tangent_summary(
92
+ [
93
+ [0.0, 0.0, 0.0, 0.1],
94
+ [0.1, 0.0, 0.2, 0.3],
95
+ [0.2, 0.1, 0.4, 0.7],
96
+ ]
97
+ )
98
+
99
+ assert summary["spline_code"]["endpoint_delta_position"] == [0.2, 0.1, 0.4]
100
+ assert summary["spline_code"]["gripper_gate_shift"] == 0.7
101
+ assert math.isclose(summary["spline_code"]["lift_bias"], 0.4)
102
+ assert summary["temporal_total_variation"] > 0.0