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 #[serde(serialize_with = "serialize_nested")]
488 interp_index_by_dist: InterpolatorEnum<f64>,
489 #[serde(serialize_with = "serialize_nested")]
491 interp_elev_by_dist: InterpolatorEnum<f64>,
492}
493
494impl Default for CycleCache {
495 fn default() -> Self {
496 Self {
497 grade_all_zero: false,
498 trapz_step_distances_m: Default::default(),
499 trapz_distances_m: Default::default(),
500 trapz_elevations_m: Default::default(),
501 stops: Default::default(),
502 interp_ds: Default::default(),
503 interp_is: Default::default(),
504 interp_hs: Default::default(),
505 grades: Default::default(),
506 interp_index_by_dist: InterpolatorEnum::new_0d(0.0),
507 interp_elev_by_dist: InterpolatorEnum::new_0d(0.0),
508 }
509 }
510}
511
512impl Init for CycleCache {}
513
514impl SerdeAPI for CycleCache {}
515
516impl CycleCache {
517 pub fn new(cyc: &Cycle) -> Self {
519 let tol = 1e-6;
520 let num_items = cyc.time.len();
521 let grade_all_zero = cyc.grade.is_empty() || cyc.grade.iter().all(|g| *g == 0.0 * uc::R);
522 let trapz_step_distances_m: Vec<f64> = cyc
523 .trapz_step_distances()
524 .iter()
525 .map(|dd| dd.get::<si::meter>())
526 .collect();
527 debug_assert!(trapz_step_distances_m.len() == num_items);
528 let trapz_distances_m: Vec<f64> = {
529 let mut ds = Vec::with_capacity(num_items);
530 let mut d = 0.0;
531 for dd in &trapz_step_distances_m {
532 d += *dd;
533 ds.push(d);
534 }
535 ds
536 };
537 debug_assert!(trapz_distances_m.len() == num_items);
538 let trapz_elevations_m = if grade_all_zero {
539 let h = cyc.init_elev.unwrap_or(0.0 * uc::M).get::<si::meter>();
540 vec![h; num_items]
541 } else {
542 let dhs: Vec<f64> = cyc
543 .grade
544 .iter()
545 .zip(&trapz_step_distances_m)
546 .map(|(g, dd)| {
547 let gr = g.get::<si::ratio>();
548 gr.atan().cos() * dd * gr
549 })
550 .collect();
551 let mut hs = Vec::with_capacity(num_items);
552 let mut h = cyc.init_elev.unwrap_or(0.0 * uc::M).get::<si::meter>();
553 for dh in &dhs {
554 h += *dh;
555 hs.push(h);
556 }
557 hs
558 };
559 debug_assert!(trapz_elevations_m.len() == num_items);
560 let stops = cyc
561 .speed
562 .iter()
563 .map(|v| v.get::<si::meter_per_second>() <= tol)
564 .collect();
565 let mut interp_ds = Vec::with_capacity(num_items);
566 let mut interp_is = Vec::with_capacity(num_items);
567 let mut interp_hs = Vec::with_capacity(num_items);
568 for idx in 0..num_items {
569 let d = trapz_distances_m[idx];
570 let h = trapz_elevations_m[idx];
571 if interp_ds.is_empty() || d > *interp_ds.last().unwrap() {
572 interp_ds.push(d);
573 interp_is.push(idx as f64);
574 interp_hs.push(h);
575 }
576 }
577 let grades: Vec<f64> = cyc.grade.iter().map(|g| g.get::<si::ratio>()).collect();
578 debug_assert!(grades.len() == num_items);
579 let interp_index_by_dist = InterpolatorEnum::new_1d(
580 interp_ds.clone().into(),
581 interp_is.clone().into(),
582 strategy::Step::upper(),
583 Extrapolate::Clamp,
584 )
585 .unwrap();
586 let interp_elev_by_dist = InterpolatorEnum::new_1d(
587 interp_ds.clone().into(),
588 interp_hs.clone().into(),
589 strategy::Linear,
590 Extrapolate::Clamp,
591 )
592 .unwrap();
593 Self {
594 grade_all_zero,
595 trapz_step_distances_m,
596 trapz_distances_m,
597 trapz_elevations_m,
598 stops,
599 interp_ds,
600 interp_is,
601 interp_hs,
602 grades,
603 interp_index_by_dist,
604 interp_elev_by_dist,
605 }
606 }
607
608 pub fn interp_grade(&self, dist_m: f64) -> f64 {
611 if self.grade_all_zero {
612 0.0
613 } else if dist_m <= self.interp_ds[0] {
614 self.grades[0]
615 } else if dist_m > *self.interp_ds.last().expect("interp_ds.len()>0") {
616 *self.grades.last().unwrap()
617 } else {
618 let idx = self.interp_index_by_dist.interpolate(&[dist_m]).unwrap();
620 self.grades[idx as usize]
621 }
622 }
623
624 pub fn interp_elevation(&self, dist_m: f64) -> f64 {
626 if self.grade_all_zero {
627 0.0
628 } else {
629 self.interp_elev_by_dist.interpolate(&[dist_m]).unwrap()
630 }
631 }
632}
633
634pub fn calc_best_rendezvous(
644 i: usize,
645 max_steps: usize,
646 cyc: &Cycle,
647 speed_ach: si::Velocity,
648) -> ConstantJerkTrajectory {
649 let max_steps = (cyc.time.len() - i).min(max_steps);
650 let i = i.clamp(1, cyc.time.len() - 1);
651 let dt = cyc.time[i] - cyc.time[i - 1];
652 let start_distance = 0.5 * (speed_ach + cyc.speed[i - 1]) * dt;
653 let mut best = ConstantJerkTrajectory {
654 steps: 0,
655 distance_m: start_distance.get::<si::meter>(),
656 speed_m_per_s: speed_ach.get::<si::meter_per_second>(),
657 acceleration_m_per_s2: 0.0,
658 jerk_m_per_s3: 0.0,
659 step_duration_s: dt.get::<si::second>(),
660 };
661 if max_steps < 2 {
662 return best;
663 }
664 let mut rendezvous_distance = 0.5 * (cyc.speed[i] + cyc.speed[i - 1]) * dt;
665 let mut max_accel_m_per_s2 = 100.0;
666 for n in 1..max_steps {
667 let j = i + n;
668 let dt = cyc.time[j] - cyc.time[j - 1];
669 rendezvous_distance += 0.5 * (cyc.speed[j] + cyc.speed[j - 1]) * dt;
670 if n >= 2 {
671 let candidate = ConstantJerkTrajectory::from_speed_and_distance_targets(
672 n,
673 start_distance.get::<si::meter>(),
674 speed_ach.get::<si::meter_per_second>(),
675 rendezvous_distance.get::<si::meter>(),
676 cyc.speed[j].get::<si::meter_per_second>(),
677 dt.get::<si::second>(),
678 );
679 let candidate_max_accel_m_per_s2 = candidate.maximum_acceleration();
680 if candidate_max_accel_m_per_s2 < max_accel_m_per_s2 {
681 max_accel_m_per_s2 = candidate_max_accel_m_per_s2;
682 best = candidate;
683 }
684 }
685 }
686 best
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692 use crate::drive_cycle::Cycle;
693
694 fn make_triangle_cycle() -> Cycle {
695 Cycle {
696 name: String::from("Triangle"),
697 init_elev: None,
698 time: vec![0.0 * uc::S, 10.0 * uc::S, 20.0 * uc::S, 30.0 * uc::S],
699 speed: vec![0.0 * uc::MPS, 4.0 * uc::MPS, 0.0 * uc::MPS, 0.0 * uc::MPS],
700 dist: vec![],
701 grade: vec![],
702 elev: vec![],
703 pwr_max_chrg: vec![],
704 grade_interp: Default::default(),
705 elev_interp: Default::default(),
706 temp_amb_air: Default::default(),
707 pwr_solar_load: Default::default(),
708 }
709 }
710 fn make_test_trajectory() -> ConstantJerkTrajectory {
711 let n = 2;
712 let d0_m = 0.0;
713 let v0_m_per_s = 0.0;
714 let dr_m = 2.0;
715 let vr_m_per_s = 2.0;
716 let dt_s = 1.0;
717 ConstantJerkTrajectory::from_speed_and_distance_targets(
718 n, d0_m, v0_m_per_s, dr_m, vr_m_per_s, dt_s,
719 )
720 }
721 #[test]
722 fn test_calc_const_jerk_trajectory() {
723 let actual = make_test_trajectory();
724 let expected = ConstantJerkTrajectory {
725 steps: 2,
726 distance_m: 0.0,
727 speed_m_per_s: 0.0,
728 acceleration_m_per_s2: 1.0,
729 jerk_m_per_s3: 0.0,
730 step_duration_s: 1.0,
731 };
732 assert_eq!(actual, expected);
733 }
734 #[test]
735 fn test_dist_for_constant_jerk() {
736 let trajectory = make_test_trajectory();
737 let expected = 2.0; let actual = trajectory.end_distance();
739 assert_eq!(actual, expected);
740 }
741 #[test]
742 fn test_speed_for_constant_jerk() {
743 let n = 2;
744 let v0_m_per_s = 0.0;
745 let a0_m_per_s2 = 1.0;
746 let k_m_per_s3 = 0.0;
747 let dt_s = 1.0;
748 let expected = 2.0;
749 let actual = speed_for_constant_jerk(n, v0_m_per_s, a0_m_per_s2, k_m_per_s3, dt_s);
750 assert_eq!(actual, expected);
751 }
752 #[test]
753 fn test_accel_for_constant_jerk() {
754 let n = 2;
755 let a0_m_per_s2 = 1.0;
756 let k_m_per_s3 = 0.0;
757 let dt_s = 1.0;
758 let expected = 1.0;
759 let actual = accel_for_constant_jerk(n, a0_m_per_s2, k_m_per_s3, dt_s);
760 assert_eq!(actual, expected);
761 }
762 #[test]
763 fn test_accel_array_for_constant_jerk() {
764 let n = 2;
765 let a0_m_per_s2 = 1.0;
766 let k_m_per_s3 = 0.0;
767 let dt_s = 1.0;
768 let expected = [1.0, 1.0];
769 let actual = accel_array_for_constant_jerk(n, a0_m_per_s2, k_m_per_s3, dt_s);
770 assert_eq!(actual.len(), expected.len());
771 for i in 0..expected.len() {
772 assert_eq!(actual[i], expected[i]);
773 }
774 }
775 #[test]
776 fn test_average_step_speeds() {
777 let cyc = make_triangle_cycle();
778 let expected = [0.0 * uc::MPS, 2.0 * uc::MPS, 2.0 * uc::MPS, 0.0 * uc::MPS];
779 let actual = average_step_speeds(&cyc);
780 assert_eq!(actual.len(), expected.len());
781 for i in 0..expected.len() {
782 assert_eq!(actual[i], expected[i]);
783 }
784 }
785
786 #[test]
787 fn test_average_step_speed_at() {
788 let cyc = make_triangle_cycle();
789 let expected = 2.0 * uc::MPS;
790 let actual = average_step_speed_at(&cyc, 1);
791 assert_eq!(actual, expected);
792 }
793
794 #[test]
795 fn test_trapz_step_distances() {
796 let cyc = make_triangle_cycle();
797 let expected = [0.0 * uc::M, 20.0 * uc::M, 20.0 * uc::M, 0.0 * uc::M];
798 let actual = trapz_step_distances(&cyc);
799 assert_eq!(actual.len(), expected.len());
800 for i in 0..expected.len() {
801 assert_eq!(actual[i], expected[i]);
802 }
803 }
804
805 #[test]
806 fn test_trapz_step_start_distance() {
807 let cyc = make_triangle_cycle();
808 let expected = 40.0 * uc::M;
809 let actual = trapz_step_start_distance(&cyc, 30);
811 assert_eq!(actual, expected);
812 }
813
814 #[test]
815 fn test_trapz_distance_for_step() {
816 let cyc = make_triangle_cycle();
817 let expected = 20.0 * uc::M;
818 let actual = trapz_distance_for_step(&cyc, 1);
819 assert_eq!(actual, expected);
820 }
821
822 #[test]
823 fn test_trapz_distance_over_range() {
824 let cyc = make_triangle_cycle();
825 let expected = 40.0 * uc::M;
826 let actual = trapz_distance_over_range(&cyc, 0, 1000);
828 assert_eq!(actual, expected);
829 }
830
831 #[test]
832 fn test_time_spent_moving() {
833 let cyc = make_triangle_cycle();
834 let expected = 20.0 * uc::S;
835 let actual = time_spent_moving(&cyc, None);
836 assert_eq!(actual, expected);
837 }
838
839 #[test]
840 fn test_create_distance_and_target_speeds_by_microtrip() {
841 let cyc = make_triangle_cycle();
842 let expected = [(0.0 * uc::M, (40.0 / 30.0) * uc::MPS)];
843 let v0 = 0.0 * uc::MPS;
844 let actual = create_distance_and_target_speeds_by_microtrip(&cyc, None, 0.0, v0);
845 assert_eq!(actual.len(), expected.len());
846 for i in 0..expected.len() {
847 assert_eq!(actual[i].0, expected[i].0);
848 assert_eq!(actual[i].1, expected[i].1);
849 }
850 }
851
852 #[test]
853 fn test_extending_cycle_time() {
854 let cyc = make_triangle_cycle();
855 let expected = {
856 let mut c = Cycle {
857 name: cyc.name.clone(),
858 init_elev: None,
859 time: vec![
860 0.0 * uc::S,
861 10.0 * uc::S,
862 20.0 * uc::S,
863 30.0 * uc::S,
864 31.0 * uc::S,
865 32.0 * uc::S,
866 33.0 * uc::S,
867 34.0 * uc::S,
868 35.0 * uc::S,
869 ],
870 speed: vec![
871 0.0 * uc::MPS,
872 4.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 0.0 * uc::MPS,
879 0.0 * uc::MPS,
880 ],
881 dist: vec![],
882 grade: vec![],
883 elev: vec![],
884 pwr_max_chrg: vec![],
885 grade_interp: cyc.grade_interp.clone(),
886 elev_interp: cyc.elev_interp.clone(),
887 temp_amb_air: Default::default(),
888 pwr_solar_load: Default::default(),
889 };
890 c.init().unwrap();
891 c
892 };
893 let actual = extend_cycle_time(&cyc, Some(2.0 * uc::S), Some(0.10 * uc::R));
894 assert_eq!(actual, expected);
895 }
896
897 #[test]
898 fn test_passing_info() {
899 let c = {
900 let mut cyc = Cycle {
902 name: String::from("Main Cycle"),
903 time: vec![
904 0.0 * uc::S,
905 10.0 * uc::S,
906 20.0 * uc::S,
907 30.0 * uc::S,
908 40.0 * uc::S,
909 ],
910 speed: vec![
911 0.0 * uc::MPS,
912 10.0 * uc::MPS,
913 10.0 * uc::MPS,
914 10.0 * uc::MPS,
915 0.0 * uc::MPS,
916 ],
917 grade: vec![],
918 dist: vec![],
919 elev: vec![],
920 init_elev: Some(0.0 * uc::M),
921 pwr_max_chrg: vec![],
922 pwr_solar_load: vec![],
923 temp_amb_air: vec![],
924 grade_interp: Default::default(),
925 elev_interp: Default::default(),
926 };
927 cyc.init().unwrap();
928 cyc
929 };
930 let c_lead = {
931 let mut cyc = Cycle {
933 name: String::from("Lead Vehicle"),
934 time: vec![
935 0.0 * uc::S,
936 10.0 * uc::S,
937 20.0 * uc::S,
938 30.0 * uc::S,
939 40.0 * uc::S,
940 ],
941 speed: vec![
942 0.0 * uc::MPS,
943 10.0 * uc::MPS,
944 10.0 * uc::MPS,
945 5.0 * uc::MPS,
946 0.0 * uc::MPS,
947 ],
948 grade: vec![],
949 dist: vec![],
950 elev: vec![],
951 init_elev: Some(0.0 * uc::M),
952 pwr_max_chrg: vec![],
953 pwr_solar_load: vec![],
954 temp_amb_air: vec![],
955 grade_interp: Default::default(),
956 elev_interp: Default::default(),
957 };
958 cyc.init().unwrap();
959 cyc
960 };
961 let expected = PassingInfo {
962 passing_detected: true,
963 index: 3,
964 num_steps: 3,
965 start_distance: 0.0 * uc::M,
966 distance: 225.0 * uc::M,
967 start_speed: 0.0 * uc::MPS,
968 speed: 5.0 * uc::MPS,
969 time_step_duration: 10.0 * uc::S,
970 };
971 let actual = PassingInfo::from(&c, &c_lead, 1, None);
972 assert_eq!(actual, expected);
973 }
974
975 #[test]
976 fn test_making_interp() {
977 let interp = InterpolatorEnum::new_1d(
978 array![0.0, 2.0, 4.0],
979 array![0.0, 4.0, 8.0],
980 strategy::Linear,
981 Extrapolate::Clamp,
982 )
983 .unwrap();
984 let value = interp.interpolate(&[1.0]).unwrap();
985 let expected = 2.0;
986 assert_eq!(value, expected);
987 }
988
989 #[test]
990 fn test_calc_best_rendezvous() {
991 let cyc = {
992 let mut c = Cycle {
993 name: String::from("Trapezoidal Trace"),
994 init_elev: None,
995 time: vec![
996 0.0 * uc::S,
997 1.0 * uc::S,
998 2.0 * uc::S,
999 3.0 * uc::S,
1000 4.0 * uc::S,
1001 5.0 * uc::S,
1002 6.0 * uc::S,
1003 7.0 * uc::S,
1004 8.0 * uc::S,
1005 ],
1006 speed: vec![
1007 0.0 * uc::MPS,
1008 0.0 * uc::MPS,
1009 8.0 * uc::MPS,
1010 8.0 * uc::MPS,
1011 8.0 * uc::MPS,
1012 8.0 * uc::MPS,
1013 8.0 * uc::MPS,
1014 0.0 * uc::MPS,
1015 0.0 * uc::MPS,
1016 ],
1017 dist: vec![],
1018 grade: vec![],
1019 elev: vec![],
1020 pwr_max_chrg: vec![],
1021 temp_amb_air: vec![],
1022 pwr_solar_load: vec![],
1023 grade_interp: None,
1024 elev_interp: None,
1025 };
1026 c.init().unwrap();
1027 c
1028 };
1029 let i = 2;
1030 let max_steps = 4;
1031 let speed_ach = 4.0 * uc::MPS;
1032 let result = calc_best_rendezvous(i, max_steps, &cyc, speed_ach);
1033 assert!(result.steps >= 2);
1034 let expected_distance_m = 4.0 + 8.0 * (result.steps as f64);
1035 let actual_distance_m = result.end_distance();
1036 assert_eq!(actual_distance_m, expected_distance_m);
1037 }
1038}