vla / workspace /tests /test_ctt.py
anhtld's picture
auto-sync 2026-07-04T06:53:39Z workspace (part 6)
ca2a8e1 verified
Raw
History Blame Contribute Delete
8.93 kB
import pytest
import numpy as np
from pathlib import Path
from types import SimpleNamespace
torch = pytest.importorskip("torch")
from cil.models import CTTConfig, CausalTangentTransport, ChartEncoder, TangentNormalizer
from cil.models.ctt import (
chamfer_to_target_set,
diversity_loss,
entropic_ot_alignment_loss,
negative_boundary_loss,
)
from scripts.eval_ctt_generated_rollout import (
ChartItem,
Proposal,
_action_bound_diagnostics_4d,
_clip_to_action_space_4d,
_measured_row_from_rollout,
_parse_execution_action_scale_vector,
_scale_actions_4d,
_source_pool_for_target,
_transform_actions_4d,
)
def test_ctt_variants_preserve_tangent_shape():
z_source = torch.randn(3, 8)
z_target = torch.randn(3, 8)
xi_source = torch.randn(3, 5)
for variant in ("residual", "gated_residual"):
model = CausalTangentTransport(
CTTConfig(chart_feature_dim=10, chart_dim=8, tangent_dim=5, hidden_dim=16, variant=variant)
)
output = model(z_source, z_target, xi_source)
assert output.shape == xi_source.shape
assert torch.isfinite(output).all()
def test_chart_encoder_and_losses_are_finite():
encoder = ChartEncoder(input_dim=6, hidden_dim=12, output_dim=8)
z = encoder(torch.randn(4, 6))
assert z.shape == (4, 8)
predicted = torch.randn(4, 5)
positives = predicted + 0.1
negatives = predicted + 10.0
assert torch.isfinite(chamfer_to_target_set(predicted, positives))
assert torch.isfinite(entropic_ot_alignment_loss(predicted, positives, epsilon=0.1, iterations=5))
assert negative_boundary_loss(predicted, negatives, margin=0.2).item() == pytest.approx(0.0)
assert diversity_loss(predicted).item() >= 0.0
def test_gated_residual_matches_documented_formula():
model = CausalTangentTransport(
CTTConfig(chart_feature_dim=10, chart_dim=2, tangent_dim=2, hidden_dim=4, variant="gated_residual")
)
for parameter in model.delta.parameters():
parameter.data.zero_()
for parameter in model.gate.parameters():
parameter.data.zero_()
model.delta[-1].bias.data[:] = torch.tensor([4.0, -2.0])
model.gate[2].bias.data[:] = torch.tensor([0.0, 0.0])
xi = torch.tensor([[2.0, 6.0]])
output = model(torch.zeros(1, 2), torch.zeros(1, 2), xi)
assert torch.allclose(output, torch.tensor([[3.0, 2.0]]), atol=1.0e-6)
def test_tangent_normalizer_round_trips():
values = torch.randn(8, 5)
normalizer = TangentNormalizer.fit(values)
restored = normalizer.inverse_transform(normalizer.transform(values))
assert torch.allclose(values, restored, atol=1.0e-5)
def test_rollout_source_pool_excludes_target_chart_and_state_hash():
def chart(chart_id: str, state_hash: str) -> ChartItem:
return ChartItem(
chart_id=chart_id,
task_id="task",
seed="0",
state_hash=state_hash,
instruction="",
source_dataset=Path("."),
base_action=np.zeros((2, 7), dtype=np.float32),
feature=np.zeros(4, dtype=np.float32),
positive_tangents=np.zeros((1, 21), dtype=np.float32),
negative_tangents=np.zeros((0, 21), dtype=np.float32),
hidden_utilities=[],
hidden_candidate_types=[],
stored_base_utility=None,
)
target = chart("chart_a", "state_a")
sources = [chart("chart_a", "state_a"), chart("chart_b", "state_a"), chart("chart_c", "state_c")]
pool = _source_pool_for_target(
target,
task_pool=sources,
source_charts=sources,
exclude_self_source=True,
)
assert [item.chart_id for item in pool] == ["chart_c"]
def test_action_bound_diagnostics_measure_preclip_violation():
env = SimpleNamespace(
single_action_space=SimpleNamespace(
low=np.asarray([-1.0, -0.5], dtype=np.float32),
high=np.asarray([1.0, 0.5], dtype=np.float32),
)
)
actions = np.asarray(
[[[[0.0, 0.0], [1.2, -0.75]], [[0.1, 0.2], [0.3, 0.4]]]],
dtype=np.float32,
)
diagnostics = _action_bound_diagnostics_4d(actions, env)
clipped = _clip_to_action_space_4d(actions, env)
assert diagnostics is not None
assert diagnostics["violation"].tolist() == [[True, False]]
assert diagnostics["max_abs"][0, 0] == pytest.approx(0.25)
assert clipped[0, 0, 1, 0] == pytest.approx(1.0)
assert clipped[0, 0, 1, 1] == pytest.approx(-0.5)
def test_execution_action_scale_applies_before_bound_diagnostics():
env = SimpleNamespace(
single_action_space=SimpleNamespace(
low=np.asarray([-1.0, -1.0], dtype=np.float32),
high=np.asarray([1.0, 1.0], dtype=np.float32),
)
)
actions = np.asarray([[[[2.0, -4.0]]]], dtype=np.float32)
scaled = _scale_actions_4d(actions, 0.25)
diagnostics = _action_bound_diagnostics_4d(scaled, env)
assert scaled.tolist() == [[[[0.5, -1.0]]]]
assert diagnostics is not None
assert diagnostics["violation"].tolist() == [[False]]
def test_execution_action_scale_vector_is_dimension_wise():
actions = np.asarray([[[[2.0, -4.0, 3.0]]]], dtype=np.float32)
vector = _parse_execution_action_scale_vector("0.5,0.25,2.0")
scaled = _scale_actions_4d(actions, 0.5, scale_vector=vector)
assert vector is not None
assert scaled.tolist() == [[[[0.5, -0.5, 3.0]]]]
def test_execution_action_scale_vector_pads_after_adapted_dims():
actions = np.asarray([[[[2.0, -4.0, 3.0, 8.0]]]], dtype=np.float32)
vector = _parse_execution_action_scale_vector("0.5,0.25,2.0")
scaled = _scale_actions_4d(actions, 0.5, scale_vector=vector)
assert vector is not None
assert scaled.tolist() == [[[[0.5, -0.5, 3.0, 4.0]]]]
def test_tanh_execution_transform_maps_to_env_bounds():
env = SimpleNamespace(
single_action_space=SimpleNamespace(
low=np.asarray([-1.0, -2.0], dtype=np.float32),
high=np.asarray([1.0, 2.0], dtype=np.float32),
)
)
actions = np.asarray([[[[10.0, -10.0]]]], dtype=np.float32)
transformed = _transform_actions_4d(actions, env, "tanh")
diagnostics = _action_bound_diagnostics_4d(transformed, env)
assert transformed[0, 0, 0, 0] <= 1.0
assert transformed[0, 0, 0, 1] >= -2.0
assert diagnostics is not None
assert diagnostics["violation"].tolist() == [[False]]
def test_env_clip_execution_transform_is_logged_bounded_convention():
env = SimpleNamespace(
single_action_space=SimpleNamespace(
low=np.asarray([-1.0, -2.0], dtype=np.float32),
high=np.asarray([1.0, 2.0], dtype=np.float32),
)
)
actions = np.asarray([[[[10.0, -3.0], [0.25, 1.5]]]], dtype=np.float32)
transformed = _transform_actions_4d(actions, env, "env_clip")
diagnostics = _action_bound_diagnostics_4d(transformed, env)
assert transformed.tolist() == [[[[1.0, -2.0], [0.25, 1.5]]]]
assert diagnostics is not None
assert diagnostics["violation"].tolist() == [[False]]
assert diagnostics["max_abs"].tolist() == [[0.0]]
def test_measured_rollout_row_records_action_bound_safety_source():
target = ChartItem(
chart_id="chart_a",
task_id="task",
seed="0",
state_hash="state_a",
instruction="",
source_dataset=Path("."),
base_action=np.zeros((2, 2), dtype=np.float32),
feature=np.zeros(4, dtype=np.float32),
positive_tangents=np.zeros((1, 21), dtype=np.float32),
negative_tangents=np.zeros((0, 21), dtype=np.float32),
hidden_utilities=[1.0],
hidden_candidate_types=["expert"],
stored_base_utility=0.3,
)
proposal = Proposal(
tangent=np.ones(21, dtype=np.float32),
action=np.ones((2, 2), dtype=np.float32),
score=0.7,
source_chart_id="source",
source_task_id="task",
source_rank=1,
)
row = _measured_row_from_rollout(
target,
[proposal],
progress=[0.3, 0.4],
success=[False, False],
utilities=[0.3, 0.4],
safety_violations=[False, True],
action_clip_max_abs=[0.0, 0.2],
restore_error=0.0,
execution_action_scale=0.25,
execution_action_scale_vector=np.asarray([0.5, 2.0], dtype=np.float32),
execution_action_transform="tanh",
)
assert row["execution_action_scale"] == pytest.approx(0.25)
assert row["execution_action_scale_vector"] == [0.5, 2.0]
assert row["execution_action_transform"] == "tanh"
assert row["base_outcome"]["safety_violation"] is False
assert row["base_outcome"]["safety_violation_source"] == "action_bounds"
assert row["candidate_outcomes"][0]["safety_violation"] is True
assert row["candidate_outcomes"][0]["action_bound_violation"] is True
assert row["candidate_action_clip_max_abs"] == [0.2]