path_planning/cspace/leader_follower/
spherical.rs

1/* Copyright (C) 2020 Dylan Staatz - All Rights Reserved. */
2
3use nalgebra::constraint::{SameNumberOfRows, ShapeConstraint};
4use nalgebra::geometry::UnitQuaternion;
5use nalgebra::storage::Storage;
6use nalgebra::{Const, Dim, RealField, SVector, Unit, Vector};
7use num_traits::Float;
8use rand::distributions::{uniform::SampleUniform, Distribution};
9use rand::{thread_rng, SeedableRng};
10use serde::{de::DeserializeOwned, Deserialize, Serialize};
11
12use crate::error::{InvalidParamError, Result};
13use crate::params::FromParams;
14use crate::rng::{LinearCoordinates, RNG};
15use crate::scalar::Scalar;
16use crate::trajectories::{EuclideanTrajectory, FullTraj};
17use crate::util::bounds::Bounds;
18use crate::util::math::{atan2, unit_d_ball_vol};
19
20use super::super::CSpace;
21use super::polar::saturate_polar_zero;
22use super::LeaderFollowerCSpace;
23
24pub const D: usize = 3;
25pub const N: usize = D * 2;
26
27#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
28#[serde(bound(
29  serialize = "X: Serialize",
30  deserialize = "X: DeserializeOwned"
31))]
32pub struct LeaderFollowerSphericalSpaceParams<X: Scalar> {
33  pub bounds: Bounds<X, D>,
34  pub seed: Option<u64>,
35  pub sensor_range: (X, X),
36}
37
38/// A space with the state vector of [x1, y1, r2, theta2]
39///
40/// x1: x-coordinate of the leader robot
41/// y1: y-coordinate of the leader robot
42/// r2: radius polar coordinate offset of the follower relative to the leader
43/// theta2: angle off of x-axis polar coordinate offset of the follower relative
44/// to the leader
45pub struct LeaderFollowerSphericalSpace<X>
46where
47  X: Scalar + SampleUniform,
48{
49  bounds: Bounds<X, D>,
50  volume: X,
51  rng: RNG,
52  distribution: LinearCoordinates<X, N>,
53  intial_sensor_range: (X, X),
54  sensor_range: (X, X),
55  sensor_range_cubed: (X, X),
56}
57
58impl<X> LeaderFollowerSphericalSpace<X>
59where
60  X: Scalar + SampleUniform,
61{
62  pub fn new(
63    bounds: Bounds<X, D>,
64    rng: RNG,
65    sensor_range: (X, X),
66  ) -> Result<Self> {
67    debug_assert_eq!(D * 2, N);
68
69    // Validate bounds
70    if !bounds.is_valid() {
71      Err(InvalidParamError {
72        parameter_name: "bounds",
73        parameter_value: format!("{:?}", bounds),
74      })?;
75    }
76
77    // Validate sensor range
78    if !(sensor_range.0 < sensor_range.1) {
79      Err(InvalidParamError {
80        parameter_name: "sensor_range",
81        parameter_value: format!("{:?}", sensor_range),
82      })?;
83    }
84
85    let sensor_range_cubed = (
86      sensor_range.0 * sensor_range.0 * sensor_range.0,
87      sensor_range.1 * sensor_range.1 * sensor_range.1,
88    );
89
90    let sensor_space_volume = (sensor_range_cubed.1 * unit_d_ball_vol(D))
91      - (sensor_range_cubed.0 * unit_d_ball_vol(D));
92    let volume = bounds.volume() * sensor_space_volume;
93
94    let mins = SVector::<X, N>::from([
95      bounds.mins[0],  // x1
96      bounds.mins[1],  // y1
97      bounds.mins[2],  // z1
98      X::zero(),       // rho2
99      X::zero(),       // lambda2
100      -X::frac_pi_2(), // phi2
101    ]);
102
103    let maxs = SVector::<X, N>::from([
104      bounds.maxs[0], // x1
105      bounds.maxs[1], // y1
106      bounds.maxs[2], // z1
107      X::one(),       // rho2
108      X::two_pi(),    // lambda2
109      X::frac_pi_2(), // phi2
110    ]);
111
112    let distribution = LinearCoordinates::new(mins, maxs);
113
114    Ok(Self {
115      bounds,
116      volume,
117      rng,
118      distribution,
119      intial_sensor_range: sensor_range,
120      sensor_range,
121      sensor_range_cubed,
122    })
123  }
124
125  pub fn intial_sensor_range(&self) -> (X, X) {
126    self.intial_sensor_range
127  }
128
129  pub fn get_sensor_range(&self) -> (X, X) {
130    self.sensor_range
131  }
132
133  pub fn set_sensor_range(&mut self, sensor_range: (X, X)) -> Option<()> {
134    if sensor_range.0 < sensor_range.1 {
135      // Valid
136      self.sensor_range = sensor_range;
137      self.sensor_range_cubed = (
138        sensor_range.0 * sensor_range.0 * sensor_range.0,
139        sensor_range.1 * sensor_range.1 * sensor_range.1,
140      );
141      Some(())
142    } else {
143      // Invalid value
144      None
145    }
146  }
147}
148
149impl<X> LeaderFollowerCSpace<X, D, N> for LeaderFollowerSphericalSpace<X>
150where
151  X: Scalar + SampleUniform,
152{
153  // Default implementation okay
154}
155
156impl<X> CSpace<X, N> for LeaderFollowerSphericalSpace<X>
157where
158  X: Scalar + SampleUniform,
159{
160  type Traj = EuclideanTrajectory<X, N>;
161
162  fn volume(&self) -> X {
163    self.volume
164  }
165
166  fn cost<R1, R2, S1, S2>(
167    &self,
168    a: &Vector<X, R1, S1>,
169    b: &Vector<X, R2, S2>,
170  ) -> X
171  where
172    X: Scalar,
173    R1: Dim,
174    R2: Dim,
175    S1: Storage<X, R1>,
176    S2: Storage<X, R2>,
177    ShapeConstraint: SameNumberOfRows<R1, R2>
178      + SameNumberOfRows<R1, Const<N>>
179      + SameNumberOfRows<R2, Const<N>>,
180  {
181    a.metric_distance(b)
182  }
183
184  fn trajectory<S1, S2>(
185    &self,
186    start: Vector<X, Const<N>, S1>,
187    end: Vector<X, Const<N>, S2>,
188  ) -> Option<FullTraj<X, Self::Traj, S1, S2, N>>
189  where
190    X: Scalar,
191    S1: Storage<X, Const<N>>,
192    S2: Storage<X, Const<N>>,
193  {
194    Some(FullTraj::new(start, end, EuclideanTrajectory::new()))
195  }
196
197  fn is_free<S>(&self, a: &Vector<X, Const<N>, S>) -> bool
198  where
199    S: Storage<X, Const<N>>,
200  {
201    let leader_abs = a.fixed_rows::<D>(0);
202    let follower_abs = a.fixed_rows::<D>(D);
203
204    if !self.bounds.within(&follower_abs) {
205      return false;
206    }
207
208    // Determine distance between leader and follower
209    let rho2 = leader_abs.metric_distance(&follower_abs);
210    self.sensor_range.0 <= rho2 && rho2 <= self.sensor_range.1
211  }
212
213  fn saturate(&self, a: &mut SVector<X, N>, b: &SVector<X, N>, delta: X) {
214    let delta = delta / (X::one() + X::one());
215
216    // Saturate leader to be delta away
217    let mut a_lead_mut = a.fixed_rows_mut::<D>(0);
218    let b_lead = b.fixed_rows::<D>(0);
219
220    let lead_scale = delta / a_lead_mut.metric_distance(&b_lead);
221
222    a_lead_mut.set_column(0, &(&a_lead_mut - &b_lead));
223    a_lead_mut.set_column(0, &(&a_lead_mut * lead_scale));
224    a_lead_mut.set_column(0, &(&a_lead_mut + &b_lead));
225
226    // Saturate follower to be delta away
227    let a_lead = a.fixed_rows::<D>(0);
228    let a_fol = a.fixed_rows::<D>(D);
229
230    // Modifiy b follower to be an offset of the a_lead
231    let mut b = b.clone(); // Local copy of b for temporary modifications
232    let mut b_lead_mut = b.fixed_rows_mut::<D>(0);
233    b_lead_mut.set_column(0, &a_lead); // A leader, B follower
234    let mut b_rel = abs_to_rel(&b); // B follower relative to new A leader
235
236    // Narrow down to spherical coordinates only
237    let mut b_fol_rel_mut = b_rel.fixed_rows_mut::<D>(D).into_owned();
238    let a_fol_rel = cartesian_to_spherical(&a_fol);
239
240    // Bounds
241    let mut delta = delta;
242    if self.sensor_range.1 < b_fol_rel_mut[0] {
243      delta -= b_fol_rel_mut[0] - self.sensor_range.1;
244      b_fol_rel_mut[0] = self.sensor_range.1;
245    }
246    if b_fol_rel_mut[0] < self.sensor_range.0 {
247      delta -= self.sensor_range.0 - b_fol_rel_mut[0];
248      b_fol_rel_mut[0] = self.sensor_range.0;
249    }
250
251    if X::zero() <= delta {
252      let mut star_fol_rel =
253        saturate_spherical(&a_fol_rel, &b_fol_rel_mut, delta);
254
255      log::debug!(
256        "sensor_range: {:?}, star_fol_rel: {:?}",
257        self.sensor_range,
258        <[X; D]>::from(star_fol_rel)
259      );
260
261      // bound rho
262      if self.sensor_range.1 < star_fol_rel[0] {
263        star_fol_rel[0] = self.sensor_range.1;
264      }
265      if star_fol_rel[0] < self.sensor_range.0 {
266        star_fol_rel[0] = self.sensor_range.0;
267      }
268
269      // Set a in relative spherical coordinates
270      let mut a_fol_mut = a.fixed_rows_mut::<D>(D);
271      a_fol_mut.set_column(0, &star_fol_rel);
272    } else {
273      // Set a in relative spherical coordinates
274      let mut a_fol_mut = a.fixed_rows_mut::<D>(D);
275      a_fol_mut.set_column(0, &b_fol_rel_mut);
276    }
277
278    // Convert relative spherical coordinates back to absolute
279    *a = rel_to_abs(&a);
280  }
281
282  fn sample(&mut self) -> SVector<X, N> {
283    let mut sample = self.distribution.sample(&mut self.rng);
284
285    // Scale rho from [0,1] to the correct range
286    let a_3 = self.sensor_range_cubed.0;
287    let b_3 = self.sensor_range_cubed.1;
288    sample[3] = <X as Float>::cbrt(sample[3] * (b_3 - a_3) + a_3);
289
290    rel_to_abs(&sample)
291  }
292}
293
294impl<X> FromParams for LeaderFollowerSphericalSpace<X>
295where
296  X: Scalar + SampleUniform,
297{
298  type Params = LeaderFollowerSphericalSpaceParams<X>;
299  fn from_params(params: Self::Params) -> Result<Self> {
300    let rng = match params.seed {
301      Some(seed) => RNG::seed_from_u64(seed),
302      None => RNG::from_rng(thread_rng())?,
303    };
304
305    LeaderFollowerSphericalSpace::new(params.bounds, rng, params.sensor_range)
306  }
307}
308
309/// Converts [x1, y1, z1, rho2, lambda2, phi2] to [x1, y1, z1, x2, y2, z2]
310///
311/// Inverse of [`abs_to_rel`]
312pub fn rel_to_abs<X, R, S>(v: &Vector<X, R, S>) -> SVector<X, N>
313where
314  X: RealField + Copy,
315  R: Dim,
316  S: Storage<X, R>,
317  ShapeConstraint: SameNumberOfRows<R, Const<N>>,
318{
319  let x1 = v[0];
320  let y1 = v[1];
321  let z1 = v[2];
322  let x2 = x1 + (v[3] * v[5].cos() * v[4].cos());
323  let y2 = y1 + (v[3] * v[5].cos() * v[4].sin());
324  let z2 = z1 + (v[3] * v[5].sin());
325
326  [x1, y1, z1, x2, y2, z2].into()
327}
328
329/// Converts [rho, lambda, phi] to [x, y, z]
330pub fn spherical_to_cartesian<X, R, S>(v: &Vector<X, R, S>) -> SVector<X, D>
331where
332  X: RealField + Copy,
333  R: Dim,
334  S: Storage<X, R>,
335  ShapeConstraint: SameNumberOfRows<R, Const<D>>,
336{
337  [
338    v[0] * v[2].cos() * v[1].cos(),
339    v[0] * v[2].cos() * v[1].sin(),
340    v[0] * v[2].sin(),
341  ]
342  .into()
343}
344
345/// Converts [x1, y1, z1, x2, y2, z2] to [x1, y1, z1, rho2, lambda2, phi2]
346///
347/// Inverse of [`rel_to_abs`]
348pub fn abs_to_rel<X, R, S>(v: &Vector<X, R, S>) -> SVector<X, N>
349where
350  X: RealField + Copy,
351  R: Dim,
352  S: Storage<X, R>,
353  ShapeConstraint: SameNumberOfRows<R, Const<N>>,
354{
355  let x1 = v[0];
356  let y1 = v[1];
357  let z1 = v[2];
358  let x2 = v[3] - x1; // offset cartesian
359  let y2 = v[4] - y1; // offset cartesian
360  let z2 = v[5] - z1; // offset cartesian
361
362  let rho2 = (x2 * x2 + y2 * y2 + z2 * z2).sqrt();
363  let lambda2 = atan2(y2, x2).unwrap_or(X::zero());
364  let phi2 = atan2(z2, (x2 * x2 + y2 * y2).sqrt()).unwrap_or(X::zero());
365
366  [x1, y1, z1, rho2, bound_lambda(lambda2), bound_phi(phi2)].into()
367}
368
369/// Converts [x, y, z] to [rho, lambda, phi]
370pub fn cartesian_to_spherical<X, R, S>(v: &Vector<X, R, S>) -> SVector<X, D>
371where
372  X: RealField + Copy,
373  R: Dim,
374  S: Storage<X, R>,
375  ShapeConstraint: SameNumberOfRows<R, Const<D>>,
376{
377  let rho2 = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
378  let lambda2 = atan2(v[1], v[0]).unwrap_or(X::zero());
379  let phi2 =
380    atan2(v[2], (v[0] * v[0] + v[1] * v[1]).sqrt()).unwrap_or(X::zero());
381  [rho2, bound_lambda(lambda2), bound_phi(phi2)].into()
382}
383
384fn saturate_spherical<X, R1, S1, R2, S2>(
385  a: &Vector<X, R1, S1>,
386  b: &Vector<X, R2, S2>,
387  delta: X,
388) -> SVector<X, D>
389where
390  X: RealField + Copy,
391  R1: Dim,
392  R2: Dim,
393  S1: Storage<X, R1>,
394  S2: Storage<X, R2>,
395  ShapeConstraint: SameNumberOfRows<R1, R2>
396    + SameNumberOfRows<R1, Const<D>>
397    + SameNumberOfRows<R2, Const<D>>,
398{
399  let rho_a = a[0];
400  let lambda_a = a[1];
401  let phi_a = a[2];
402  let rho_b = b[0];
403  let lambda_b = b[1];
404  let phi_b = b[2];
405
406  // Normal Vectors: https://en.wikipedia.org/wiki/N-vector
407  let n_a = SVector::<X, D>::from([
408    phi_a.cos() * lambda_a.cos(),
409    phi_a.cos() * lambda_a.sin(),
410    phi_a.sin(),
411  ]);
412
413  let n_b = SVector::<X, D>::from([
414    phi_b.cos() * lambda_b.cos(),
415    phi_b.cos() * lambda_b.sin(),
416    phi_b.sin(),
417  ]);
418
419  // Slicing planes normal (b -> a)
420  let n = n_b.cross(&n_a);
421  let (n, magnitude) = Unit::new_and_get(n);
422
423  // The angle formed by the great circle path: https://en.wikipedia.org/wiki/Great-circle_distance#Vector_version
424  let omega = atan2(magnitude, n_b.dot(&n_a)).unwrap_or(X::zero());
425
426  // Setup 2D problem in polar coordinates on slicing plane
427  let r_a = rho_a;
428  let theta_a = omega;
429  let r_b = rho_b;
430
431  log::debug!("a: [{:?}, {:?}]", r_a, theta_a);
432  log::debug!("b: [{:?}, {:?}]", r_b, 0.0);
433
434  let (r_star, theta_star) = saturate_polar_zero(r_a, theta_a, r_b, delta);
435
436  log::debug!("*: [{:?}, {:?}]", r_star, theta_star);
437
438  // Convert 2D solution back to 3D
439  let rotation = UnitQuaternion::from_axis_angle(&n, theta_star);
440  let v = rotation * n_b;
441
442  // Return bounded
443  let rho_star = r_star;
444  let lambda_star = atan2(v.y, v.x).unwrap_or(X::zero());
445  let phi_star =
446    atan2(v.z, (v.x.powi(2) + v.y.powi(2)).sqrt()).unwrap_or(X::zero());
447
448  [rho_star, bound_lambda(lambda_star), bound_phi(phi_star)].into()
449}
450
451/// Longitude
452fn bound_lambda<X>(mut lambda: X) -> X
453where
454  X: RealField + Copy,
455{
456  // Lower bound (Inclusive)
457  while lambda < X::zero() {
458    lambda += X::two_pi()
459  }
460  // Upper bound (Exclusive)
461  while X::two_pi() <= lambda {
462    lambda -= X::two_pi()
463  }
464  lambda
465}
466
467/// Latitude
468fn bound_phi<X>(mut phi: X) -> X
469where
470  X: RealField + Copy,
471{
472  // Lower bound (Inclusive)
473  while phi < -X::frac_pi_2() {
474    phi += X::pi()
475  }
476  // Upper bound (Inclusive)
477  while X::frac_pi_2() < phi {
478    phi -= X::pi()
479  }
480  phi
481}