| from __future__ import annotations |
|
|
| import pytest |
|
|
| from dovla_cil.data.schema import ( |
| CIL_VERSION, |
| ActionChunk, |
| CILGroup, |
| CILRecord, |
| FailureInfo, |
| RewardInfo, |
| StructuredEffect, |
| compute_regret_and_ranks, |
| compute_state_hash, |
| make_record_id, |
| validate_group, |
| ) |
|
|
|
|
| def make_record( |
| group_id: str, action_id: str, progress: float, *, state_hash: str = "s" |
| ) -> CILRecord: |
| return CILRecord( |
| version=CIL_VERSION, |
| record_id=make_record_id(group_id, action_id, seed=7), |
| group_id=group_id, |
| state_hash=state_hash, |
| task_id="task", |
| scene_id=None, |
| instruction="move the mug", |
| instruction_family={"family": "place"}, |
| observation_ref=None, |
| observation_inline={"symbolic": True}, |
| action_chunk=ActionChunk( |
| action_id=action_id, |
| representation="delta_xy", |
| horizon=1, |
| values=[[progress, 0.0]], |
| skill_type="push", |
| ), |
| next_observation_ref=None, |
| next_observation_inline={"symbolic": True, "next": True}, |
| structured_effect=StructuredEffect( |
| object_pose_delta={"mug": [progress, 0.0, 0.0]}, |
| relation_before={"inside(mug,bowl)": False}, |
| relation_after={"inside(mug,bowl)": progress > 0.5}, |
| moved_objects=["mug"] if progress else [], |
| symbolic_before={"objects": {"mug": {"position": [0, 0, 0]}}}, |
| symbolic_after={"objects": {"mug": {"position": [progress, 0, 0]}}}, |
| ), |
| reward=RewardInfo( |
| progress=progress, |
| success=progress > 0.5, |
| terminal_success=progress > 0.5, |
| dense_components={"progress": progress}, |
| ), |
| regret=None, |
| rank_within_group=None, |
| candidate_type="sampled", |
| failure=None |
| if progress > 0 |
| else FailureInfo(type="no_motion", symbolic_reason="object did not move"), |
| ) |
|
|
|
|
| def test_schema_roundtrip() -> None: |
| record = make_record("g", "a", 1.0) |
| record.validate() |
| assert CILRecord.from_dict(record.to_dict()) == record |
| group = CILGroup.from_records([record]) |
| assert group.group_id == "g" |
|
|
|
|
| def test_group_validation() -> None: |
| records = [make_record("g", "a", 0.0), make_record("g", "b", 1.0)] |
| validate_group(records) |
| with pytest.raises(ValueError): |
| validate_group([make_record("g", "a", 0.0), make_record("other", "b", 1.0)]) |
| with pytest.raises(ValueError): |
| validate_group([make_record("g", "a", 0.0), make_record("g", "b", 1.0, state_hash="x")]) |
|
|
|
|
| def test_regret_and_rank_computation() -> None: |
| ranked = compute_regret_and_ranks( |
| [make_record("g", "low", 0.0), make_record("g", "high", 1.0)] |
| ) |
| by_action = {record.action_chunk.action_id: record for record in ranked} |
| assert by_action["high"].rank_within_group == 0 |
| assert by_action["high"].regret == 0.0 |
| assert by_action["low"].rank_within_group == 1 |
| assert by_action["low"].regret == 2.0 |
|
|
|
|
| def test_deterministic_record_ids_and_state_hashes() -> None: |
| assert make_record_id("g", "a", 1) == make_record_id("g", "a", 1) |
| assert make_record_id("g", "a", 1) != make_record_id("g", "a", 2) |
| assert compute_state_hash(b"state") == compute_state_hash(b"state") |
| assert compute_state_hash(b"state") != compute_state_hash(b"other") |
|
|