1use crate::drive_cycle::Cycle;
2use crate::imports::*;
3
4pub struct RendezvousTrajectory {
7 pub found_trajectory: bool,
8 pub idx: usize,
9 pub n: usize,
10 pub full_brake_steps: usize,
11 pub jerk_m_per_s3: f64,
12 pub accel0_m_per_s2: f64,
13 pub accel_spread: f64,
14}
15
16pub struct CoastTrajectory {
18 pub found_trajectory: bool,
19 pub distance_to_stop_via_coast_m: f64,
20 pub start_idx: usize,
21 pub speed_m_per_s: Option<Vec<f64>>,
22 pub distance_to_brake_m: Option<f64>,
23}
24
25#[serde_api]
26#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
27#[non_exhaustive]
28#[serde(deny_unknown_fields)]
29#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
30pub struct ConstantJerkTrajectory {
32 pub steps: usize,
34 pub distance_m: f64,
36 pub speed_m_per_s: f64,
38 pub acceleration_m_per_s2: f64,
40 pub jerk_m_per_s3: f64,
42 pub step_duration_s: f64,
44}
45
46impl SerdeAPI for ConstantJerkTrajectory {}
47impl Init for ConstantJerkTrajectory {}
48
49impl ConstantJerkTrajectory {
50 pub fn from_speed_and_distance_targets(
60 n: usize,
61 d0: f64,
62 v0: f64,
63 dr: f64,
64 vr: f64,
65 dt: f64,
66 ) -> ConstantJerkTrajectory {
67 assert!(n > 1);
68 assert!(dr > d0);
69 let n_orig = n;
70 let n = n as f64;
71 let ddr = dr - d0;
72 let dvr = vr - v0;
73 let k = (dvr - (2.0 * ddr / (n * dt)) + 2.0 * v0)
74 / (0.5 * n * (n - 1.0) * dt
75 - (1.0 / 3.0) * (n - 1.0) * (n - 2.0) * dt
76 - 0.5 * (n - 1.0) * dt * dt);
77 let a0 = ((ddr / dt)
78 - n * v0
79 - ((1.0 / 6.0) * n * (n - 1.0) * (n - 2.0) * dt + 0.25 * n * (n - 1.0) * dt * dt) * k)
80 / (0.5 * n * n * dt);
81 ConstantJerkTrajectory {
82 steps: n_orig,
83 distance_m: d0,
84 speed_m_per_s: v0,
85 acceleration_m_per_s2: a0,
86 jerk_m_per_s3: k,
87 step_duration_s: dt,
88 }
89 }
90 pub fn distance_at_step(&self, n: usize) -> f64 {
92 let n = n as f64;
93 let d0 = self.distance_m;
94 let v0 = self.speed_m_per_s;
95 let a0 = self.acceleration_m_per_s2;
96 let k = self.jerk_m_per_s3;
97 let dt = self.step_duration_s;
98 let term1 = dt
99 * ((n * v0)
100 + (0.5 * n * (n - 1.0) * a0 * dt)
101 + ((1.0 / 6.0) * k * dt * (n - 2.0) * (n - 1.0) * n));
102 let term2 = 0.5 * dt * dt * ((n * a0) + (0.5 * n * (n - 1.0) * k * dt));
103 d0 + term1 + term2
104 }
105 pub fn end_distance(&self) -> f64 {
107 self.distance_at_step(self.steps)
108 }
109 pub fn speed_at_step(&self, n: usize) -> f64 {
111 let n = n as f64;
112 let v0 = self.speed_m_per_s;
113 let a0 = self.acceleration_m_per_s2;
114 let k = self.jerk_m_per_s3;
115 let dt = self.step_duration_s;
116 v0 + (n * a0 * dt) + (0.5 * n * (n - 1.0) * k * dt)
117 }
118 pub fn end_speed(&self) -> f64 {
120 self.speed_at_step(self.steps)
121 }
122 pub fn acceleration_at_step(&self, n: usize) -> f64 {
124 let n = n as f64;
125 let a0 = self.acceleration_m_per_s2;
126 let k = self.jerk_m_per_s3;
127 let dt = self.step_duration_s;
128 a0 + (n * k * dt)
129 }
130 pub fn end_acceleration(&self) -> f64 {
132 self.acceleration_at_step(self.steps)
133 }
134 pub fn all_accelerations(&self) -> Vec<f64> {
137 let mut accels = Vec::with_capacity(self.steps);
138 for n_idx in 0..self.steps {
139 accels.push(self.acceleration_at_step(n_idx));
140 }
141 accels
142 }
143 pub fn maximum_acceleration(&self) -> f64 {
145 let accels = self.all_accelerations();
146 *accels.max().unwrap_or(&0.0)
147 }
148}
149
150#[cfg_attr(feature = "pyo3", pyfunction)]
151pub fn calc_constant_jerk_trajectory(
160 n: usize,
161 d0: f64,
162 v0: f64,
163 dr: f64,
164 vr: f64,
165 dt: f64,
166) -> ConstantJerkTrajectory {
167 assert!(n > 1);
168 assert!(dr > d0);
169 assert!(v0 >= 0.0);
170 assert!(vr >= 0.0);
171 assert!(dt > 0.0);
172 ConstantJerkTrajectory::from_speed_and_distance_targets(n, d0, v0, dr, vr, dt)
173}
174
175#[cfg_attr(feature = "pyo3", pyfunction)]
176pub fn dist_for_constant_jerk(n: usize, d0: f64, v0: f64, a0: f64, k: f64, dt: f64) -> f64 {
187 let trajectory = ConstantJerkTrajectory {
188 steps: n,
189 distance_m: d0,
190 speed_m_per_s: v0,
191 acceleration_m_per_s2: a0,
192 jerk_m_per_s3: k,
193 step_duration_s: dt,
194 };
195 trajectory.end_distance()
196}
197
198#[cfg_attr(feature = "pyo3", pyfunction)]
199pub fn speed_for_constant_jerk(n: usize, v0: f64, a0: f64, k: f64, dt: f64) -> f64 {
211 let trajectory = ConstantJerkTrajectory {
212 steps: n,
213 distance_m: 0.0,
214 speed_m_per_s: v0,
215 acceleration_m_per_s2: a0,
216 jerk_m_per_s3: k,
217 step_duration_s: dt,
218 };
219 trajectory.end_speed()
220}
221
222#[cfg_attr(feature = "pyo3", pyfunction)]
223pub fn accel_for_constant_jerk(n: usize, a0: f64, k: f64, dt: f64) -> f64 {
232 let trajectory = ConstantJerkTrajectory {
233 steps: n,
234 distance_m: 0.0,
235 speed_m_per_s: 0.0,
236 acceleration_m_per_s2: a0,
237 jerk_m_per_s3: k,
238 step_duration_s: dt,
239 };
240 trajectory.end_acceleration()
241}
242
243#[cfg_attr(feature = "pyo3", pyfunction)]
244pub fn accel_array_for_constant_jerk(n: usize, a0: f64, k: f64, dt: f64) -> Vec<f64> {
251 let trajectory = ConstantJerkTrajectory {
252 steps: n,
253 distance_m: 0.0,
254 speed_m_per_s: 0.0,
255 acceleration_m_per_s2: a0,
256 jerk_m_per_s3: k,
257 step_duration_s: dt,
258 };
259 trajectory.all_accelerations()
260}
261
262pub fn average_step_speeds(cyc: &Cycle) -> Vec<si::Velocity> {
268 cyc.average_step_speeds()
269}
270
271pub fn average_step_speed_at(cyc: &Cycle, i: usize) -> si::Velocity {
274 cyc.average_step_speed_at(i)
275}
276
277pub fn trapz_step_distances(cyc: &Cycle) -> Vec<si::Length> {
280 cyc.trapz_step_distances()
281}
282
283pub fn trapz_step_start_distance(cyc: &Cycle, i: usize) -> si::Length {
286 cyc.trapz_step_start_distance(i)
287}
288
289pub fn trapz_distance_for_step(cyc: &Cycle, i: usize) -> si::Length {
292 cyc.trapz_distance_for_step(i)
293}
294
295pub fn trapz_distance_over_range(cyc: &Cycle, i_start: usize, i_end: usize) -> si::Length {
298 cyc.trapz_distance_over_range(i_start, i_end)
299}
300
301pub fn time_spent_moving(cyc: &Cycle, stopped_speed: Option<si::Velocity>) -> si::Time {
306 cyc.time_spent_moving(stopped_speed)
307}
308
309pub fn create_distance_and_target_speeds_by_microtrip(
323 cyc: &Cycle,
324 stop_speed: Option<si::Velocity>,
325 blend_factor: f64,
326 min_target_speed: si::Velocity,
327) -> Vec<(si::Length, si::Velocity)> {
328 cyc.distance_and_target_speeds_by_microtrip(stop_speed, blend_factor, min_target_speed)
329}
330
331pub fn extend_cycle_time(
341 cyc: &Cycle,
342 absolute_time: Option<si::Time>,
343 time_fraction: Option<si::Ratio>,
344) -> Cycle {
345 cyc.extend_time(absolute_time, time_fraction)
346}
347
348#[serde_api]
349#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
350#[non_exhaustive]
351#[serde(deny_unknown_fields)]
352#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
353pub struct PassingInfo {
354 pub passing_detected: bool,
356 pub index: usize,
358 pub num_steps: usize,
360 pub start_distance: si::Length,
362 pub distance: si::Length,
364 pub start_speed: si::Velocity,
366 pub speed: si::Velocity,
368 pub time_step_duration: si::Time,
370}
371
372impl PassingInfo {
373 pub fn from(
382 cyc: &Cycle,
383 cyc_ref: &Cycle,
384 i: usize,
385 distance_tolerance: Option<si::Length>,
386 ) -> Self {
387 let i = std::cmp::max(i, 1);
388 if i >= cyc.time.len() {
389 return Self {
390 passing_detected: false,
391 index: 0,
392 num_steps: 0,
393 start_distance: 0.0 * uc::M,
394 distance: 0.0 * uc::M,
395 start_speed: 0.0 * uc::MPS,
396 speed: 0.0 * uc::MPS,
397 time_step_duration: 1.0 * uc::S,
398 };
399 }
400 let zero_speed_tol = 1e-6 * uc::MPS;
401 let distance_tol = distance_tolerance.unwrap_or(0.1 * uc::M);
402 let mut v0 = cyc.speed[i - 1];
403 let d0 = cyc.trapz_step_start_distance(i);
404 let mut v0_lv = cyc_ref.speed[i - 1];
405 let d0_lv = cyc_ref.trapz_step_start_distance(i);
406 let mut d = d0;
407 let mut d_lv = d0_lv;
408 let mut rendezvous_index = None;
409 let mut rendezvous_num_steps = 0;
410 let mut rendezvous_distance = 0.0 * uc::M;
411 let mut rendezvous_speed = 0.0 * uc::MPS;
412 for di in 0..(cyc.speed.len() - i) {
413 let idx = i + di;
414 let v = cyc.speed[idx];
416 let v_lv = cyc_ref.speed[idx];
418 let vavg = (v + v0) * 0.5;
420 let vavg_lv = (v_lv + v0_lv) * 0.5;
422 let dt = cyc.time[idx] - cyc.time[idx - 1];
424 let dd = vavg * dt;
426 let dt_lv = cyc_ref.time[idx] - cyc_ref.time[idx - 1];
428 let dd_lv = vavg_lv * dt_lv;
430 d += dd;
432 d_lv += dd_lv;
434 let dtlv = d_lv - d;
436 v0 = v;
437 v0_lv = v_lv;
438 if di > 0 && dtlv < -distance_tol {
439 rendezvous_index = Some(idx);
440 rendezvous_num_steps = di + 1;
441 rendezvous_distance = d_lv;
442 rendezvous_speed = v_lv;
443 break;
444 }
445 if v <= zero_speed_tol {
446 break;
447 }
448 }
449 Self {
450 passing_detected: rendezvous_index.is_some(),
451 index: rendezvous_index.unwrap_or(0),
452 num_steps: rendezvous_num_steps,
453 start_distance: d0,
454 distance: rendezvous_distance,
455 start_speed: cyc.speed[i - 1],
456 speed: rendezvous_speed,
457 time_step_duration: cyc.time[i] - cyc.time[i - 1],
458 }
459 }
460}
461
462#[serde_api]
463#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
464#[non_exhaustive]
465#[serde(deny_unknown_fields)]
466#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
467pub struct CycleCache {
468 pub grade_all_zero: bool,
470 pub trapz_step_distances_m: Vec<f64>,
472 pub trapz_distances_m: Vec<f64>,
474 pub trapz_elevations_m: Vec<f64>,
476 pub stops: Vec<bool>,
478 interp_ds: Vec<f64>,
480 interp_is: Vec<f64>,
482 interp_hs: Vec<f64>,
484 grades: Vec<f64>,
486 interp_index_by_dist: InterpolatorEnumOwned<f64>,
488 interp_elev_by_dist: InterpolatorEnumOwned<f64>,
490}
491
492impl Default for CycleCache {
493 fn default() -> Self {
494 Self {
495 grade_all_zero: false,
496 trapz_step_distances_m: Default::default(),
497 trapz_distances_m: Default::default(),
498 trapz_elevations_m: Default::default(),
499 stops: Default::default(),
500 interp_ds: Default::default(),
501 interp_is: Default::default(),
502 interp_hs: Default::default(),
503 grades: Default::default(),
504 interp_index_by_dist: InterpolatorEnum::new_0d(0.0),
505 interp_elev_by_dist: InterpolatorEnum::new_0d(0.0),
506 }
507 }
508}
509
510impl Init for CycleCache {}
511
512impl SerdeAPI for CycleCache {}
513
514impl CycleCache {
515 pub fn new(cyc: &Cycle) -> Self {
517 let tol = 1e-6;
518 let num_items = cyc.time.len();
519 let grade_all_zero = cyc.grade.is_empty() || cyc.grade.iter().all(|g| *g == 0.0 * uc::R);
520 let trapz_step_distances_m: Vec<f64> = cyc
521 .trapz_step_distances()
522 .iter()
523 .map(|dd| dd.get::<si::meter>())
524 .collect();
525 debug_assert!(trapz_step_distances_m.len() == num_items);
526 let trapz_distances_m: Vec<f64> = {
527 let mut ds = Vec::with_capacity(num_items);
528 let mut d = 0.0;
529 for dd in &trapz_step_distances_m {
530 d += *dd;
531 ds.push(d);
532 }
533 ds
534 };
535 debug_assert!(trapz_distances_m.len() == num_items);
536 let trapz_elevations_m = if grade_all_zero {
537 let h = cyc.init_elev.unwrap_or(0.0 * uc::M).get::<si::meter>();
538 vec![h; num_items]
539 } else {
540 let dhs: Vec<f64> = cyc
541 .grade
542 .iter()
543 .zip(&trapz_step_distances_m)
544 .map(|(g, dd)| {
545 let gr = g.get::<si::ratio>();
546 gr.atan().cos() * dd * gr
547 })
548 .collect();
549 let mut hs = Vec::with_capacity(num_items);
550 let mut h = cyc.init_elev.unwrap_or(0.0 * uc::M).get::<si::meter>();
551 for dh in &dhs {
552 h += *dh;
553 hs.push(h);
554 }
555 hs
556 };
557 debug_assert!(trapz_elevations_m.len() == num_items);
558 let stops = cyc
559 .speed
560 .iter()
561 .map(|v| v.get::<si::meter_per_second>() <= tol)
562 .collect();
563 let mut interp_ds = Vec::with_capacity(num_items);
564 let mut interp_is = Vec::with_capacity(num_items);
565 let mut interp_hs = Vec::with_capacity(num_items);
566 for idx in 0..num_items {
567 let d = trapz_distances_m[idx];
568 let h = trapz_elevations_m[idx];
569 if interp_ds.is_empty() || d > *interp_ds.last().unwrap() {
570 interp_ds.push(d);
571 interp_is.push(idx as f64);
572 interp_hs.push(h);
573 }
574 }
575 let grades: Vec<f64> = cyc.grade.iter().map(|g| g.get::<si::ratio>()).collect();
576 debug_assert!(grades.len() == num_items);
577 let interp_index_by_dist = InterpolatorEnum::new_1d(
578 interp_ds.clone().into(),
579 interp_is.clone().into(),
580 strategy::RightNearest,
581 Extrapolate::Clamp,
582 )
583 .unwrap();
584 let interp_elev_by_dist = InterpolatorEnum::new_1d(
585 interp_ds.clone().into(),
586 interp_hs.clone().into(),
587 strategy::Linear,
588 Extrapolate::Clamp,
589 )
590 .unwrap();
591 Self {
592 grade_all_zero,
593 trapz_step_distances_m,
594 trapz_distances_m,
595 trapz_elevations_m,
596 stops,
597 interp_ds,
598 interp_is,
599 interp_hs,
600 grades,
601 interp_index_by_dist,
602 interp_elev_by_dist,
603 }
604 }
605
606 pub fn interp_grade(&self, dist_m: f64) -> f64 {
609 if self.grade_all_zero {
610 0.0
611 } else if dist_m <= self.interp_ds[0] {
612 self.grades[0]
613 } else if dist_m > *self.interp_ds.last().expect("interp_ds.len()>0") {
614 *self.grades.last().unwrap()
615 } else {
616 let idx = self.interp_index_by_dist.interpolate(&[dist_m]).unwrap();
618 self.grades[idx as usize]
619 }
620 }
621
622 pub fn interp_elevation(&self, dist_m: f64) -> f64 {
624 if self.grade_all_zero {
625 0.0
626 } else {
627 self.interp_elev_by_dist.interpolate(&[dist_m]).unwrap()
628 }
629 }
630}
631
632pub fn calc_best_rendezvous(
642 i: usize,
643 max_steps: usize,
644 cyc: &Cycle,
645 speed_ach: si::Velocity,
646) -> ConstantJerkTrajectory {
647 let max_steps = (cyc.time.len() - i).min(max_steps);
648 let i = i.clamp(1, cyc.time.len() - 1);
649 let dt = cyc.time[i] - cyc.time[i - 1];
650 let start_distance = 0.5 * (speed_ach + cyc.speed[i - 1]) * dt;
651 let mut best = ConstantJerkTrajectory {
652 steps: 0,
653 distance_m: start_distance.get::<si::meter>(),
654 speed_m_per_s: speed_ach.get::<si::meter_per_second>(),
655 acceleration_m_per_s2: 0.0,
656 jerk_m_per_s3: 0.0,
657 step_duration_s: dt.get::<si::second>(),
658 };
659 if max_steps < 2 {
660 return best;
661 }
662 let mut rendezvous_distance = 0.5 * (cyc.speed[i] + cyc.speed[i - 1]) * dt;
663 let mut max_accel_m_per_s2 = 100.0;
664 for n in 1..max_steps {
665 let j = i + n;
666 let dt = cyc.time[j] - cyc.time[j - 1];
667 rendezvous_distance += 0.5 * (cyc.speed[j] + cyc.speed[j - 1]) * dt;
668 if n >= 2 {
669 let candidate = ConstantJerkTrajectory::from_speed_and_distance_targets(
670 n,
671 start_distance.get::<si::meter>(),
672 speed_ach.get::<si::meter_per_second>(),
673 rendezvous_distance.get::<si::meter>(),
674 cyc.speed[j].get::<si::meter_per_second>(),
675 dt.get::<si::second>(),
676 );
677 let candidate_max_accel_m_per_s2 = candidate.maximum_acceleration();
678 if candidate_max_accel_m_per_s2 < max_accel_m_per_s2 {
679 max_accel_m_per_s2 = candidate_max_accel_m_per_s2;
680 best = candidate;
681 }
682 }
683 }
684 best
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690 use crate::drive_cycle::Cycle;
691
692 fn make_triangle_cycle() -> Cycle {
693 Cycle {
694 name: String::from("Triangle"),
695 init_elev: None,
696 time: vec![0.0 * uc::S, 10.0 * uc::S, 20.0 * uc::S, 30.0 * uc::S],
697 speed: vec![0.0 * uc::MPS, 4.0 * uc::MPS, 0.0 * uc::MPS, 0.0 * uc::MPS],
698 dist: vec![],
699 grade: vec![],
700 elev: vec![],
701 pwr_max_chrg: vec![],
702 grade_interp: Default::default(),
703 elev_interp: Default::default(),
704 temp_amb_air: Default::default(),
705 pwr_solar_load: Default::default(),
706 }
707 }
708 fn make_test_trajectory() -> ConstantJerkTrajectory {
709 let n = 2;
710 let d0_m = 0.0;
711 let v0_m_per_s = 0.0;
712 let dr_m = 2.0;
713 let vr_m_per_s = 2.0;
714 let dt_s = 1.0;
715 ConstantJerkTrajectory::from_speed_and_distance_targets(
716 n, d0_m, v0_m_per_s, dr_m, vr_m_per_s, dt_s,
717 )
718 }
719 #[test]
720 fn test_calc_const_jerk_trajectory() {
721 let actual = make_test_trajectory();
722 let expected = ConstantJerkTrajectory {
723 steps: 2,
724 distance_m: 0.0,
725 speed_m_per_s: 0.0,
726 acceleration_m_per_s2: 1.0,
727 jerk_m_per_s3: 0.0,
728 step_duration_s: 1.0,
729 };
730 assert_eq!(actual, expected);
731 }
732 #[test]
733 fn test_dist_for_constant_jerk() {
734 let trajectory = make_test_trajectory();
735 let expected = 2.0; let actual = trajectory.end_distance();
737 assert_eq!(actual, expected);
738 }
739 #[test]
740 fn test_speed_for_constant_jerk() {
741 let n = 2;
742 let v0_m_per_s = 0.0;
743 let a0_m_per_s2 = 1.0;
744 let k_m_per_s3 = 0.0;
745 let dt_s = 1.0;
746 let expected = 2.0;
747 let actual = speed_for_constant_jerk(n, v0_m_per_s, a0_m_per_s2, k_m_per_s3, dt_s);
748 assert_eq!(actual, expected);
749 }
750 #[test]
751 fn test_accel_for_constant_jerk() {
752 let n = 2;
753 let a0_m_per_s2 = 1.0;
754 let k_m_per_s3 = 0.0;
755 let dt_s = 1.0;
756 let expected = 1.0;
757 let actual = accel_for_constant_jerk(n, a0_m_per_s2, k_m_per_s3, dt_s);
758 assert_eq!(actual, expected);
759 }
760 #[test]
761 fn test_accel_array_for_constant_jerk() {
762 let n = 2;
763 let a0_m_per_s2 = 1.0;
764 let k_m_per_s3 = 0.0;
765 let dt_s = 1.0;
766 let expected = [1.0, 1.0];
767 let actual = accel_array_for_constant_jerk(n, a0_m_per_s2, k_m_per_s3, dt_s);
768 assert_eq!(actual.len(), expected.len());
769 for i in 0..expected.len() {
770 assert_eq!(actual[i], expected[i]);
771 }
772 }
773 #[test]
774 fn test_average_step_speeds() {
775 let cyc = make_triangle_cycle();
776 let expected = [0.0 * uc::MPS, 2.0 * uc::MPS, 2.0 * uc::MPS, 0.0 * uc::MPS];
777 let actual = average_step_speeds(&cyc);
778 assert_eq!(actual.len(), expected.len());
779 for i in 0..expected.len() {
780 assert_eq!(actual[i], expected[i]);
781 }
782 }
783
784 #[test]
785 fn test_average_step_speed_at() {
786 let cyc = make_triangle_cycle();
787 let expected = 2.0 * uc::MPS;
788 let actual = average_step_speed_at(&cyc, 1);
789 assert_eq!(actual, expected);
790 }
791
792 #[test]
793 fn test_trapz_step_distances() {
794 let cyc = make_triangle_cycle();
795 let expected = [0.0 * uc::M, 20.0 * uc::M, 20.0 * uc::M, 0.0 * uc::M];
796 let actual = trapz_step_distances(&cyc);
797 assert_eq!(actual.len(), expected.len());
798 for i in 0..expected.len() {
799 assert_eq!(actual[i], expected[i]);
800 }
801 }
802
803 #[test]
804 fn test_trapz_step_start_distance() {
805 let cyc = make_triangle_cycle();
806 let expected = 40.0 * uc::M;
807 let actual = trapz_step_start_distance(&cyc, 30);
809 assert_eq!(actual, expected);
810 }
811
812 #[test]
813 fn test_trapz_distance_for_step() {
814 let cyc = make_triangle_cycle();
815 let expected = 20.0 * uc::M;
816 let actual = trapz_distance_for_step(&cyc, 1);
817 assert_eq!(actual, expected);
818 }
819
820 #[test]
821 fn test_trapz_distance_over_range() {
822 let cyc = make_triangle_cycle();
823 let expected = 40.0 * uc::M;
824 let actual = trapz_distance_over_range(&cyc, 0, 1000);
826 assert_eq!(actual, expected);
827 }
828
829 #[test]
830 fn test_time_spent_moving() {
831 let cyc = make_triangle_cycle();
832 let expected = 20.0 * uc::S;
833 let actual = time_spent_moving(&cyc, None);
834 assert_eq!(actual, expected);
835 }
836
837 #[test]
838 fn test_create_distance_and_target_speeds_by_microtrip() {
839 let cyc = make_triangle_cycle();
840 let expected = [(0.0 * uc::M, (40.0 / 30.0) * uc::MPS)];
841 let v0 = 0.0 * uc::MPS;
842 let actual = create_distance_and_target_speeds_by_microtrip(&cyc, None, 0.0, v0);
843 assert_eq!(actual.len(), expected.len());
844 for i in 0..expected.len() {
845 assert_eq!(actual[i].0, expected[i].0);
846 assert_eq!(actual[i].1, expected[i].1);
847 }
848 }
849
850 #[test]
851 fn test_extending_cycle_time() {
852 let cyc = make_triangle_cycle();
853 let expected = {
854 let mut c = Cycle {
855 name: cyc.name.clone(),
856 init_elev: None,
857 time: vec![
858 0.0 * uc::S,
859 10.0 * uc::S,
860 20.0 * uc::S,
861 30.0 * uc::S,
862 31.0 * uc::S,
863 32.0 * uc::S,
864 33.0 * uc::S,
865 34.0 * uc::S,
866 35.0 * uc::S,
867 ],
868 speed: vec![
869 0.0 * uc::MPS,
870 4.0 * uc::MPS,
871 0.0 * uc::MPS,
872 0.0 * uc::MPS,
873 0.0 * uc::MPS,
874 0.0 * uc::MPS,
875 0.0 * uc::MPS,
876 0.0 * uc::MPS,
877 0.0 * uc::MPS,
878 ],
879 dist: vec![],
880 grade: vec![],
881 elev: vec![],
882 pwr_max_chrg: vec![],
883 grade_interp: cyc.grade_interp.clone(),
884 elev_interp: cyc.elev_interp.clone(),
885 temp_amb_air: Default::default(),
886 pwr_solar_load: Default::default(),
887 };
888 c.init().unwrap();
889 c
890 };
891 let actual = extend_cycle_time(&cyc, Some(2.0 * uc::S), Some(0.10 * uc::R));
892 assert_eq!(actual, expected);
893 }
894
895 #[test]
896 fn test_passing_info() {
897 let c = {
898 let mut cyc = Cycle {
900 name: String::from("Main Cycle"),
901 time: vec![
902 0.0 * uc::S,
903 10.0 * uc::S,
904 20.0 * uc::S,
905 30.0 * uc::S,
906 40.0 * uc::S,
907 ],
908 speed: vec![
909 0.0 * uc::MPS,
910 10.0 * uc::MPS,
911 10.0 * uc::MPS,
912 10.0 * uc::MPS,
913 0.0 * uc::MPS,
914 ],
915 grade: vec![],
916 dist: vec![],
917 elev: vec![],
918 init_elev: Some(0.0 * uc::M),
919 pwr_max_chrg: vec![],
920 pwr_solar_load: vec![],
921 temp_amb_air: vec![],
922 grade_interp: Default::default(),
923 elev_interp: Default::default(),
924 };
925 cyc.init().unwrap();
926 cyc
927 };
928 let c_lead = {
929 let mut cyc = Cycle {
931 name: String::from("Lead Vehicle"),
932 time: vec![
933 0.0 * uc::S,
934 10.0 * uc::S,
935 20.0 * uc::S,
936 30.0 * uc::S,
937 40.0 * uc::S,
938 ],
939 speed: vec![
940 0.0 * uc::MPS,
941 10.0 * uc::MPS,
942 10.0 * uc::MPS,
943 5.0 * uc::MPS,
944 0.0 * uc::MPS,
945 ],
946 grade: vec![],
947 dist: vec![],
948 elev: vec![],
949 init_elev: Some(0.0 * uc::M),
950 pwr_max_chrg: vec![],
951 pwr_solar_load: vec![],
952 temp_amb_air: vec![],
953 grade_interp: Default::default(),
954 elev_interp: Default::default(),
955 };
956 cyc.init().unwrap();
957 cyc
958 };
959 let expected = PassingInfo {
960 passing_detected: true,
961 index: 3,
962 num_steps: 3,
963 start_distance: 0.0 * uc::M,
964 distance: 225.0 * uc::M,
965 start_speed: 0.0 * uc::MPS,
966 speed: 5.0 * uc::MPS,
967 time_step_duration: 10.0 * uc::S,
968 };
969 let actual = PassingInfo::from(&c, &c_lead, 1, None);
970 assert_eq!(actual, expected);
971 }
972
973 #[test]
974 fn test_making_interp() {
975 let interp = InterpolatorEnum::new_1d(
976 array![0.0, 2.0, 4.0],
977 array![0.0, 4.0, 8.0],
978 strategy::Linear,
979 Extrapolate::Clamp,
980 )
981 .unwrap();
982 let value = interp.interpolate(&[1.0]).unwrap();
983 let expected = 2.0;
984 assert_eq!(value, expected);
985 }
986
987 #[test]
988 fn test_calc_best_rendezvous() {
989 let cyc = {
990 let mut c = Cycle {
991 name: String::from("Trapezoidal Trace"),
992 init_elev: None,
993 time: vec![
994 0.0 * uc::S,
995 1.0 * uc::S,
996 2.0 * uc::S,
997 3.0 * uc::S,
998 4.0 * uc::S,
999 5.0 * uc::S,
1000 6.0 * uc::S,
1001 7.0 * uc::S,
1002 8.0 * uc::S,
1003 ],
1004 speed: vec![
1005 0.0 * uc::MPS,
1006 0.0 * uc::MPS,
1007 8.0 * uc::MPS,
1008 8.0 * uc::MPS,
1009 8.0 * uc::MPS,
1010 8.0 * uc::MPS,
1011 8.0 * uc::MPS,
1012 0.0 * uc::MPS,
1013 0.0 * uc::MPS,
1014 ],
1015 dist: vec![],
1016 grade: vec![],
1017 elev: vec![],
1018 pwr_max_chrg: vec![],
1019 temp_amb_air: vec![],
1020 pwr_solar_load: vec![],
1021 grade_interp: None,
1022 elev_interp: None,
1023 };
1024 c.init().unwrap();
1025 c
1026 };
1027 let i = 2;
1028 let max_steps = 4;
1029 let speed_ach = 4.0 * uc::MPS;
1030 let result = calc_best_rendezvous(i, max_steps, &cyc, speed_ach);
1031 assert!(result.steps >= 2);
1032 let expected_distance_m = 4.0 + 8.0 * (result.steps as f64);
1033 let actual_distance_m = result.end_distance();
1034 assert_eq!(actual_distance_m, expected_distance_m);
1035 }
1036}