File size: 3,328 Bytes
78738de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
"""Test phần TOÁN THUẦN của poolcoach_cv.resim (lát A2 phần 2) — chạy
trên venv app KHÔNG pooltool thật (conftest stub): mọi test ở đây chỉ đụng
sample_traj / rmse_mm / align_t0 / các đường ra sớm của resim_result.
Pipeline pooltool thật nghiệm thu bằng probe tự-nhất-quán trên venv CV
(sim → lấy chính sim làm quan sát → RMSE 0.0mm, 13/08) + rerun rack_a1.
"""

from __future__ import annotations

import numpy as np
import pytest

from poolcoach_cv import resim as rs


def test_sample_traj_noi_suy_va_kep_hai_dau():
    sim_t = np.array([0.0, 1.0, 2.0])
    sim_xy = np.array([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]])
    out = rs.sample_traj(sim_t, sim_xy, np.array([-0.5, 0.5, 1.5, 9.0]))
    assert out == pytest.approx(np.array(
        [[0.0, 0.0],      # trước t=0 — kẹp điểm đầu (bi chưa đánh)
         [0.5, 0.0],      # nội suy tuyến tính
         [1.0, 0.5],
         [1.0, 1.0]]))    # sau khi sim dừng — kẹp điểm cuối (lệch là thật)


def test_rmse_mm():
    obs = np.array([[0.0, 0.0], [1.0, 0.0]])
    sim = np.array([[0.0, 0.003], [1.0, -0.004]])
    assert rs.rmse_mm(obs, sim) == pytest.approx(
        np.sqrt((3.0 ** 2 + 4.0 ** 2) / 2))


def test_align_t0_chon_dung_nac_lech():
    """Quan sát = chính quỹ đạo sim dịch sớm 1 bước frame (motion_start
    trễ sau cú chạm) — lưới dò phải chọn Δ ≈ dt và RMSE về ~0."""
    dt = 1 / 30.0
    sim_t = np.arange(0.0, 2.0, 0.01)
    sim_xy = np.stack([0.3 + 1.5 * sim_t, 0.4 + 0.9 * sim_t], axis=1)
    ms = 0.5
    true_t0 = ms - dt                       # cú chạm sớm hơn motion_start 1 frame
    obs_t = np.arange(ms, 1.8, dt)
    obs_xy = rs.sample_traj(sim_t, sim_xy, obs_t - true_t0)
    t0, delta, err = rs.align_t0(obs_t, obs_xy, sim_t, sim_xy, ms, dt)
    assert delta == pytest.approx(dt, abs=dt / rs.ALIGN_STEPS + 1e-9)
    assert err < 2.0                        # mm — lưới dò hữu hạn, không đòi 0 tuyệt đối
    assert t0 == pytest.approx(ms - delta)


def test_resim_result_khong_motion_start_ra_error_ca_hai_bo():
    out = rs.resim_result({"metrics": {"motion_start_s": None},
                           "track": [], "shotnet": None}, state={})
    assert set(out["sets"]) == {"shotnet", "analytic"}
    for blk in out["sets"].values():
        assert "motion_start" in blk["error"]
        assert "rmse_mm" not in blk
    assert out["table_w_m"] == 1.27 and out["table_l_m"] == 2.54


def test_resim_result_track_thieu_ra_error():
    out = rs.resim_result(
        {"metrics": {"motion_start_s": 0.5},
         "track": [{"t_s": 0.6, "x_m": 0.5, "y_m": 0.5}]}, state={})
    for blk in out["sets"].values():
        assert "track" in blk["error"]


def test_obs_arrays_cue_nghi_la_median_truoc_motion_start():
    res = {"metrics": {"motion_start_s": 0.5},
           "track": [{"t_s": 0.1, "x_m": 0.60, "y_m": 0.70},
                     {"t_s": 0.2, "x_m": 0.62, "y_m": 0.70},
                     {"t_s": 0.3, "x_m": 0.61, "y_m": 0.72},
                     {"t_s": 0.6, "x_m": 0.80, "y_m": 0.90},
                     {"t_s": 0.7, "x_m": 0.90, "y_m": 1.00}]}
    obs_t, obs_xy, cue0 = rs._obs_arrays(res)
    assert list(obs_t) == [0.6, 0.7]
    assert cue0 == pytest.approx([0.61, 0.70])