anhtld commited on
Commit
19241c0
·
verified ·
1 Parent(s): a4d9743

test score-shape dominance features

Browse files
Files changed (1) hide show
  1. tests/test_dominance_selector.py +401 -1
tests/test_dominance_selector.py CHANGED
@@ -1,16 +1,33 @@
1
  from __future__ import annotations
2
 
 
 
 
3
  import numpy as np
 
4
 
5
- from scripts.eval_dominance_selector import _choose_tau, _conformal_quantile, _evaluate_case
 
 
 
 
 
6
  from scripts.eval_learned_dominance_selector import (
 
7
  _candidate_feature,
 
8
  _evaluate_dataset,
 
9
  _feature_names,
10
  _fit_select_ridge,
 
 
 
11
  _target_value,
 
12
  )
13
  from scripts.eval_nonlinear_dominance_selector import _split_rows, _subset_dataset
 
14
 
15
 
16
  def test_conformal_quantile_and_fallback_selection() -> None:
@@ -44,6 +61,54 @@ def test_conformal_quantile_and_fallback_selection() -> None:
44
  assert fallback["selected_utility"] == 0.3
45
 
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def test_choose_tau_prefers_success_then_utility_then_coverage() -> None:
48
  cases = [
49
  {
@@ -118,6 +183,171 @@ def test_learned_dominance_ridge_prefers_positive_candidate() -> None:
118
  assert [row["selected_success"] for row in rows] == [1.0, 1.0]
119
 
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def test_learned_dominance_tangent_features_and_success_weighted_target() -> None:
122
  tangent = np.arange(21, dtype=float)
123
  basic = _candidate_feature(
@@ -174,14 +404,143 @@ def test_learned_dominance_tangent_features_and_success_weighted_target() -> Non
174
  "instruction": "Pick up the cube.",
175
  },
176
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  assert len(basic) == len(_feature_names("basic")) == 10
178
  assert len(expanded) == len(_feature_names("tangent")) == 52
179
  assert len(context) == len(_feature_names("context")) == 37
180
  assert len(context_tangent) == len(_feature_names("context_tangent")) == 79
 
 
181
  assert basic[2] == 0.75
182
  assert basic[6] == 3.0
183
  assert context[-3] == 1.0
 
 
 
184
  assert _target_value("success_weighted_margin", utility_margin=0.2, candidate_success=1.0) == 1.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
 
187
  def test_nonlinear_dominance_split_is_row_disjoint() -> None:
@@ -222,3 +581,44 @@ def test_nonlinear_dominance_split_is_row_disjoint() -> None:
222
  subset = _subset_dataset(dataset, select_rows)
223
  assert subset["num_rows"] == len(select_rows)
224
  assert len(subset["samples"]) == 2 * len(select_rows)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
+ import json
4
+ from types import SimpleNamespace
5
+
6
  import numpy as np
7
+ import pytest
8
 
9
+ from scripts.eval_dominance_selector import (
10
+ _choose_tau,
11
+ _conformal_quantile,
12
+ _evaluate_case,
13
+ _pairwise_calibration_summary as _score_pairwise_calibration_summary,
14
+ )
15
  from scripts.eval_learned_dominance_selector import (
16
+ _chart_compat_feature,
17
  _candidate_feature,
18
+ _choose_thresholds,
19
  _evaluate_dataset,
20
+ _evaluate_predictions,
21
  _feature_names,
22
  _fit_select_ridge,
23
+ _pairwise_calibration_summary,
24
+ _score_shape_matrix,
25
+ _source_evidence_feature,
26
  _target_value,
27
+ _write_report_artifact,
28
  )
29
  from scripts.eval_nonlinear_dominance_selector import _split_rows, _subset_dataset
30
+ from scripts.eval_nonlinear_dominance_selector import _write_provenance as _write_nonlinear_provenance
31
 
32
 
33
  def test_conformal_quantile_and_fallback_selection() -> None:
 
61
  assert fallback["selected_utility"] == 0.3
62
 
63
 
64
+ def test_score_dominance_logs_pairwise_calibration_and_unsafe_execution() -> None:
65
+ case = {
66
+ "chart_id": "c0",
67
+ "task_id": "pick",
68
+ "seed": "0",
69
+ "train_seed": "0",
70
+ "predicted_scores": [3.0, 1.0, -1.0],
71
+ "predicted_margin": 1.0,
72
+ "measured_margin": 0.7,
73
+ "base_utility": 0.2,
74
+ "base_success": 0.0,
75
+ "base_unsafe_known": 0.0,
76
+ "top_generated_utility": 0.9,
77
+ "top_generated_success": 1.0,
78
+ "top_candidate_unsafe_known": 1.0,
79
+ "candidate_safety_label_coverage": 1.0,
80
+ "candidate_unsafe_rate_known": 1.0 / 3.0,
81
+ "generated_utilities": [0.9, 0.4, 0.1],
82
+ "proposal_oracle_utility": 0.9,
83
+ "proposal_oracle_success": 1.0,
84
+ "hidden_chart_oracle_utility": 1.0,
85
+ "hidden_chart_oracle_success": 1.0,
86
+ "outcome_ptr": 1.0,
87
+ }
88
+ calibration = _score_pairwise_calibration_summary([case])
89
+ executed = _evaluate_case(
90
+ case,
91
+ residual_quantile=0.0,
92
+ tau=0.0,
93
+ pairwise_calibration=calibration["rows"][0],
94
+ )
95
+ fallback = _evaluate_case(
96
+ case,
97
+ residual_quantile=2.0,
98
+ tau=0.0,
99
+ pairwise_calibration=calibration["rows"][0],
100
+ )
101
+
102
+ assert calibration["num_pairs"] == 3
103
+ assert executed["pairwise_causal_calibration_pairs"] == 3.0
104
+ assert executed["pairwise_causal_calibration_accuracy"] == 1.0
105
+ assert executed["unsafe_execution_label_known"] == 1.0
106
+ assert executed["unsafe_execution_known"] == 1.0
107
+ assert fallback["fallback_to_base"] == 1.0
108
+ assert fallback["unsafe_execution_label_known"] == 1.0
109
+ assert fallback["unsafe_execution_known"] == 0.0
110
+
111
+
112
  def test_choose_tau_prefers_success_then_utility_then_coverage() -> None:
113
  cases = [
114
  {
 
183
  assert [row["selected_success"] for row in rows] == [1.0, 1.0]
184
 
185
 
186
+ def test_learned_dominance_pairwise_objective_prefers_within_chart_winner() -> None:
187
+ def sample(row: int, candidate: int, feature_value: float, success: float) -> dict:
188
+ feature = np.zeros(10, dtype=float)
189
+ feature[0] = 1.0
190
+ feature[1] = feature_value
191
+ return {
192
+ "row_index": row,
193
+ "candidate_index": candidate,
194
+ "feature": feature,
195
+ "target_margin": success,
196
+ "candidate_utility": success,
197
+ "candidate_success": success,
198
+ "base_utility": 0.0,
199
+ "base_success": 0.0,
200
+ "proposal_oracle_utility": 1.0,
201
+ "proposal_oracle_success": 1.0,
202
+ "hidden_chart_oracle_utility": 1.0,
203
+ "hidden_chart_oracle_success": 1.0,
204
+ "outcome_ptr": 1.0,
205
+ "chart_id": f"c{row}",
206
+ "task_id": "pick",
207
+ "seed": "0",
208
+ "train_seed": "0",
209
+ }
210
+
211
+ dataset = {
212
+ "samples": [
213
+ sample(0, 0, 2.0, 1.0),
214
+ sample(0, 1, -1.0, 0.0),
215
+ sample(0, 2, 0.0, 0.0),
216
+ sample(1, 0, 3.0, 1.0),
217
+ sample(1, 1, -2.0, 0.0),
218
+ sample(1, 2, 0.5, 0.0),
219
+ ],
220
+ "by_row": {0: [0, 1, 2], 1: [3, 4, 5]},
221
+ "num_rows": 2,
222
+ }
223
+ fit = _fit_select_ridge(dataset, lambdas=[0.1], fit_objective="pairwise")
224
+ rows = _evaluate_dataset(dataset, fit["weights"], fit["mean"], fit["std"], tau=fit["tau"])
225
+ assert fit["fit_design"]["num_candidate_rows"] == 6
226
+ assert fit["fit_design"]["num_pairwise_rows"] == 8
227
+ assert fit["fit_design"]["num_fit_rows"] == 8
228
+ assert [row["selected_candidate_index"] for row in rows] == [0, 0]
229
+ assert [row["selected_success"] for row in rows] == [1.0, 1.0]
230
+
231
+
232
+ def test_task_scoped_threshold_can_use_visible_task_bucket() -> None:
233
+ dataset = {
234
+ "samples": [
235
+ {
236
+ "row_index": 0,
237
+ "candidate_index": 0,
238
+ "feature": np.ones(2, dtype=float),
239
+ "target_margin": 1.0,
240
+ "candidate_utility": 1.0,
241
+ "candidate_success": 1.0,
242
+ "base_utility": 0.0,
243
+ "base_success": 0.0,
244
+ "proposal_oracle_utility": 1.0,
245
+ "proposal_oracle_success": 1.0,
246
+ "hidden_chart_oracle_utility": 1.0,
247
+ "hidden_chart_oracle_success": 1.0,
248
+ "outcome_ptr": 1.0,
249
+ "chart_id": "a",
250
+ "task_id": "task_execute",
251
+ "seed": "0",
252
+ "train_seed": "0",
253
+ },
254
+ {
255
+ "row_index": 1,
256
+ "candidate_index": 0,
257
+ "feature": np.ones(2, dtype=float),
258
+ "target_margin": -1.0,
259
+ "candidate_utility": 0.0,
260
+ "candidate_success": 0.0,
261
+ "base_utility": 1.0,
262
+ "base_success": 1.0,
263
+ "proposal_oracle_utility": 0.0,
264
+ "proposal_oracle_success": 0.0,
265
+ "hidden_chart_oracle_utility": 1.0,
266
+ "hidden_chart_oracle_success": 1.0,
267
+ "outcome_ptr": 0.0,
268
+ "chart_id": "b",
269
+ "task_id": "task_fallback",
270
+ "seed": "0",
271
+ "train_seed": "0",
272
+ },
273
+ ],
274
+ "by_row": {0: [0], 1: [1]},
275
+ "num_rows": 2,
276
+ }
277
+ predictions = np.asarray([0.6, 0.8], dtype=float)
278
+ global_tau, _global_summary = _choose_thresholds(
279
+ dataset,
280
+ predictions,
281
+ threshold_scope="global",
282
+ )
283
+ task_tau, task_summary = _choose_thresholds(
284
+ dataset,
285
+ predictions,
286
+ threshold_scope="task",
287
+ )
288
+ global_rows = _evaluate_predictions(dataset, predictions, tau=global_tau)
289
+ task_rows = _evaluate_predictions(dataset, predictions, tau=task_tau)
290
+ assert np.mean([row["selected_success"] for row in global_rows]) == 0.5
291
+ assert task_summary["selected_success"] == 1.0
292
+ assert [row["selected_success"] for row in task_rows] == [1.0, 1.0]
293
+
294
+
295
+ def test_pairwise_calibration_ece_is_logged_per_selector_row() -> None:
296
+ def sample(row: int, candidate: int, utility: float) -> dict:
297
+ return {
298
+ "row_index": row,
299
+ "candidate_index": candidate,
300
+ "feature": np.asarray([1.0, float(candidate)]),
301
+ "target_margin": utility,
302
+ "candidate_utility": utility,
303
+ "candidate_success": float(utility > 0.5),
304
+ "base_utility": 0.0,
305
+ "base_success": 0.0,
306
+ "proposal_oracle_utility": 1.0,
307
+ "proposal_oracle_success": 1.0,
308
+ "hidden_chart_oracle_utility": 1.0,
309
+ "hidden_chart_oracle_success": 1.0,
310
+ "outcome_ptr": 1.0,
311
+ "chart_id": f"c{row}",
312
+ "task_id": "pick",
313
+ "seed": "0",
314
+ "train_seed": "0",
315
+ }
316
+
317
+ dataset = {
318
+ "samples": [
319
+ sample(0, 0, 1.0),
320
+ sample(0, 1, 0.0),
321
+ sample(0, 2, 0.5),
322
+ sample(1, 0, 1.0),
323
+ sample(1, 1, 0.0),
324
+ sample(1, 2, 0.5),
325
+ ],
326
+ "by_row": {0: [0, 1, 2], 1: [3, 4, 5]},
327
+ "num_rows": 2,
328
+ }
329
+ good_predictions = np.asarray([2.0, -2.0, 0.0, 2.0, -2.0, 0.0], dtype=float)
330
+ bad_predictions = -good_predictions
331
+
332
+ good = _pairwise_calibration_summary(dataset, good_predictions)
333
+ bad = _pairwise_calibration_summary(dataset, bad_predictions)
334
+ rows = _evaluate_predictions(
335
+ dataset,
336
+ good_predictions,
337
+ tau=-10.0,
338
+ include_pairwise_calibration=True,
339
+ pairwise_calibration=good,
340
+ )
341
+
342
+ assert good["num_pairs"] == 6
343
+ assert good["accuracy"] == 1.0
344
+ assert bad["accuracy"] == 0.0
345
+ assert rows[0]["pairwise_causal_calibration_pairs"] == 3.0
346
+ assert rows[0]["pairwise_causal_calibration_ece"] == pytest.approx(
347
+ good["rows"][0]["ece"]
348
+ )
349
+
350
+
351
  def test_learned_dominance_tangent_features_and_success_weighted_target() -> None:
352
  tangent = np.arange(21, dtype=float)
353
  basic = _candidate_feature(
 
404
  "instruction": "Pick up the cube.",
405
  },
406
  )
407
+ source_stats = {
408
+ "positive_tangents": [np.zeros(21, dtype=float), np.ones(21, dtype=float)],
409
+ "negative_tangents": [np.full(21, 4.0, dtype=float)],
410
+ "num_nonbase": 4,
411
+ "positive_delta_utilities": [0.4, 0.8],
412
+ "positive_utilities": [1.2, 1.6],
413
+ "positive_success": [1.0, 0.0],
414
+ "positive_progress": [0.8, 0.9],
415
+ "positive_safety": [0.0, 0.0],
416
+ }
417
+ source_evidence = _source_evidence_feature(source_stats, tangent=np.zeros(21, dtype=float))
418
+ context_tangent_source = _candidate_feature(
419
+ score=1.0,
420
+ base_score=0.25,
421
+ score_mean=0.5,
422
+ score_std=0.5,
423
+ candidate_index=1,
424
+ candidate_type="ctt_transport_rank3",
425
+ tangent=tangent,
426
+ num_candidates=8,
427
+ feature_set="context_tangent_source_evidence",
428
+ context={
429
+ "target_task_id": "PickCube-v1",
430
+ "source_task_id": "PickCube-v1",
431
+ "instruction": "Pick up the cube.",
432
+ },
433
+ source_evidence=source_evidence,
434
+ )
435
  assert len(basic) == len(_feature_names("basic")) == 10
436
  assert len(expanded) == len(_feature_names("tangent")) == 52
437
  assert len(context) == len(_feature_names("context")) == 37
438
  assert len(context_tangent) == len(_feature_names("context_tangent")) == 79
439
+ assert len(context_tangent_source) == len(_feature_names("context_tangent_source_evidence"))
440
+ assert len(context_tangent_source) == 97
441
  assert basic[2] == 0.75
442
  assert basic[6] == 3.0
443
  assert context[-3] == 1.0
444
+ assert source_evidence[0] == 1.0
445
+ assert source_evidence[1] == pytest.approx(np.log1p(2))
446
+ assert source_evidence[-1] == 1.0
447
  assert _target_value("success_weighted_margin", utility_margin=0.2, candidate_success=1.0) == 1.2
448
+ assert _target_value(
449
+ "success_weighted_margin",
450
+ utility_margin=0.2,
451
+ candidate_success=1.0,
452
+ success_bonus=2.5,
453
+ ) == 2.7
454
+
455
+
456
+ def test_score_shape_feature_set_and_markdown_suppression(tmp_path) -> None:
457
+ score_shape = _score_shape_matrix([3.0, 1.0, 2.0])
458
+ feature = _candidate_feature(
459
+ score=3.0,
460
+ base_score=0.25,
461
+ score_mean=2.0,
462
+ score_std=1.0,
463
+ candidate_index=0,
464
+ candidate_type="ctt_transport_rank0",
465
+ tangent=np.zeros(21, dtype=float),
466
+ num_candidates=3,
467
+ feature_set="score_context_chart_compat",
468
+ context={
469
+ "target_task_id": "PickCube-v1",
470
+ "source_task_id": "PickCube-v1",
471
+ "instruction": "Pick up the cube.",
472
+ },
473
+ chart_compat=np.ones(22, dtype=float),
474
+ score_shape=score_shape[0],
475
+ )
476
+
477
+ assert score_shape.shape == (3, 8)
478
+ assert score_shape[0, 0] == 0.0
479
+ assert score_shape[0, 2] == 0.0
480
+ assert score_shape[0, 3] == 1.0
481
+ assert score_shape[1, 0] == 1.0
482
+ assert len(feature) == len(_feature_names("score_context_chart_compat"))
483
+ assert len(feature) == 10 + 8 + 27 + 22
484
+
485
+ metrics = {
486
+ "num_calibration_rows": 1,
487
+ "num_eval_rows": 1,
488
+ "selected_lambda": 0.0,
489
+ "tau": 0.0,
490
+ "threshold_scope": "global",
491
+ "fit_objective": "pointwise",
492
+ "feature_set": "basic",
493
+ "target": "utility_margin",
494
+ "eval_summary": {},
495
+ "calibration_summary": {},
496
+ }
497
+ report_path = tmp_path / "report.md"
498
+ report_path.write_text("old\n")
499
+ _write_report_artifact(tmp_path, metrics, no_markdown_report=True)
500
+ assert not report_path.exists()
501
+ _write_report_artifact(tmp_path, metrics, no_markdown_report=False)
502
+ assert report_path.exists()
503
+
504
+
505
+ def test_chart_compat_feature_is_deployment_visible_and_fixed_width() -> None:
506
+ base = np.arange(14, dtype=float).reshape(2, 7) / 10.0
507
+ context = np.linspace(-0.5, 0.5, 18)
508
+ obs = np.linspace(0.0, 1.0, 32)
509
+ obj = np.linspace(1.0, 2.0, 64)
510
+ target = SimpleNamespace(
511
+ base_action=base,
512
+ feature=np.concatenate([base.reshape(-1), context, obs, obj]),
513
+ )
514
+ source = SimpleNamespace(
515
+ base_action=base + 0.1,
516
+ feature=np.concatenate([base.reshape(-1) + 0.1, context, obs + 0.2, obj + 0.3]),
517
+ )
518
+
519
+ compat = _chart_compat_feature(
520
+ target,
521
+ source,
522
+ chart_feature_mode="base_context_obs_obj",
523
+ )
524
+ feature = _candidate_feature(
525
+ score=1.0,
526
+ base_score=0.25,
527
+ score_mean=0.5,
528
+ score_std=0.5,
529
+ candidate_index=1,
530
+ candidate_type="ctt_transport_rank3",
531
+ tangent=np.zeros(21, dtype=float),
532
+ num_candidates=8,
533
+ feature_set="chart_compat",
534
+ chart_compat=compat,
535
+ )
536
+
537
+ assert compat.shape == (22,)
538
+ assert len(feature) == len(_feature_names("chart_compat")) == 32
539
+ assert compat[0] == 1.0
540
+ assert compat[12] == 1.0
541
+ assert compat[17] == 1.0
542
+ assert np.all(np.isfinite(compat))
543
+ assert np.all(_chart_compat_feature(target, None, chart_feature_mode="base_context_obs_obj") == 0.0)
544
 
545
 
546
  def test_nonlinear_dominance_split_is_row_disjoint() -> None:
 
581
  subset = _subset_dataset(dataset, select_rows)
582
  assert subset["num_rows"] == len(select_rows)
583
  assert len(subset["samples"]) == 2 * len(select_rows)
584
+
585
+
586
+ def test_nonlinear_chart_compat_provenance_records_default_source_index(tmp_path) -> None:
587
+ calibration_input = tmp_path / "calibration.json"
588
+ eval_input = tmp_path / "eval.json"
589
+ train_index = tmp_path / "train_index.json"
590
+ test_index = tmp_path / "test_index.json"
591
+ calibration_input.write_text("{}\n")
592
+ eval_input.write_text("{}\n")
593
+ train_index.write_text(
594
+ '{"split":"train","content_hash":"train-content","split_hash":"train-split","retrieval_index_allowed":true}\n'
595
+ )
596
+ test_index.write_text(
597
+ '{"split":"test","content_hash":"test-content","split_hash":"test-split","retrieval_index_allowed":false}\n'
598
+ )
599
+ args = SimpleNamespace(
600
+ calibration_input=calibration_input,
601
+ calibration_target_index=train_index,
602
+ eval_input=eval_input,
603
+ eval_target_index=test_index,
604
+ source_index=None,
605
+ checkpoint_template="runs/ctt_residual_base_context_obs_seed{seed}/model.pt",
606
+ out_dir=tmp_path / "out",
607
+ k=16,
608
+ feature_set="chart_compat",
609
+ selector_chart_feature_mode="base_context_obs",
610
+ target="utility_margin",
611
+ model_types="hgb_regressor",
612
+ selection_frac=0.35,
613
+ seed=0,
614
+ bootstrap_samples=10,
615
+ )
616
+
617
+ args.out_dir.mkdir()
618
+ _write_nonlinear_provenance(args.out_dir, args)
619
+ data_hash = json.loads((args.out_dir / "data_hash.txt").read_text())
620
+ split_hash = json.loads((args.out_dir / "split_hash.txt").read_text())
621
+
622
+ assert "source_index" in data_hash
623
+ assert split_hash["source_index"]["split"] == "train"
624
+ assert split_hash["source_index"]["retrieval_index_allowed"] is True