File size: 3,320 Bytes
ceea884
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""closed-loop μ„œλ²„ ν”„λ‘œν† μ½œ(src/vkgs/src/serve.hpp)κ³Ό drive.hpp μ œμ–΄ ν™˜μ‚°μ„ g++ 둜 μ»΄νŒŒμΌν•΄ μ‹œν—˜ν•œλ‹€ (μ°½Β·GPU 없이).
Python μͺ½ νŒμ •(nusim.sim.geometry)이 C++ vehicle_contact 와 같은 점을 μ„ΈλŠ”μ§€λ„ λŒ€μ‘°ν•œλ‹€."""

import os
import shutil
import subprocess
from pathlib import Path

import numpy as np
import pytest

from nusim.sim.closed_loop import format_command, parse_reply
from nusim.sim.geometry import ContactParams, VehicleGeom, World

ROOT = Path(__file__).resolve().parents[1]
CXX = os.environ.get("CXX") or "g++"
needs_cxx = pytest.mark.skipif(shutil.which(CXX) is None, reason="g++ μ—†μŒ")


def _build(tmp_path):
    exe = tmp_path / "test_serve"
    subprocess.run([CXX, "-std=c++20", "-O1", "-I", str(ROOT / "src/vkgs/src"), str(ROOT / "tests/cpp/test_serve.cpp"),
                    "-o", str(exe)], check=True)
    return exe


@needs_cxx
def test_serve_protocol_and_control(tmp_path):
    r = subprocess.run([str(_build(tmp_path)), "self"], capture_output=True, text=True)
    assert r.returncode == 0, r.stdout + r.stderr


def test_python_protocol_roundtrip():
    st = parse_reply("ok t=0.500000 x=-9.5 obs=- cams=CAM_FRONT,CAM_BACK contact=1\n")
    assert st == {"t": 0.5, "x": -9.5, "obs": "", "cams": "CAM_FRONT,CAM_BACK", "contact": 1.0}
    with pytest.raises(RuntimeError, match="μ„œλ²„ 였λ₯˜"):
        parse_reply("err step: dt= κ°€ ν•„μš”ν•¨")
    with pytest.raises(RuntimeError):
        parse_reply("")
    assert format_command("step", accel=1.25, delta=-0.1, dt=0.05, render=True, sensors=None) == \
        "step accel=1.25 delta=-0.1 dt=0.05 render=1"
    with pytest.raises(ValueError):
        format_command("actors", boxes="1 2")


@needs_cxx
def test_python_contact_matches_cpp(tmp_path):
    """같은 μ§€λ„Β·μžμ„Έμ—μ„œ Python World 와 C++ vehicle_contact κ°€ 같은 수의 점을 μ„Όλ‹€."""
    res, n, x0 = 0.2, 150, -15.0
    rng = np.random.default_rng(0)
    g = (rng.random((n, n)) < 0.03).astype(np.uint8) * 255  # 흩어진 μΉΈ
    g[60:64, :] = 255  # y β‰ˆ -3 m 의 λ²½
    pgm = tmp_path / "obst.pgm"
    pgm.write_bytes(f"P5\n# nusim obstacles {x0} {x0} {res}\n{n} {n}\n255\n".encode() + g.tobytes())
    driv = tmp_path / "drivable.txt"
    # λ°”κΉ₯ μ‚¬κ°ν˜• + κ°€μš΄λ° ꡬ멍
    driv.write_text("# t\nP 2 4 -10 -10 10 -10 10 10 -10 10 4 2 2 4 2 4 4 2 4\n")
    poses = np.c_[rng.uniform(-9, 9, 60), rng.uniform(-9, 9, 60), rng.uniform(-np.pi, np.pi, 60)]
    r = subprocess.run([str(_build(tmp_path)), "contact", str(pgm), str(driv)], input="\n".join(
        f"{x:.4f} {y:.4f} {yaw:.4f}" for x, y, yaw in poses), capture_output=True, text=True, check=True)
    cpp = np.array([list(map(int, l.split())) for l in r.stdout.split("\n") if l.strip()])
    w = World.load(VehicleGeom(), ContactParams(curb=True, curb_tol=0.15, obst_tol=0.1, graze=2), pgm, driv)
    poses32 = poses.astype(np.float32).astype(np.float64)  # C++ λŠ” float 둜 μ½λŠ”λ‹€
    py_obst = [w.hard_contacts(*p) for p in poses32]
    py_road = [w.on_road(*p) for p in poses32]
    assert np.abs(cpp[:, 0] - np.array(py_obst)).max() <= 1, (cpp[:, 0], py_obst)  # float 경계 1μ κΉŒμ§€
    assert (cpp[:, 0] > 0).sum() > 5  # μ‹€μ œλ‘œ λ‹ΏλŠ” μžμ„Έκ°€ μ„žμ—¬ μžˆμ–΄μ•Ό μ‹œν—˜μ΄ λœλ‹€
    assert ((cpp[:, 1] == 0) == np.array(py_road)).all()