Skip to content

Commit 7fca294

Browse files
committed
Add tests for intrusion and avoidance computation
- Intrusion: two-agent known distance, neighbor cutoff exclusion, three-agent sum, and MAX aggregation method - Avoidance: head-on collision with analytically known TTC, diverging agents with zero avoidance
1 parent 23ae89e commit 7fca294

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import pandas as pd
2+
import pytest
3+
4+
from pedpy.column_identifier import AVOIDANCE_COL, FRAME_COL, ID_COL, INTRUSION_COL
5+
from pedpy.data.trajectory_data import TrajectoryData
6+
from pedpy.methods.dimensionless_number_calculator import (
7+
IntrusionMethod,
8+
compute_avoidance,
9+
compute_intrusion,
10+
)
11+
12+
13+
def _make_traj(positions, frame_rate=10):
14+
"""Build a single-frame TrajectoryData from a list of (x, y) positions."""
15+
rows = []
16+
for pid, (x, y) in enumerate(positions):
17+
rows.append({"id": pid, "frame": 0, "x": x, "y": y})
18+
return TrajectoryData(data=pd.DataFrame(rows), frame_rate=frame_rate)
19+
20+
21+
# ---------------------------------------------------------------------------
22+
# Intrusion
23+
# ---------------------------------------------------------------------------
24+
25+
26+
class TestComputeIntrusion:
27+
"""Tests for compute_intrusion (Cordes et al. 2024, Eq. 1)."""
28+
29+
def test_two_agents_known_distance(self):
30+
"""Two agents at known distance, check In_i by hand."""
31+
r_soc = 0.8
32+
l_min = 0.2
33+
d = 0.5 # distance between the two agents
34+
# Expected per-pair intrusion: ((0.8-0.2)/(0.5-0.2))^2 = (0.6/0.3)^2 = 4.0
35+
traj = _make_traj([(0, 0), (d, 0)])
36+
result = compute_intrusion(traj_data=traj, r_soc=r_soc, l_min=l_min)
37+
38+
for pid in [0, 1]:
39+
val = result.loc[result[ID_COL] == pid, INTRUSION_COL].iloc[0]
40+
assert val == pytest.approx(4.0)
41+
42+
def test_neighbor_cutoff_excludes_distant_agents(self):
43+
"""Agents beyond 3*r_soc should not contribute to intrusion."""
44+
r_soc = 0.8
45+
l_min = 0.2
46+
far = 3 * r_soc + 0.1 # just beyond cutoff
47+
traj = _make_traj([(0, 0), (far, 0)])
48+
result = compute_intrusion(traj_data=traj, r_soc=r_soc, l_min=l_min)
49+
50+
# Both agents should be absent (no neighbors within cutoff)
51+
assert result.empty
52+
53+
def test_three_agents_sum(self):
54+
"""Three agents: agent 0 has two neighbors, check sum."""
55+
r_soc = 0.8
56+
l_min = 0.2
57+
d1 = 0.5
58+
d2 = 0.6
59+
traj = _make_traj([(0, 0), (d1, 0), (0, d2)])
60+
61+
result = compute_intrusion(
62+
traj_data=traj, r_soc=r_soc, l_min=l_min, method=IntrusionMethod.SUM
63+
)
64+
65+
in_01 = ((r_soc - l_min) / (d1 - l_min)) ** 2
66+
in_02 = ((r_soc - l_min) / (d2 - l_min)) ** 2
67+
expected_0 = in_01 + in_02
68+
69+
val = result.loc[result[ID_COL] == 0, INTRUSION_COL].iloc[0]
70+
assert val == pytest.approx(expected_0)
71+
72+
def test_method_max(self):
73+
"""IntrusionMethod.MAX returns the max over neighbors."""
74+
r_soc = 0.8
75+
l_min = 0.2
76+
d1 = 0.5
77+
d2 = 0.6
78+
traj = _make_traj([(0, 0), (d1, 0), (0, d2)])
79+
80+
result = compute_intrusion(
81+
traj_data=traj, r_soc=r_soc, l_min=l_min, method=IntrusionMethod.MAX
82+
)
83+
84+
in_01 = ((r_soc - l_min) / (d1 - l_min)) ** 2
85+
in_02 = ((r_soc - l_min) / (d2 - l_min)) ** 2
86+
expected_0 = max(in_01, in_02)
87+
88+
val = result.loc[result[ID_COL] == 0, INTRUSION_COL].iloc[0]
89+
assert val == pytest.approx(expected_0)
90+
91+
92+
# ---------------------------------------------------------------------------
93+
# Avoidance
94+
# ---------------------------------------------------------------------------
95+
96+
97+
def _make_moving_traj(agent_data, frame_rate=10):
98+
"""Build multi-frame TrajectoryData for agents with constant velocity.
99+
100+
agent_data: list of (x0, y0, vx, vy) per agent.
101+
Creates two frames separated by dt = 1/frame_rate so velocities resolve.
102+
"""
103+
rows = []
104+
dt = 1.0 / frame_rate
105+
for pid, (x0, y0, vx, vy) in enumerate(agent_data):
106+
for frame in range(3):
107+
rows.append(
108+
{
109+
"id": pid,
110+
"frame": frame,
111+
"x": x0 + vx * frame * dt,
112+
"y": y0 + vy * frame * dt,
113+
}
114+
)
115+
return TrajectoryData(data=pd.DataFrame(rows), frame_rate=frame_rate)
116+
117+
118+
class TestComputeAvoidance:
119+
"""Tests for compute_avoidance (Cordes et al. 2024, Eq. 2)."""
120+
121+
def test_head_on_collision(self):
122+
"""Two agents approaching head-on: TTC is analytically known."""
123+
# Agent 0 at x=-2, moving right at v=1
124+
# Agent 1 at x=+2, moving left at v=-1
125+
# At frame 1 (dt=0.1): positions are -1.9 and +1.9, distance=3.8
126+
# Relative speed = 2, TTC = (d - R) / |delta_v| = (3.8 - 0.2) / 2 = 1.8
127+
radius = 0.2
128+
tau_0 = 3.0
129+
d_at_frame1 = 3.8
130+
expected_ttc = (d_at_frame1 - radius) / 2.0
131+
expected_av = tau_0 / expected_ttc
132+
133+
traj = _make_moving_traj([(-2, 0, 1, 0), (2, 0, -1, 0)], frame_rate=10)
134+
result = compute_avoidance(
135+
traj_data=traj, frame_step=1, radius=radius, tau_0=tau_0
136+
)
137+
138+
# Check frame 1 (middle frame where velocity is computed)
139+
row = result[(result[ID_COL] == 0) & (result[FRAME_COL] == 1)]
140+
assert len(row) == 1
141+
assert row[AVOIDANCE_COL].iloc[0] == pytest.approx(expected_av, rel=0.05)
142+
143+
def test_diverging_agents_zero_avoidance(self):
144+
"""Two agents moving apart should have Av = 0 (TTC = inf)."""
145+
tau_0 = 3.0
146+
traj = _make_moving_traj([(-2, 0, -1, 0), (2, 0, 1, 0)], frame_rate=10)
147+
result = compute_avoidance(
148+
traj_data=traj, frame_step=1, radius=0.2, tau_0=tau_0
149+
)
150+
151+
row = result[(result[ID_COL] == 0) & (result[FRAME_COL] == 1)]
152+
assert len(row) == 1
153+
assert row[AVOIDANCE_COL].iloc[0] == pytest.approx(0.0, abs=1e-10)

0 commit comments

Comments
 (0)