An orchard robot knows its pose pose = (x, y, theta) exactly (metres, x to the right, y up; heading in radians counter-clockwise from +x). Its map lists tree trunks: landmarks[j] = (mx, my) with a 2 by 2 covariance covs[j] saying how well the map knows that trunk's position (in the world frame). Its lidar reports observations, each a trunk position (f, l) in the robot's frame: f metres forward and l metres to the robot's left. The lidar's error has covariance R in the robot's frame (forward first, left second).
To decide which trunk an observation belongs to, the robot:
- converts the observation to a world point p;
- for every landmark j, forms the difference d = p - m_j and the combined covariance S_j = \Sigma_j + R_w, where R_w = \text{Rot}(\theta)\, R\, \text{Rot}(\theta)^T is the lidar's covariance turned into the world frame and \text{Rot}(\theta) = \begin{bmatrix}\cos\theta & -\sin\theta\\ \sin\theta & \cos\theta\end{bmatrix};
- computes the squared Mahalanobis distance d^T S_j^{-1} d: a distance measured in standard deviations, so a miss along a direction the map is unsure about counts for little;
- matches the observation to the landmark with the smallest squared distance among those at most
gate; if several share that smallest value, the lowest index. If no landmark is within the gate, the observation is unmatched:-1.
Each observation is matched on its own (two observations may get the same landmark). Write associate(pose, observations, landmarks, covs, R, gate) that returns the list of matched indices, one per observation. numpy is available (np.linalg.solve(S, d) or np.linalg.inv(S)).
Examples
Input: pose = (0, 0, 0), observations = [(5, 0.5), (8, 0)], landmarks = [(5, 0), (5, 1.2)],
covs = [[[0.01, 0], [0, 0.01]], [[0.01, 0], [0, 1.0]]], R = [[0.04, 0], [0, 0.04]], gate = 5.99
Output: [1, -1]
Explanation: (5, 0.5) is nearer trunk 0 (0.5 m against 0.7 m), but the map knows trunk 1's
y only to within about 1 m: the squared distances are 0.25 / 0.05 = 5.0 and 0.49 / 1.04 = 0.47.
(8, 0) is 3 m from both trunks, far outside the gate.
Input: pose = (0, 0, math.pi / 2), observations = [(6, 0)], landmarks = [(0, 5), (0.5, 6)],
covs = [[[0.01, 0], [0, 0.01]], [[0.01, 0], [0, 0.01]]], R = [[1.0, 0], [0, 0.01]], gate = 5.99
Output: [0]
Explanation: the lidar is unsure along its beam (variance 1.0 forward). The robot faces +y,
so in the world that doubt lies along y: trunk 0 is 1 m off along y (0.99), trunk 1 is 0.5 m
off along x, where the lidar is sharp (12.5).
Constraints
covshas one entry per landmark; everycovs[j]andRis symmetric, and every S_j is invertiblegate > 0; there may be no landmarks or no observations
Goals
- Match each observation to a known landmark by Mahalanobis distance, not by plain distance
- Combine the map's uncertainty with the sensor's, rotated into the world frame
- Leave an observation unmatched when no landmark is inside the gate