ctt train-calibration hygiene 2026-07-03T16:31:19Z: tests/test_ctt.py
Browse files- tests/test_ctt.py +92 -0
tests/test_ctt.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
import numpy as np
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
torch = pytest.importorskip("torch")
|
| 6 |
+
|
| 7 |
+
from cil.models import CTTConfig, CausalTangentTransport, ChartEncoder, TangentNormalizer
|
| 8 |
+
from cil.models.ctt import chamfer_to_target_set, diversity_loss, negative_boundary_loss
|
| 9 |
+
from scripts.eval_ctt_generated_rollout import ChartItem, _source_pool_for_target
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_ctt_variants_preserve_tangent_shape():
|
| 13 |
+
z_source = torch.randn(3, 8)
|
| 14 |
+
z_target = torch.randn(3, 8)
|
| 15 |
+
xi_source = torch.randn(3, 5)
|
| 16 |
+
|
| 17 |
+
for variant in ("residual", "gated_residual"):
|
| 18 |
+
model = CausalTangentTransport(
|
| 19 |
+
CTTConfig(chart_feature_dim=10, chart_dim=8, tangent_dim=5, hidden_dim=16, variant=variant)
|
| 20 |
+
)
|
| 21 |
+
output = model(z_source, z_target, xi_source)
|
| 22 |
+
assert output.shape == xi_source.shape
|
| 23 |
+
assert torch.isfinite(output).all()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def test_chart_encoder_and_losses_are_finite():
|
| 27 |
+
encoder = ChartEncoder(input_dim=6, hidden_dim=12, output_dim=8)
|
| 28 |
+
z = encoder(torch.randn(4, 6))
|
| 29 |
+
assert z.shape == (4, 8)
|
| 30 |
+
|
| 31 |
+
predicted = torch.randn(4, 5)
|
| 32 |
+
positives = predicted + 0.1
|
| 33 |
+
negatives = predicted + 10.0
|
| 34 |
+
|
| 35 |
+
assert torch.isfinite(chamfer_to_target_set(predicted, positives))
|
| 36 |
+
assert negative_boundary_loss(predicted, negatives, margin=0.2).item() == pytest.approx(0.0)
|
| 37 |
+
assert diversity_loss(predicted).item() >= 0.0
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_gated_residual_matches_documented_formula():
|
| 41 |
+
model = CausalTangentTransport(
|
| 42 |
+
CTTConfig(chart_feature_dim=10, chart_dim=2, tangent_dim=2, hidden_dim=4, variant="gated_residual")
|
| 43 |
+
)
|
| 44 |
+
for parameter in model.delta.parameters():
|
| 45 |
+
parameter.data.zero_()
|
| 46 |
+
for parameter in model.gate.parameters():
|
| 47 |
+
parameter.data.zero_()
|
| 48 |
+
model.delta[-1].bias.data[:] = torch.tensor([4.0, -2.0])
|
| 49 |
+
model.gate[2].bias.data[:] = torch.tensor([0.0, 0.0])
|
| 50 |
+
|
| 51 |
+
xi = torch.tensor([[2.0, 6.0]])
|
| 52 |
+
output = model(torch.zeros(1, 2), torch.zeros(1, 2), xi)
|
| 53 |
+
|
| 54 |
+
assert torch.allclose(output, torch.tensor([[3.0, 2.0]]), atol=1.0e-6)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_tangent_normalizer_round_trips():
|
| 58 |
+
values = torch.randn(8, 5)
|
| 59 |
+
normalizer = TangentNormalizer.fit(values)
|
| 60 |
+
restored = normalizer.inverse_transform(normalizer.transform(values))
|
| 61 |
+
assert torch.allclose(values, restored, atol=1.0e-5)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_rollout_source_pool_excludes_target_chart_and_state_hash():
|
| 65 |
+
def chart(chart_id: str, state_hash: str) -> ChartItem:
|
| 66 |
+
return ChartItem(
|
| 67 |
+
chart_id=chart_id,
|
| 68 |
+
task_id="task",
|
| 69 |
+
seed="0",
|
| 70 |
+
state_hash=state_hash,
|
| 71 |
+
instruction="",
|
| 72 |
+
source_dataset=Path("."),
|
| 73 |
+
base_action=np.zeros((2, 7), dtype=np.float32),
|
| 74 |
+
feature=np.zeros(4, dtype=np.float32),
|
| 75 |
+
positive_tangents=np.zeros((1, 21), dtype=np.float32),
|
| 76 |
+
negative_tangents=np.zeros((0, 21), dtype=np.float32),
|
| 77 |
+
hidden_utilities=[],
|
| 78 |
+
hidden_candidate_types=[],
|
| 79 |
+
stored_base_utility=None,
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
target = chart("chart_a", "state_a")
|
| 83 |
+
sources = [chart("chart_a", "state_a"), chart("chart_b", "state_a"), chart("chart_c", "state_c")]
|
| 84 |
+
|
| 85 |
+
pool = _source_pool_for_target(
|
| 86 |
+
target,
|
| 87 |
+
task_pool=sources,
|
| 88 |
+
source_charts=sources,
|
| 89 |
+
exclude_self_source=True,
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
assert [item.chart_id for item in pool] == ["chart_c"]
|