1#![allow(dead_code, clippy::too_many_arguments)]
2
3use nalgebra::{DMatrix, DVector, Matrix3, Vector3};
12use rand_distr::{Distribution, Normal};
13use std::f64::consts::PI;
14
15const DT: f64 = 2.0; const MAX_RANGE: f64 = 30.0; const STATE_SIZE: usize = 3; const Q_SIM: [[f64; 2]; 2] = [[0.2, 0.0], [0.0, 0.00030462]]; const R_SIM: [[f64; 2]; 2] = [[0.1, 0.0], [0.0, 0.030462]]; const C_SIGMA1: f64 = 0.1;
26const C_SIGMA2: f64 = 0.1;
27const C_SIGMA3: f64 = 0.017453; const MAX_ITR: usize = 20; #[derive(Clone)]
33pub struct Observation {
34 pub d: f64, pub angle: f64, pub phi: f64, pub id: usize, }
39
40#[derive(Clone)]
42struct Edge {
43 e: Vector3<f64>, omega: Matrix3<f64>, d1: f64,
46 d2: f64,
47 yaw1: f64,
48 yaw2: f64,
49 angle1: f64,
50 angle2: f64,
51 id1: usize,
52 id2: usize,
53}
54
55impl Edge {
56 fn new() -> Self {
57 Edge {
58 e: Vector3::zeros(),
59 omega: Matrix3::zeros(),
60 d1: 0.0,
61 d2: 0.0,
62 yaw1: 0.0,
63 yaw2: 0.0,
64 angle1: 0.0,
65 angle2: 0.0,
66 id1: 0,
67 id2: 0,
68 }
69 }
70}
71
72fn pi_2_pi(angle: f64) -> f64 {
74 let mut a = angle;
75 while a > PI {
76 a -= 2.0 * PI;
77 }
78 while a < -PI {
79 a += 2.0 * PI;
80 }
81 a
82}
83
84fn cal_observation_sigma() -> Matrix3<f64> {
86 Matrix3::from_diagonal(&Vector3::new(
87 C_SIGMA1 * C_SIGMA1,
88 C_SIGMA2 * C_SIGMA2,
89 C_SIGMA3 * C_SIGMA3,
90 ))
91}
92
93fn calc_3d_rotational_matrix(angle: f64) -> Matrix3<f64> {
95 let c = angle.cos();
96 let s = angle.sin();
97 Matrix3::new(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0)
98}
99
100fn calc_edge(
102 x1: f64,
103 y1: f64,
104 yaw1: f64,
105 x2: f64,
106 y2: f64,
107 yaw2: f64,
108 d1: f64,
109 angle1: f64,
110 d2: f64,
111 angle2: f64,
112 t1: usize,
113 t2: usize,
114) -> Edge {
115 let mut edge = Edge::new();
116
117 let tangle1 = pi_2_pi(yaw1 + angle1);
118 let tangle2 = pi_2_pi(yaw2 + angle2);
119
120 let tmp1 = d1 * tangle1.cos();
121 let tmp2 = d2 * tangle2.cos();
122 let tmp3 = d1 * tangle1.sin();
123 let tmp4 = d2 * tangle2.sin();
124
125 edge.e[0] = x2 - x1 - tmp1 + tmp2;
127 edge.e[1] = y2 - y1 - tmp3 + tmp4;
128 edge.e[2] = 0.0;
129
130 let rt1 = calc_3d_rotational_matrix(tangle1);
132 let rt2 = calc_3d_rotational_matrix(tangle2);
133
134 let sig1 = cal_observation_sigma();
135 let sig2 = cal_observation_sigma();
136
137 let cov = rt1 * sig1 * rt1.transpose() + rt2 * sig2 * rt2.transpose();
138 edge.omega = cov.try_inverse().unwrap_or(Matrix3::identity());
139
140 edge.d1 = d1;
141 edge.d2 = d2;
142 edge.yaw1 = yaw1;
143 edge.yaw2 = yaw2;
144 edge.angle1 = angle1;
145 edge.angle2 = angle2;
146 edge.id1 = t1;
147 edge.id2 = t2;
148
149 edge
150}
151
152fn calc_edges(x_list: &DMatrix<f64>, z_list: &[Option<Vec<Observation>>]) -> (Vec<Edge>, f64) {
154 let mut edges = Vec::new();
155 let mut cost = 0.0;
156
157 let nt = z_list.len();
158
159 for t1 in 0..nt {
161 for t2 in (t1 + 1)..nt {
162 let x1 = x_list[(0, t1)];
163 let y1 = x_list[(1, t1)];
164 let yaw1 = x_list[(2, t1)];
165 let x2 = x_list[(0, t2)];
166 let y2 = x_list[(1, t2)];
167 let yaw2 = x_list[(2, t2)];
168
169 if let (Some(z1), Some(z2)) = (&z_list[t1], &z_list[t2]) {
171 for obs1 in z1 {
173 for obs2 in z2 {
174 if obs1.id == obs2.id {
175 let edge = calc_edge(
176 x1, y1, yaw1, x2, y2, yaw2, obs1.d, obs1.angle, obs2.d, obs2.angle,
177 t1, t2,
178 );
179
180 let e_t_omega = edge.e.transpose() * edge.omega;
182 cost += (e_t_omega * edge.e)[(0, 0)];
183
184 edges.push(edge);
185 }
186 }
187 }
188 }
189 }
190 }
191
192 (edges, cost)
193}
194
195fn calc_jacobian(edge: &Edge) -> (Matrix3<f64>, Matrix3<f64>) {
197 let t1 = edge.yaw1 + edge.angle1;
198 let a = Matrix3::new(
199 -1.0,
200 0.0,
201 edge.d1 * t1.sin(),
202 0.0,
203 -1.0,
204 -edge.d1 * t1.cos(),
205 0.0,
206 0.0,
207 0.0,
208 );
209
210 let t2 = edge.yaw2 + edge.angle2;
211 let b = Matrix3::new(
212 1.0,
213 0.0,
214 -edge.d2 * t2.sin(),
215 0.0,
216 1.0,
217 edge.d2 * t2.cos(),
218 0.0,
219 0.0,
220 0.0,
221 );
222
223 (a, b)
224}
225
226fn fill_h_and_b(h: &mut DMatrix<f64>, b: &mut DVector<f64>, edge: &Edge) {
228 let (a, b_jac) = calc_jacobian(edge);
229
230 let id1 = edge.id1 * STATE_SIZE;
231 let id2 = edge.id2 * STATE_SIZE;
232
233 let at_omega = a.transpose() * edge.omega;
235 let at_omega_a = at_omega * a;
236 let at_omega_b = at_omega * b_jac;
237
238 let bt_omega = b_jac.transpose() * edge.omega;
239 let bt_omega_a = bt_omega * a;
240 let bt_omega_b = bt_omega * b_jac;
241
242 for i in 0..STATE_SIZE {
243 for j in 0..STATE_SIZE {
244 h[(id1 + i, id1 + j)] += at_omega_a[(i, j)];
245 h[(id1 + i, id2 + j)] += at_omega_b[(i, j)];
246 h[(id2 + i, id1 + j)] += bt_omega_a[(i, j)];
247 h[(id2 + i, id2 + j)] += bt_omega_b[(i, j)];
248 }
249 }
250
251 let at_omega_e = at_omega * edge.e;
253 let bt_omega_e = bt_omega * edge.e;
254
255 for i in 0..STATE_SIZE {
256 b[id1 + i] += at_omega_e[i];
257 b[id2 + i] += bt_omega_e[i];
258 }
259}
260
261pub fn graph_based_slam(x_init: &DMatrix<f64>, hz: &[Option<Vec<Observation>>]) -> DMatrix<f64> {
263 let mut x_opt = x_init.clone();
264 let nt = x_opt.ncols();
265 let n = nt * STATE_SIZE;
266
267 for _itr in 0..MAX_ITR {
268 let (edges, _cost) = calc_edges(&x_opt, hz);
269
270 let mut h = DMatrix::<f64>::zeros(n, n);
271 let mut b = DVector::<f64>::zeros(n);
272
273 for edge in &edges {
274 fill_h_and_b(&mut h, &mut b, edge);
275 }
276
277 for i in 0..STATE_SIZE {
279 h[(i, i)] += 1.0;
280 }
281
282 let h_inv = match h.clone().try_inverse() {
284 Some(inv) => inv,
285 None => {
286 let mut h_reg = h.clone();
288 for i in 0..n {
289 h_reg[(i, i)] += 0.001;
290 }
291 h_reg.try_inverse().unwrap_or(DMatrix::identity(n, n))
292 }
293 };
294
295 let dx = -h_inv * &b;
296
297 for i in 0..nt {
299 for j in 0..STATE_SIZE {
300 x_opt[(j, i)] += dx[i * STATE_SIZE + j];
301 }
302 }
303
304 let diff = dx.dot(&dx);
305
306 if diff < 1.0e-5 {
307 break;
308 }
309 }
310
311 x_opt
312}
313
314pub fn motion_model(x: &Vector3<f64>, u: &[f64; 2]) -> Vector3<f64> {
316 let yaw = x[2];
317 Vector3::new(
318 x[0] + u[0] * DT * yaw.cos(),
319 x[1] + u[0] * DT * yaw.sin(),
320 x[2] + u[1] * DT,
321 )
322}
323
324pub fn calc_input() -> [f64; 2] {
326 let v = 1.0; let yaw_rate = 0.1; [v, yaw_rate]
329}
330
331pub fn observation(
333 x_true: &Vector3<f64>,
334 xd: &Vector3<f64>,
335 u: &[f64; 2],
336 rfid: &[(f64, f64)],
337) -> (
338 Vector3<f64>,
339 Option<Vec<Observation>>,
340 Vector3<f64>,
341 [f64; 2],
342) {
343 let normal = Normal::new(0.0, 1.0).unwrap();
344 let mut rng = rand::rng();
345
346 let x_true_new = motion_model(x_true, u);
348
349 let mut z = Vec::new();
351 for (i, (lx, ly)) in rfid.iter().enumerate() {
352 let dx = lx - x_true_new[0];
353 let dy = ly - x_true_new[1];
354 let d = (dx * dx + dy * dy).sqrt();
355 let angle = pi_2_pi(dy.atan2(dx) - x_true_new[2]);
356 let phi = pi_2_pi(dy.atan2(dx));
357
358 if d <= MAX_RANGE {
359 let dn = d + normal.sample(&mut rng) * Q_SIM[0][0].sqrt();
361 let angle_noise = normal.sample(&mut rng) * Q_SIM[1][1].sqrt();
362 let angle_noisy = angle + angle_noise;
363 let phi_noisy = phi + angle_noise;
364
365 z.push(Observation {
366 d: dn,
367 angle: angle_noisy,
368 phi: phi_noisy,
369 id: i,
370 });
371 }
372 }
373
374 let ud = [
376 u[0] + normal.sample(&mut rng) * R_SIM[0][0].sqrt(),
377 u[1] + normal.sample(&mut rng) * R_SIM[1][1].sqrt(),
378 ];
379 let xd_new = motion_model(xd, &ud);
380
381 let z_opt = if z.is_empty() { None } else { Some(z) };
382
383 (x_true_new, z_opt, xd_new, ud)
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 const EPS: f64 = 1e-10;
391
392 fn assert_near(a: f64, b: f64, eps: f64) {
393 assert!(
394 (a - b).abs() < eps,
395 "expected {b}, got {a} (diff={})",
396 (a - b).abs()
397 );
398 }
399
400 #[test]
401 fn test_motion_model() {
402 let x = Vector3::new(0.0, 0.0, 0.0);
404 let u = [1.0, 0.0]; let x_new = motion_model(&x, &u);
406
407 assert_near(x_new[0], 2.0, EPS);
409 assert_near(x_new[1], 0.0, EPS);
410 assert_near(x_new[2], 0.0, EPS);
411
412 let x2 = Vector3::new(1.0, 2.0, std::f64::consts::FRAC_PI_2);
414 let u2 = [1.0, 0.0];
415 let x2_new = motion_model(&x2, &u2);
416
417 assert_near(x2_new[0], 1.0, 1e-9); assert_near(x2_new[1], 4.0, 1e-9); assert_near(x2_new[2], std::f64::consts::FRAC_PI_2, EPS);
420 }
421
422 #[test]
423 fn test_calc_input() {
424 let u = calc_input();
425 assert_near(u[0], 1.0, EPS); assert_near(u[1], 0.1, EPS); }
428
429 #[test]
430 fn test_observation_struct() {
431 let obs = Observation {
432 d: 5.0,
433 angle: 0.3,
434 phi: 1.2,
435 id: 7,
436 };
437 assert_near(obs.d, 5.0, EPS);
438 assert_near(obs.angle, 0.3, EPS);
439 assert_near(obs.phi, 1.2, EPS);
440 assert_eq!(obs.id, 7);
441
442 let obs2 = obs.clone();
444 assert_eq!(obs2.id, 7);
445 assert_near(obs2.d, 5.0, EPS);
446 }
447
448 #[test]
449 fn test_motion_model_zero_input() {
450 let x = Vector3::new(3.0, 4.0, 0.5);
451 let u = [0.0, 0.0];
452 let x_new = motion_model(&x, &u);
453
454 assert_near(x_new[0], 3.0, EPS);
456 assert_near(x_new[1], 4.0, EPS);
457 assert_near(x_new[2], 0.5, EPS);
458 }
459
460 #[test]
461 fn test_graph_based_slam_small() {
462 let n_poses = 3;
465 let mut x_init = DMatrix::<f64>::zeros(3, n_poses);
466 for i in 0..n_poses {
468 x_init[(0, i)] = i as f64 * 2.0;
469 x_init[(1, i)] = 0.0;
470 x_init[(2, i)] = 0.0;
471 }
472
473 let hz: Vec<Option<Vec<Observation>>> = vec![
475 Some(vec![Observation {
476 d: 5.0,
477 angle: 0.3,
478 phi: 0.3,
479 id: 0,
480 }]),
481 None,
482 Some(vec![Observation {
483 d: 3.0,
484 angle: -0.2,
485 phi: -0.2,
486 id: 0,
487 }]),
488 ];
489
490 let result = graph_based_slam(&x_init, &hz);
491
492 assert_eq!(result.nrows(), 3);
494 assert_eq!(result.ncols(), n_poses);
495
496 for i in 0..result.nrows() {
498 for j in 0..result.ncols() {
499 assert!(
500 result[(i, j)].is_finite(),
501 "Result contains non-finite value at ({i}, {j})"
502 );
503 }
504 }
505 }
506}