"""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])