fastsim_core/drive_cycle/
maneuvers.rs

1use std::collections::HashSet;
2
3use crate::{imports::*, simdrive::roadload::StepInfo, simdrive::SimParams, vehicle::Vehicle};
4
5use super::manipulation_utils::trapz_distance_for_step;
6use super::{
7    manipulation_utils::{
8        accel_array_for_constant_jerk, accel_for_constant_jerk, calc_constant_jerk_trajectory,
9        trapz_step_distances, trapz_step_start_distance, CoastTrajectory, CycleCache, PassingInfo,
10        RendezvousTrajectory,
11    },
12    Cycle,
13};
14
15#[serde_api]
16#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
17#[non_exhaustive]
18#[serde(deny_unknown_fields)]
19#[cfg_attr(feature = "pyo3", pyclass(module = "fastsim", subclass, eq))]
20pub struct Maneuver {
21    // Cycle Instances
22    /// Cycle to apply maneuver to
23    #[serde(default)]
24    pub cyc: Cycle,
25    /// Reference cycle
26    #[serde(default)]
27    pub cyc0: Cycle,
28
29    // Chassis Data
30    /// Constant mass to assume for maneuvers
31    #[serde(default)]
32    pub mass: si::Mass,
33    /// Constant air density to assume for manuvers
34    #[serde(default)]
35    pub air_density: si::MassDensity,
36    /// Constant aerodynamic drag coefficient to assume
37    #[serde(default)]
38    pub drag_coef: si::Ratio,
39    /// Constant Frontal Area of vehicle to assume
40    #[serde(default)]
41    pub frontal_area: si::Area,
42    /// Constant Wheel Rolling Resistance Coefficient to assume
43    #[serde(default)]
44    pub wheel_rr_coef: si::Ratio,
45    /// Wheel inertia per wheel
46    #[serde(default)]
47    pub wheel_inertia: si::MomentOfInertia,
48    /// Number of wheels
49    #[serde(default)]
50    pub num_wheels: u8,
51    /// Wheel radius
52    #[serde(default)]
53    pub wheel_radius: si::Length,
54
55    // Solver Settings
56    /// max number of iterations allowed in setting achieved speed when trace
57    /// cannot be achieved
58    #[serde(default)]
59    pub ach_speed_max_iter: u32,
60    /// tolerance in change in speed guess in setting achieved speed when trace
61    /// cannot be achieved
62    #[serde(default)]
63    pub ach_speed_tol: si::Ratio,
64    /// Newton method gain for setting achieved speed
65    #[serde(default)]
66    pub ach_speed_solver_gain: f64,
67
68    // Coasting Parameters
69    /// whether to allow coasting or not.
70    #[serde(default)]
71    pub coast_allow: bool,
72    /// for testing: triggers coasting when vehicle passes the given speed
73    #[serde(default)]
74    pub coast_start_speed: si::Velocity,
75    /// speed at which mechanical braking will initiate during coasting maneuvers
76    #[serde(default)]
77    pub coast_brake_start_speed: si::Velocity,
78    /// acceleration assumed during braking for coast maneuvers
79    /// NOTE: should be negative
80    #[serde(default)]
81    pub coast_brake_accel: si::Acceleration,
82    /// if true, accuracy will be favored over performance for grade per step
83    /// estimates Specifically, for performance, grade for a step will be
84    /// assumed to be the grade looked up at step start distance. For accuracy,
85    /// the actual elevations will be used. This distinciton only makes a
86    /// difference for CAV maneuvers.
87    #[serde(default)]
88    pub favor_grade_accuracy: bool,
89    /// if true, coasting vehicle can eclipse the shadow trace (i.e., reference
90    /// vehicle in front)
91    #[serde(default)]
92    pub coast_allow_passing: bool,
93    /// maximum allowable speed under coast
94    #[serde(default)]
95    pub coast_max_speed: si::Velocity,
96    /// "look-ahead" time for speed changes to be considered to feature coasting
97    /// to hit a given stopping distance mark
98    #[serde(default)]
99    pub coast_time_horizon_for_adjustment: si::Time,
100
101    // IDM - Intelligent Driver Model, Adaptive Cruise Control version
102    /// if true, initiates the IDM - Intelligent Driver Model, Adaptive Cruise
103    /// Control version
104    #[serde(default)]
105    pub idm_allow: bool,
106    /// IDM algorithm: a way to specify desired speed by course distance
107    /// traveled. Can simulate changing speed limits over a driving cycle.
108    /// optional list of (distance (m), desired speed (m/s)).
109    #[serde(default)]
110    pub idm_desired_speed_by_distance: Option<Vec<(si::Length, si::Velocity)>>,
111    /// IDM algorithm: desired speed (m/s). Only used if
112    /// idm_v_desired_in_m_per_s_by_distance_m is NOT set (i.e., is None)
113    #[serde(default)]
114    pub idm_desired_speed: si::Velocity,
115    /// IDM algorithm: headway time desired to vehicle in front (s)
116    #[serde(default)]
117    pub idm_headway: si::Time,
118    /// IDM algorithm: minimum desired gap between vehicle and lead vehicle (m)
119    #[serde(default)]
120    pub idm_minimum_gap: si::Length,
121    /// IDM algorithm: delta parameter
122    #[serde(default)]
123    pub idm_delta: f64,
124    /// IDM algorithm: acceleration parameter
125    #[serde(default)]
126    pub idm_acceleration: si::Acceleration,
127    /// IDM algorithm: deceleration parameter
128    #[serde(default)]
129    pub idm_deceleration: si::Acceleration,
130
131    // Internal Fields
132    pub i: usize,
133    pub coast_delay_index: Vec<i32>,
134    #[serde(default)]
135    pub impose_coast: Vec<bool>,
136    #[serde(default)]
137    pub idm_target_speed_m_per_s: Vec<f64>,
138
139    pub cyc0_cache: CycleCache,
140}
141
142#[pyo3_api]
143impl Maneuver {
144    #[pyo3(name = "create_from")]
145    #[staticmethod]
146    /// create a maneuver object based on the given cycle and vehicle.
147    fn create_from_py(cyc: &Cycle, veh: &Vehicle) -> PyResult<Self> {
148        Ok(Maneuver::from(cyc, veh))
149    }
150
151    #[pyo3(name = "apply_maneuvers")]
152    /// apply all maneuvers to the cycle and return it.
153    fn apply_maneuvers_py(&mut self) -> PyResult<Cycle> {
154        self.apply();
155        let cyc = self.cyc.clone();
156        Ok(cyc)
157    }
158
159    #[pyo3(name = "is_coasting")]
160    /// return a vector of signals indicating 1.0 for coast, otherwise 0.0
161    fn is_coasting_py(&self) -> PyResult<Vec<f64>> {
162        let mut result = Vec::with_capacity(self.impose_coast.len());
163        for ic in &self.impose_coast {
164            if *ic {
165                result.push(1.0);
166            } else {
167                result.push(0.0);
168            }
169        }
170        Ok(result)
171    }
172}
173
174impl SerdeAPI for Maneuver {
175    #[cfg(feature = "resources")]
176    const RESOURCES_SUBDIR: &'static str = "maneuvers";
177}
178
179impl Init for Maneuver {
180    fn init(&mut self) -> Result<(), Error> {
181        self.i = 1;
182        let n = self.cyc.speed.len();
183        self.coast_delay_index = vec![0; n];
184        self.impose_coast = vec![false; n];
185        self.idm_target_speed_m_per_s = vec![0.0; n];
186        self.cyc0_cache = self.cyc0.build_cache();
187        Ok(())
188    }
189}
190
191impl Default for Maneuver {
192    fn default() -> Self {
193        Self {
194            cyc: Cycle::default(),
195            cyc0: Cycle::default(),
196            mass: si::Mass::default(),
197            air_density: si::MassDensity::default(),
198            drag_coef: si::Ratio::default(),
199            frontal_area: si::Area::default(),
200            wheel_rr_coef: si::Ratio::default(),
201            wheel_inertia: si::MomentOfInertia::default(),
202            num_wheels: u8::default(),
203            wheel_radius: si::Length::default(),
204            ach_speed_max_iter: 3,
205            ach_speed_tol: 1.0e-3 * uc::R,
206            ach_speed_solver_gain: 0.9,
207            coast_allow: false,
208            coast_start_speed: 0.0 * uc::MPS,
209            coast_brake_start_speed: 20.0 * uc::MPH,
210            coast_brake_accel: -2.5 * uc::MPS2,
211            favor_grade_accuracy: true,
212            coast_allow_passing: false,
213            coast_max_speed: 40.0 * uc::MPS,
214            coast_time_horizon_for_adjustment: 20.0 * uc::S,
215            idm_allow: false,
216            idm_desired_speed_by_distance: None,
217            idm_desired_speed: 75.0 * uc::MPH,
218            idm_headway: 1.0 * uc::S,
219            idm_minimum_gap: 2.0 * uc::M,
220            idm_delta: 4.0,
221            idm_acceleration: 1.0 * uc::MPS2,
222            idm_deceleration: 1.5 * uc::MPS2,
223            i: 1,
224            coast_delay_index: vec![0, 0],
225            impose_coast: vec![false, false],
226            idm_target_speed_m_per_s: vec![0.0, 0.0],
227            cyc0_cache: CycleCache::default(),
228        }
229    }
230}
231
232impl Maneuver {
233    /// Create maneuver object from cycle and vehicle.
234    pub fn from(cyc: &Cycle, veh: &Vehicle) -> Self {
235        let mut c = cyc.clone();
236        c.init().unwrap();
237        let mut v = veh.clone();
238        v.init().unwrap();
239        let cyc0 = c.clone();
240        let cyc0_cache = cyc0.build_cache();
241        let default_mass = 1200.0 * uc::KG;
242        let mass = if let Ok(Some(m)) = veh.mass() {
243            m
244        } else {
245            default_mass
246        };
247        // TODO[mok]: what is the proper way to get air_density from the
248        //            vehicle? The below returns 0.0...
249        // *veh.state.air_density.get_fresh(|| format_dbg!()).unwrap();
250        let air_density = 1.2 * uc::KGPM3;
251
252        // NOTE: wheel radius should exist as Some(value) after v.init() above.
253        let wheel_radius = veh.chassis.wheel_radius.unwrap();
254        let params = SimParams::default();
255        Self {
256            cyc: c,
257            cyc0,
258            cyc0_cache,
259            mass,
260            air_density,
261            drag_coef: veh.chassis.drag_coef,
262            frontal_area: veh.chassis.frontal_area,
263            wheel_rr_coef: veh.chassis.wheel_rr_coef,
264            wheel_inertia: veh.chassis.wheel_inertia,
265            num_wheels: veh.chassis.num_wheels,
266            wheel_radius,
267            ach_speed_max_iter: params.ach_speed_max_iter,
268            ach_speed_tol: params.ach_speed_tol,
269            ach_speed_solver_gain: params.ach_speed_solver_gain,
270            ..Default::default()
271        }
272    }
273
274    /// Apply the eco-coast maneuver to the given cycle with
275    /// the given reference cycle.
276    /// - cyc: cycle to modify
277    /// - cyc0: reference cycle
278    pub fn apply(&mut self) {
279        self.cyc.init().unwrap();
280        self.cyc0.init().unwrap();
281        self.i = 1;
282        let cyc_len = self.cyc.time.len();
283        self.coast_delay_index = vec![0; cyc_len];
284        self.impose_coast = vec![false; cyc_len];
285        self.idm_target_speed_m_per_s = vec![0.0; cyc_len];
286        self.cyc0_cache = self.cyc0.build_cache();
287        self.walk(cyc_len);
288    }
289
290    /// Walk from step to step for the maneuver simulation.
291    fn walk(&mut self, cyc_len: usize) {
292        while self.i < cyc_len {
293            self.step();
294        }
295        // NOTE: force dist and elev to recalculate
296        // TODO: need to investigate re-deriving grade from interpolation of
297        // elevation by distance.
298        self.cyc.dist = vec![];
299        self.cyc.elev = vec![];
300        self.cyc.init().unwrap();
301    }
302
303    /// Step: compute a single time-step for the maneuver simulation.
304    fn step(&mut self) {
305        if self.idm_allow {
306            self.idm_target_speed_m_per_s[self.i] = match &self.idm_desired_speed_by_distance {
307                Some(vtgt_by_dist) => {
308                    let mut found_v_target = vtgt_by_dist[0].1;
309                    let mut current_d = si::Length::ZERO;
310                    for (idx, d) in self.cyc.dist.iter().enumerate() {
311                        if idx > self.i {
312                            break;
313                        }
314                        current_d += *d;
315                    }
316                    for (d, v_target) in vtgt_by_dist {
317                        if current_d >= *d {
318                            found_v_target = *v_target;
319                        } else {
320                            break;
321                        }
322                    }
323                    found_v_target.get::<si::meter_per_second>()
324                }
325                None => self.idm_desired_speed.get::<si::meter_per_second>(),
326            };
327            self.set_speed_for_target_gap_using_idm(self.i);
328        }
329        if self.coast_allow {
330            self.set_coast_speed(self.i);
331            self.cyc.grade[self.i] = self.lookup_grade_for_step(self.i, None);
332        }
333        self.i += 1;
334    }
335
336    /// Set gap
337    /// - i: non-negative integer, the step index
338    ///
339    /// RETURN: None
340    ///
341    /// EFFECTS:
342    /// - sets the next speed (m/s)
343    ///
344    /// EQUATION:
345    /// parameters:
346    ///     - v_desired: the desired speed (m/s)
347    ///     - delta: number, typical value is 4.0
348    ///     - a: max acceleration, (m/s2)
349    ///     - b: max deceleration, (m/s2)
350    /// s = d_lead - d
351    /// dv/dt = a * (1 - (v/v_desired)**delta - (s_desired(v,v-v_lead)/s)**2)
352    /// s_desired(v, dv) = s0 + max(0, v*dt_headway + (v * dv)/(2.0 * sqrt(a*b)))
353    /// REFERENCE:
354    /// Treiber, Martin and Kesting, Arne. 2013. "Chapter 11: Car-Following Models Based on Driving Strategies".
355    ///     Traffic Flow Dynamics: Data, Models and Simulation. Springer-Verlag. Springer, Berlin, Heidelberg.
356    ///     DOI: <https://doi.org/10.1007/978-3-642-32460-4>
357    pub fn set_speed_for_target_gap_using_idm(&mut self, i: usize) {
358        // PARAMETERS
359        let v_desired_m_per_s = if self.idm_target_speed_m_per_s[i] > 0.0 {
360            self.idm_target_speed_m_per_s[i]
361        } else {
362            let mut v = self.cyc0.speed[0];
363            for vi in &self.cyc0.speed {
364                if *vi > v {
365                    v = *vi;
366                }
367            }
368            v.get::<si::meter_per_second>()
369        };
370        // DERIVED VALUES
371        self.cyc.speed[i] = self.next_speed_by_idm(
372            i,
373            self.idm_acceleration.get::<si::meter_per_second_squared>(),
374            self.idm_deceleration.get::<si::meter_per_second_squared>(),
375            self.idm_headway.get::<si::second>(),
376            self.idm_minimum_gap.get::<si::meter>(),
377            v_desired_m_per_s,
378            self.idm_delta,
379        ) * uc::MPS;
380    }
381
382    /// Calculate the next speed by the Intelligent Driver Model
383    /// - i: int, the index
384    /// - a_m_per_s2: number, max acceleration (m/s2)
385    /// - b_m_per_s2: number, max deceleration (m/s2)
386    /// - dt_headway_s: number, the headway between us and the lead vehicle in seconds
387    /// - s0_m: number, the initial gap between us and the lead vehicle in meters
388    /// - v_desired_m_per_s: number, the desired speed in (m/s)
389    /// - delta: number, a shape parameter; typical value is 4.0
390    ///
391    /// RETURN: number, the next speed (m/s)
392    ///
393    /// REFERENCE:
394    /// Treiber, Martin and Kesting, Arne. 2013. "Chapter 11: Car-Following Models Based on Driving Strategies".
395    ///     Traffic Flow Dynamics: Data, Models and Simulation. Springer-Verlag. Springer, Berlin, Heidelberg.
396    ///     DOI: <https://doi.org/10.1007/978-3-642-32460-4>.
397    #[allow(clippy::too_many_arguments)]
398    pub fn next_speed_by_idm(
399        &mut self,
400        i: usize,
401        a_m_per_s2: f64,
402        b_m_per_s2: f64,
403        dt_headway_s: f64,
404        s0_m: f64,
405        v_desired_m_per_s: f64,
406        delta: f64,
407    ) -> f64 {
408        if v_desired_m_per_s <= 0.0 {
409            return 0.0;
410        }
411        let a_m_per_s2 = a_m_per_s2.abs();
412        let b_m_per_s2 = b_m_per_s2.abs();
413        let dt_headway_s = dt_headway_s.max(0.0);
414        // we assume the vehicles start out a "minimum gap" apart
415        let s0_m = s0_m.max(0.0);
416        // DERIVED VALUES
417        let sqrt_ab = (a_m_per_s2 * b_m_per_s2).powf(0.5);
418        let v0_m_per_s = self.cyc.speed[i - 1].get::<si::meter_per_second>();
419        let v0_lead_m_per_s = self.cyc0.speed[i - 1].get::<si::meter_per_second>();
420        let dv0_m_per_s = v0_m_per_s - v0_lead_m_per_s;
421        let d0_lead_m = self.cyc0_cache.trapz_distances_m[(i - 1).max(0)] + s0_m;
422        let d0_m = trapz_step_start_distance(&self.cyc, i).get::<si::meter>();
423        let s_m = (d0_lead_m - d0_m).max(0.01);
424        let dt = (self.cyc0.time[i] - self.cyc0.time[i - 1]).get::<si::second>();
425        // IDM EQUATIONS
426        let s_target_m = s0_m
427            + ((v0_m_per_s * dt_headway_s) + ((v0_m_per_s * dv0_m_per_s) / (2.0 * sqrt_ab)))
428                .max(0.0);
429        let accel_target_m_per_s2 = a_m_per_s2
430            * (1.0 - ((v0_m_per_s / v_desired_m_per_s).powf(delta)) - ((s_target_m / s_m).powi(2)));
431        (v0_m_per_s + (accel_target_m_per_s2 * dt)).max(0.0)
432    }
433
434    /// For situations where cyc can deviate from cyc0, this method
435    /// looks up and accurately interpolates what the average grade over
436    /// the step should be. The achieved value is used to predict the
437    /// distance traveled over the step.
438    ///
439    /// NOTE:
440    /// If not allowing coasting (i.e., sim_params.coast_allow == False)
441    /// and not allowing IDM/following (i.e., self.sim_params.idm_allow
442    /// == False) then returns self.cyc.grade\[i\]
443    pub fn lookup_grade_for_step(&self, i: usize, speed_ach: Option<si::Velocity>) -> si::Ratio {
444        if self.cyc0_cache.grade_all_zero {
445            return 0.0 * uc::R;
446        }
447        if !self.coast_allow && !self.idm_allow {
448            return self.cyc.grade[i];
449        }
450        match speed_ach {
451            Some(v1) => {
452                let dt = self.cyc.time[i] - self.cyc.time[i - 1];
453                self.cyc0.average_grade_over_range(
454                    trapz_step_start_distance(&self.cyc, i),
455                    0.5 * (v1 + self.cyc.speed[i - 1]) * dt,
456                    Some(&self.cyc0_cache),
457                )
458            }
459            None => self.cyc0.average_grade_over_range(
460                trapz_step_start_distance(&self.cyc, i),
461                trapz_distance_for_step(&self.cyc, i),
462                Some(&self.cyc0_cache),
463            ),
464        }
465    }
466
467    /// Determine whether the vehicle should go into a 'coasting' state.
468    /// Normal coasting logic is that the vehicle will coast if it is
469    /// within coasting distance of a stop:
470    /// - if distance to coast from start of step <= distance to next stop
471    /// - AND distance to coast from end of step (using reference speed) is
472    ///   > distance to next step
473    /// - AND vehicle was at or above the speed to start braking
474    /// - AND at least four time-steps away from where braking would start
475    ///
476    /// NOTE: for the case when coast-start speed is used, we only worry
477    /// about if the vehicle is above the coast-start speed. This is mainly
478    /// for testing.
479    pub fn should_impose_coast(&mut self, i: usize) -> bool {
480        let v0 = self.cyc.speed[i - 1];
481        if self.coast_start_speed > si::Velocity::ZERO {
482            return v0 >= self.coast_start_speed;
483        }
484        if v0 < self.coast_brake_start_speed {
485            return false;
486        }
487        // distance to stop by coasting from start of step (i - 1)
488        let dtsc0 = self.calc_distance_to_stop_coast_v2(i);
489        if dtsc0.is_none() {
490            return false;
491        }
492        let dtsc0 = dtsc0.unwrap();
493        // distance to next stop (m)
494        let d0 = trapz_step_start_distance(&self.cyc, i);
495        let dts0 = self
496            .cyc0
497            .calc_distance_to_next_stop_from(d0, Some(&self.cyc0_cache));
498        let dtb = -0.5 * v0 * v0 / self.coast_brake_accel;
499        dtsc0 >= dts0 && dts0 >= (4.0 * dtb)
500    }
501
502    /// Calculate the distance to stop via coasting.
503    /// - i: the current index
504    ///
505    /// RETURN: if Some, the distance, else None
506    ///
507    /// NOTES:
508    /// - if None, that means there is no solution to a coast-down distance.
509    ///   This can happen due to being too close to the given stop or
510    ///   perhaps due to coasting downhill (i.e., will not stop).
511    /// - if Some, the distance in meters that the vehicle would freely coast
512    ///   is unobstructed. We do account fro grade between the current point
513    ///   and the end-point.
514    pub fn calc_distance_to_stop_coast_v2(&mut self, i: usize) -> Option<si::Length> {
515        let not_found = -1.0;
516        let v0 = self.cyc.speed[i - 1].get::<si::meter_per_second>();
517        let v_brake = self.coast_brake_start_speed.get::<si::meter_per_second>();
518        let a_brake = self.coast_brake_accel.get::<si::meter_per_second_squared>();
519        let ds = &self.cyc0_cache.trapz_distances_m;
520        let gs: Vec<f64> = self
521            .cyc0
522            .grade
523            .iter()
524            .map(|g| g.get::<si::ratio>())
525            .collect();
526        assert!(
527            ds.len() == gs.len(),
528            "Assumed lengths of distances and grades must equal. ds.len()={}, gs.len()={}",
529            ds.len(),
530            gs.len(),
531        );
532        let d0 = trapz_step_start_distance(&self.cyc, i).get::<si::meter>();
533        let mut grade_by_distance = Vec::with_capacity(ds.len());
534        for idx in 0..ds.len() {
535            if ds[idx] >= d0 {
536                grade_by_distance.push(gs[idx]);
537            }
538        }
539        let veh_mass_kg = self.mass.get::<si::kilogram>();
540        let air_density_kg_per_m3 = self.air_density.get::<si::kilogram_per_cubic_meter>();
541        let cdfa_m2 =
542            self.drag_coef.get::<si::ratio>() * self.frontal_area.get::<si::square_meter>();
543        let rrc = self.wheel_rr_coef.get::<si::ratio>();
544        let gravity_m_per_s2 = uc::ACC_GRAV.get::<si::meter_per_second_squared>();
545        // distance traveled while stopping via friction-braking (i.e., distance to brake)
546        let dtb = -0.5 * v_brake * v_brake / a_brake;
547        if v0 <= v_brake {
548            let result = -0.5 * v0 * v0 / a_brake;
549            return Some(result * uc::M);
550        }
551        let grade_mult = 10000;
552        let unique_grades = {
553            let mut result = HashSet::new();
554            for gr in &grade_by_distance {
555                let gr_to_store = (*gr * (grade_mult as f64)).round() as i32;
556                result.insert(gr_to_store);
557            }
558            result
559        };
560        if unique_grades.len() == 1 {
561            // if there is only one grade, there may be a closed-form solution
562            let unique_grade = (*unique_grades.iter().nth(0).unwrap() as f64) / (grade_mult as f64);
563            let theta = unique_grade.atan();
564            let c1 = gravity_m_per_s2 * (theta.sin() + rrc * theta.cos());
565            let c2 = (air_density_kg_per_m3 * cdfa_m2) / (2.0 * veh_mass_kg);
566            let v02 = v0 * v0;
567            let vb2 = v_brake * v_brake;
568            let mut d = not_found;
569            let a1 = c1 + c2 * v02;
570            let b1 = c1 + c2 * vb2;
571            if c2 == 0.0 {
572                if c1 > 0.0 {
573                    d = (1.0 / (2.0 * c1)) * (v02 - vb2);
574                }
575            } else if a1 > 0.0 && b1 > 0.0 {
576                d = (1.0 / (2.0 * c2)) * (a1.ln() - b1.ln());
577            }
578            if d != not_found {
579                let result = d + dtb;
580                return Some(result * uc::M);
581            }
582        }
583        let ct = self.generate_coast_trajectory(i);
584        if ct.found_trajectory {
585            Some(ct.distance_to_stop_via_coast_m * uc::M)
586        } else {
587            None
588        }
589    }
590
591    /// Set the coasting speed.
592    pub fn set_coast_speed(&mut self, i: usize) {
593        let tol = 1e-6;
594        let v0 = self.cyc.speed[i - 1].get::<si::meter_per_second>();
595        if v0 > tol && !self.impose_coast[i] && self.should_impose_coast(i) {
596            let ct = self.generate_coast_trajectory(i);
597            if ct.found_trajectory {
598                let d = ct.distance_to_stop_via_coast_m;
599                if d < 0.0 {
600                    for idx in i..self.cyc0.speed.len() {
601                        self.impose_coast[idx] = false;
602                    }
603                } else {
604                    self.apply_coast_trajectory(&ct);
605                }
606                if !self.coast_allow_passing {
607                    self.prevent_collisions(i, None);
608                }
609            }
610        }
611        if !self.impose_coast[i] {
612            if !self.idm_allow {
613                // NOTE: transforming to i32 so we can carry negative indices.
614                let i_i32 = i32::try_from(i).ok();
615                let target_idx = i_i32.map(|v| v - self.coast_delay_index[i]);
616                let target_idx = match target_idx {
617                    Some(ti) => {
618                        if ti < 0 {
619                            Some(0)
620                        } else {
621                            usize::try_from(ti).ok()
622                        }
623                    }
624                    None => None,
625                };
626                if let Some(ti) = target_idx {
627                    self.cyc.speed[i] = self.cyc0.speed[ti.min(self.cyc0.speed.len() - 1)];
628                }
629            }
630            return;
631        }
632        let v1_traj = self.cyc.speed[i].get::<si::meter_per_second>();
633        let v_brake = self.coast_brake_start_speed.get::<si::meter_per_second>();
634        if v0 > v_brake {
635            if self.coast_allow_passing {
636                // NOTE: We could be coasting downhill so could in theory go
637                // to a higher speed. Since we can pass, allow vehicle to go
638                // up to max coasting speed (m/s). The solver will show us what
639                // we can actually achieve.
640                self.cyc.speed[i] = self.coast_max_speed;
641            } else {
642                self.cyc.speed[i] =
643                    v1_traj.min(self.coast_max_speed.get::<si::meter_per_second>()) * uc::MPS;
644            }
645        }
646        // Solve for the actual coasting speed
647        let coast_speed = self.solve_step(i);
648        if self.impose_coast[i - 1] && v1_traj <= v_brake {
649            // NOTE: if we've been coasting for at least one step already
650            // and the current trajectory takes us below v_brake, we should
651            // use that as it is the brake trajectory.
652            self.cyc.speed[i] = v1_traj * uc::MPS;
653        } else {
654            self.cyc.speed[i] = coast_speed;
655        }
656        let v_tol = tol * uc::MPS;
657        let dt = self.cyc.time[i] - self.cyc.time[i - 1];
658        let accel_proposed = (self.cyc.speed[i] - self.cyc.speed[i - 1]) / dt;
659        if self.cyc.speed[i] < v_tol {
660            for idx in i..self.cyc0.speed.len() {
661                self.impose_coast[idx] = false;
662            }
663            self.set_coast_delay(i);
664            self.cyc.speed[i] = si::Velocity::ZERO;
665            return;
666        }
667        if (self.cyc.speed[i] - v1_traj * uc::MPS).abs() > v_tol {
668            let mut adjusted_current_speed = false;
669            let brake_speed_start_tol = 0.1 * uc::MPS;
670            if self.cyc.speed[i] < (self.coast_brake_start_speed - brake_speed_start_tol) {
671                let (_, num_steps) =
672                    self.cyc
673                        .modify_with_braking_trajectory(self.coast_brake_accel, i, None);
674                for idx in i..self.cyc0.speed.len() {
675                    self.impose_coast[idx] = idx < (i + num_steps);
676                }
677                adjusted_current_speed = true;
678            } else {
679                // NOTE: will the below work when coasting downhill and picking up speed?
680                let (traj_found, traj_n, traj_jerk_m_per_s3, traj_accel_m_per_s2) = self
681                    .calc_next_rendezvous_trajectory(
682                        i,
683                        self.coast_brake_accel,
684                        accel_proposed.min(0.0 * uc::MPS2),
685                    );
686                if traj_found {
687                    // adjust cyc to perform the trajectory
688                    let final_speed = self.cyc.modify_by_const_jerk_trajectory(
689                        i,
690                        traj_n,
691                        traj_jerk_m_per_s3 * uc::MPS3,
692                        traj_accel_m_per_s2 * uc::MPS2,
693                    );
694                    for idx in i..self.cyc0.speed.len() {
695                        self.impose_coast[idx] = idx < (i + traj_n);
696                    }
697                    adjusted_current_speed = true;
698                    let i_for_brake = i + traj_n;
699                    if (final_speed - self.coast_brake_start_speed).abs() < brake_speed_start_tol {
700                        let (_, num_steps) = self.cyc.modify_with_braking_trajectory(
701                            self.coast_brake_accel,
702                            i_for_brake,
703                            None,
704                        );
705                        for idx in i_for_brake..self.cyc0.speed.len() {
706                            self.impose_coast[idx] = idx < i_for_brake + num_steps;
707                        }
708                        adjusted_current_speed = true;
709                    } else {
710                        println!("## WARNING ##");
711                        println!("final_speed={:?} not close to coast_brake_start_speed={:?} for i={:?}; i_for_brake={:?}, traj_n={:?}",
712                            final_speed, self.coast_brake_start_speed, i, i_for_brake, traj_n);
713                    }
714                }
715            }
716            if adjusted_current_speed {
717                if !self.coast_allow_passing {
718                    self.prevent_collisions(i, None);
719                }
720                self.cyc.speed[i] = self.solve_step(i);
721            }
722        }
723    }
724
725    /// Solve for coast speed and set.
726    pub fn solve_step(&mut self, i: usize) -> si::Velocity {
727        let dt = self.cyc.time[i] - self.cyc.time[i - 1];
728        let step_info = StepInfo {
729            dt,
730            speed_prev: self.cyc.speed[i - 1],
731            cyc_speed: self.cyc.speed[i],
732            grade_curr: self.cyc.grade[i],
733            air_density: self.air_density,
734            mass: self.mass,
735            drag_coef: self.drag_coef,
736            frontal_area: self.frontal_area,
737            wheel_inertia: self.wheel_inertia,
738            num_wheels: self.num_wheels,
739            wheel_radius: self.wheel_radius,
740            wheel_rr_coef: self.wheel_rr_coef,
741            pwr_prop_fwd_max: 0.0 * uc::KW,
742        };
743        let coast_speed = step_info.solve_for_speed(
744            self.ach_speed_max_iter,
745            self.ach_speed_tol,
746            self.ach_speed_solver_gain,
747        );
748        let max_coast_speed = self.coast_max_speed.min(coast_speed);
749        let brake_start_speed = self.coast_brake_start_speed + 0.1 * uc::MPS;
750        if coast_speed > brake_start_speed {
751            max_coast_speed
752        } else {
753            // NOTE: We follow trace below the brake start speed as the
754            // brake trajectory has been added to the cycle
755            self.cyc.speed[i]
756                .min(max_coast_speed)
757                .max(si::Velocity::ZERO)
758        }
759    }
760
761    /// Calculate next rendezvous trajectory for eco-coasting
762    /// - i: the index into cyc for the end of start-of-step (i.e., the step
763    ///   that may be modified; should be i)
764    /// - min_accel: the minimum acceleration permitted
765    /// - max_accel: the maximum acceleration permitted
766    ///
767    /// RETURN: (Tuple
768    ///     found_rendezvous: Bool, if True the remainder of the data is valid; if False, no rendezvous found
769    ///     n: positive integer, the number of steps ahead to rendezvous at
770    ///     jerk_m__s3: number, the Jerk or first-derivative of acceleration (m/s3)
771    ///     accel_m__s2: number, the initial acceleration of the trajectory (m/s2)
772    /// )
773    /// If no rendezvous exists within the scope, the returned tuple has False for the first item.
774    /// Otherwise, returns the next closest rendezvous in time/space
775    pub fn calc_next_rendezvous_trajectory(
776        &self,
777        i: usize,
778        min_accel: si::Acceleration,
779        max_accel: si::Acceleration,
780    ) -> (bool, usize, f64, f64) {
781        let tol = 1e-6;
782        let min_accel_m_per_s2 = min_accel.get::<si::meter_per_second_squared>();
783        let max_accel_m_per_s2 = max_accel.get::<si::meter_per_second_squared>();
784        // v0 is where n=0; i.e., i - 1
785        let v0 = self.cyc.speed[i - 1].get::<si::meter_per_second>();
786        let brake_start_speed_m_per_s = self.coast_brake_start_speed.get::<si::meter_per_second>();
787        let brake_accel_m_per_s2 = self.coast_brake_accel.get::<si::meter_per_second_squared>();
788        let time_horizon_s = self.coast_time_horizon_for_adjustment.get::<si::second>();
789        // distance_horizon_m = 1_000.0;
790        let not_found_n = 0;
791        let not_found_jerk_m_per_s3 = 0.0;
792        let not_found_accel_m_per_s2 = 0.0;
793        let not_found = (
794            false,
795            not_found_n,
796            not_found_jerk_m_per_s3,
797            not_found_accel_m_per_s2,
798        );
799        if v0 < (brake_start_speed_m_per_s + tol) {
800            // don't process braking
801            return not_found;
802        }
803        let (min_accel_m_per_s2, max_accel_m_per_s2) = if min_accel_m_per_s2 > max_accel_m_per_s2 {
804            (max_accel_m_per_s2, min_accel_m_per_s2)
805        } else {
806            (min_accel_m_per_s2, max_accel_m_per_s2)
807        };
808        let num_samples = self.cyc.speed.len();
809        let d0 = trapz_step_start_distance(&self.cyc, i).get::<si::meter>();
810        // a_proposed = (v1 - v0) / dt
811        // distance to stop from start of time-step
812        let dts0 = self
813            .cyc0
814            .calc_distance_to_next_stop_from(d0 * uc::M, Some(&self.cyc0_cache))
815            .get::<si::meter>();
816        if dts0 < 0.0 {
817            return not_found;
818        }
819        let dt = (self.cyc0.time[i] - self.cyc0.time[i - 1]).get::<si::second>();
820        // distance to brake from the brake start speed (m/s)
821        let dtb =
822            -0.5 * brake_start_speed_m_per_s * brake_start_speed_m_per_s / brake_accel_m_per_s2;
823        // distance to brake initialization from start of time-step (m)
824        let dtbi0 = dts0 - dtb;
825        if dtbi0 < 0.0 {
826            return not_found;
827        }
828        // Now, check rendezvous trajectories
829        let mut step_idx = i;
830        let mut dt_plan = 0.0;
831        let mut r_best_found = false;
832        let mut r_best_n = 0;
833        let mut r_best_jerk_m_per_s3 = 0.0;
834        let mut r_best_accel_m_per_s2 = 0.0;
835        let mut r_best_accel_spread_m_per_s2 = 0.0;
836        while dt_plan <= time_horizon_s && step_idx < num_samples {
837            dt_plan += dt;
838            let step_ahead = step_idx - (i - 1);
839            if step_ahead == 1 {
840                // for brake init rendezvous
841                let accel = (brake_start_speed_m_per_s - v0) / dt;
842                let v1 = (v0 + accel * dt).max(0.0);
843                let dd_proposed = ((v0 + v1) / 2.0) * dt;
844                if (v1 - brake_start_speed_m_per_s).abs() < tol && (dtbi0 - dd_proposed).abs() < tol
845                {
846                    r_best_found = true;
847                    r_best_n = 1;
848                    r_best_jerk_m_per_s3 = 0.0;
849                    r_best_accel_m_per_s2 = accel;
850                    break;
851                }
852            } else {
853                // rendezvous trajectory for brake-start -- assumes fixed time-steps
854                if dtbi0 > 0.0 {
855                    let r_bi_traj = calc_constant_jerk_trajectory(
856                        step_ahead,
857                        0.0,
858                        v0,
859                        dtbi0,
860                        brake_start_speed_m_per_s,
861                        dt,
862                    );
863                    let r_bi_jerk_m_per_s3 = r_bi_traj.jerk_m_per_s3;
864                    let r_bi_accel_m_per_s2 = r_bi_traj.acceleration_m_per_s2;
865                    if r_bi_accel_m_per_s2 < max_accel_m_per_s2
866                        && min_accel_m_per_s2 < r_bi_accel_m_per_s2
867                        && r_bi_jerk_m_per_s3 >= 0.0
868                    {
869                        let as_bi = accel_array_for_constant_jerk(
870                            step_ahead,
871                            r_bi_accel_m_per_s2,
872                            r_bi_jerk_m_per_s3,
873                            dt,
874                        );
875                        let as_bi_min = as_bi.iter().cloned().reduce(f64::min).unwrap_or(0.0);
876                        let as_bi_max = as_bi.iter().cloned().reduce(f64::max).unwrap_or(0.0);
877                        let accel_spread = (as_bi_max - as_bi_min).abs();
878                        let flag = as_bi_max < (max_accel_m_per_s2 + 1e-6)
879                            && as_bi_min > (min_accel_m_per_s2 - 1e-6)
880                            && (!r_best_found || (accel_spread < r_best_accel_spread_m_per_s2));
881                        if flag {
882                            r_best_found = true;
883                            r_best_n = step_ahead;
884                            r_best_accel_m_per_s2 = r_bi_accel_m_per_s2;
885                            r_best_jerk_m_per_s3 = r_bi_jerk_m_per_s3;
886                            r_best_accel_spread_m_per_s2 = accel_spread;
887                        }
888                    }
889                }
890            }
891            step_idx += 1;
892        }
893        if r_best_found {
894            (
895                r_best_found,
896                r_best_n,
897                r_best_jerk_m_per_s3,
898                r_best_accel_m_per_s2,
899            )
900        } else {
901            not_found
902        }
903    }
904
905    /// Coast Delay allows us to represent coasting to a stop when the lead
906    /// vehicle has already moved on from that stop.  In this case, the coasting
907    /// vehicle need not dwell at this or any stop while it is lagging behind
908    /// the lead vehicle in distance. Instead, the vehicle comes to a stop and
909    /// resumes mimicing the lead-vehicle trace at the first time-step the
910    /// lead-vehicle moves past the stop-distance. This index is the "coast delay index".
911    ///
912    /// Arguments
913    /// ---------
914    /// - i: the step index
915    ///
916    /// NOTE: Resets the coast_delay_index to 0 and calculates and sets the next
917    /// appropriate coast_delay_index if appropriate
918    pub fn set_coast_delay(&mut self, i: usize) {
919        let speed_tol = 0.01; // m/s
920        let dist_tol = 0.1; // m
921        for idx in i..self.cyc0.speed.len() {
922            // clear all future coast delays
923            self.coast_delay_index[idx] = 0;
924        }
925        let mut coast_delay = None;
926        if !self.idm_allow && self.cyc.speed[i].get::<si::meter_per_second>() < speed_tol {
927            let d0 = trapz_step_start_distance(&self.cyc, i).get::<si::meter>();
928            let d0_lv = self.cyc0_cache.trapz_distances_m[i - 1];
929            let dtlv0 = d0_lv - d0;
930            if dtlv0.abs() > dist_tol {
931                let mut d_lv = 0.0;
932                let mut min_dtlv = None;
933                for (idx, (&dd, &v)) in trapz_step_distances(&self.cyc0)
934                    .iter()
935                    .zip(self.cyc0.speed.iter())
936                    .enumerate()
937                {
938                    let dd = dd.get::<si::meter>();
939                    let v = v.get::<si::meter_per_second>();
940                    d_lv += dd;
941                    let dtlv = (d_lv - d0).abs();
942                    if v < speed_tol && (min_dtlv.is_none() || dtlv <= min_dtlv.unwrap()) {
943                        if min_dtlv.is_none()
944                            || dtlv < min_dtlv.unwrap()
945                            || (d0 < d0_lv && min_dtlv.unwrap() == dtlv)
946                        {
947                            let i_i32 = i32::try_from(i).unwrap();
948                            let idx_i32 = i32::try_from(idx).unwrap();
949                            coast_delay = Some(i_i32 - idx_i32);
950                        }
951                        min_dtlv = Some(dtlv);
952                    }
953                    if min_dtlv.is_some() && dtlv > min_dtlv.unwrap() {
954                        break;
955                    }
956                }
957            }
958        }
959        if let Some(cd) = coast_delay {
960            if cd < 0 {
961                let mut new_cd = cd;
962                for idx in i..self.cyc0.speed.len() {
963                    self.coast_delay_index[idx] = new_cd;
964                    new_cd += 1;
965                    if new_cd == 0 {
966                        break;
967                    }
968                }
969            } else {
970                for idx in i..self.cyc0.speed.len() {
971                    self.coast_delay_index[idx] = cd;
972                }
973            }
974        }
975    }
976
977    /// Generate a coast trajectory without actually modifying the cycle.
978    /// This can be used to calculate the distance to stop via coast using
979    /// actual time-stepping and changing grade.
980    pub fn generate_coast_trajectory(&mut self, i: usize) -> CoastTrajectory {
981        let v0 = self.cyc.speed[i - 1].get::<si::meter_per_second>();
982        let v_brake = self.coast_brake_start_speed.get::<si::meter_per_second>();
983        let a_brake = {
984            let result = self.coast_brake_accel.get::<si::meter_per_second_squared>();
985            if result > 0.0 {
986                -result
987            } else {
988                result
989            }
990        };
991        let ds = &self.cyc0_cache.trapz_distances_m;
992        let d0 = trapz_step_start_distance(&self.cyc, i).get::<si::meter>();
993        let mut distances_m = Vec::with_capacity(ds.len());
994        let mut grade_by_distance = Vec::with_capacity(ds.len());
995        for (idx, d) in ds.iter().enumerate() {
996            if *d >= d0 {
997                distances_m.push(*d - d0);
998                grade_by_distance.push(self.cyc0.grade[idx].get::<si::ratio>());
999            }
1000        }
1001        if distances_m.is_empty() {
1002            return CoastTrajectory {
1003                found_trajectory: false,
1004                distance_to_stop_via_coast_m: 0.0,
1005                start_idx: 0,
1006                speed_m_per_s: None,
1007                distance_to_brake_m: None,
1008            };
1009        }
1010        if v0 <= v_brake {
1011            return CoastTrajectory {
1012                found_trajectory: true,
1013                distance_to_stop_via_coast_m: -0.5 * v0 * v0 / a_brake,
1014                start_idx: i,
1015                speed_m_per_s: None,
1016                distance_to_brake_m: None,
1017            };
1018        }
1019        // dtb = distance to brake: distance traveled during the friction
1020        //       braking part of the maneuver
1021        let dtb = -0.5 * v_brake * v_brake / a_brake;
1022        let mut d = 0.0;
1023        let d_max = distances_m.last().unwrap() - dtb;
1024        let mut unique_grades = HashSet::with_capacity(ds.len());
1025        let grade_mult = 10000.0;
1026        for g in grade_by_distance.iter() {
1027            let grade = (g * grade_mult).round() as i32;
1028            unique_grades.insert(grade);
1029        }
1030        let unique_grade = if unique_grades.len() == 1 {
1031            let ug = unique_grades.iter().nth(0).unwrap();
1032            let ug = (*ug as f64) / grade_mult;
1033            Some(ug)
1034        } else {
1035            None
1036        };
1037        let has_unique_grade = unique_grade.is_some();
1038        let max_iter = 180;
1039        let iters_per_step = if self.favor_grade_accuracy { 2 } else { 1 };
1040        let mut new_speeds_m_per_s = Vec::with_capacity(max_iter as usize);
1041        let mut v = v0;
1042        let mut iter = 0;
1043        let mut idx = i;
1044        // dts0 = distance to next stop from d0
1045        let dts0 = self
1046            .cyc0
1047            .calc_distance_to_next_stop_from(d0 * uc::M, Some(&self.cyc0_cache))
1048            .get::<si::meter>();
1049        while v > v_brake
1050            && v >= 0.0
1051            && d <= d_max
1052            && iter < max_iter
1053            && idx < self.cyc0.speed.len()
1054        {
1055            let dt_s = (self.cyc0.time[i] - self.cyc0.time[i - 1]).get::<si::second>();
1056            let mut gr = match unique_grade {
1057                Some(g) => g,
1058                None => self.cyc0_cache.interp_grade(d + d0),
1059            };
1060            let mut k = self.calc_dvdd(v, gr);
1061            let mut v_next = v * (1.0 + 0.5 * k * dt_s) / (1.0 - 0.5 * k * dt_s);
1062            let mut vavg = 0.5 * (v + v_next);
1063            let mut dd: f64;
1064            for _ in 0..iters_per_step {
1065                k = self.calc_dvdd(vavg, gr);
1066                v_next = v * (1.0 + 0.5 * k * dt_s) / (1.0 - 0.5 * k * dt_s);
1067                vavg = 0.5 * (v + v_next);
1068                dd = vavg * dt_s;
1069                if self.favor_grade_accuracy {
1070                    gr = match unique_grade {
1071                        Some(g) => g,
1072                        None => {
1073                            let dist = (d + d0) * uc::M;
1074                            let delta_dist = dd * uc::M;
1075                            self.cyc0
1076                                .average_grade_over_range(dist, delta_dist, Some(&self.cyc0_cache))
1077                                .get::<si::ratio>()
1078                        }
1079                    };
1080                }
1081            }
1082            if k >= 0.0 && has_unique_grade {
1083                // there is no solution for coast-down -- speed will never decrease
1084                return CoastTrajectory {
1085                    found_trajectory: false,
1086                    distance_to_stop_via_coast_m: 0.0,
1087                    start_idx: 0,
1088                    speed_m_per_s: None,
1089                    distance_to_brake_m: None,
1090                };
1091            }
1092            if v_next <= v_brake {
1093                break;
1094            }
1095            vavg = 0.5 * (v + v_next);
1096            dd = vavg * dt_s;
1097            // dtb = distance to break
1098            let dtb = -0.5 * v_next * v_next / a_brake;
1099            d += dd;
1100            new_speeds_m_per_s.push(v_next);
1101            v = v_next;
1102            if d + dtb > dts0 {
1103                break;
1104            }
1105            iter += 1;
1106            idx += 1;
1107        }
1108        if iter < max_iter && idx < self.cyc0.speed.len() {
1109            let dtb = -0.5 * v * v / a_brake;
1110            let dtb_target = (dts0 - d).max(0.5 * dtb).min(2.0 * dtb);
1111            // dtsc = distance to stop via coasting
1112            let dtsc = d + dtb_target;
1113            return CoastTrajectory {
1114                found_trajectory: true,
1115                distance_to_stop_via_coast_m: dtsc,
1116                start_idx: i,
1117                speed_m_per_s: Some(new_speeds_m_per_s),
1118                distance_to_brake_m: Some(dtb_target),
1119            };
1120        }
1121        CoastTrajectory {
1122            found_trajectory: false,
1123            distance_to_stop_via_coast_m: 0.0,
1124            start_idx: 0,
1125            speed_m_per_s: None,
1126            distance_to_brake_m: None,
1127        }
1128    }
1129
1130    /// Allply the given coasting trajectory to the drive cycle.
1131    fn apply_coast_trajectory(&mut self, coast_traj: &CoastTrajectory) {
1132        if !coast_traj.found_trajectory {
1133            return;
1134        }
1135        let num_speeds = match &coast_traj.speed_m_per_s {
1136            Some(speeds_m_per_s) => {
1137                for (di, &new_speed) in speeds_m_per_s.iter().enumerate() {
1138                    let idx = coast_traj.start_idx + di;
1139                    if idx >= self.cyc0.speed.len() {
1140                        break;
1141                    }
1142                    self.cyc.speed[idx] = new_speed * uc::MPS;
1143                }
1144                speeds_m_per_s.len()
1145            }
1146            None => 0,
1147        };
1148        let (_, n) = self.cyc.modify_with_braking_trajectory(
1149            self.coast_brake_accel,
1150            coast_traj.start_idx + num_speeds,
1151            coast_traj.distance_to_brake_m.map(|d| d * uc::M),
1152        );
1153        for di in 0..(self.cyc0.speed.len() - coast_traj.start_idx) {
1154            let idx = coast_traj.start_idx + di;
1155            self.impose_coast[idx] = di < num_speeds + n;
1156        }
1157    }
1158
1159    /// Calculates the derivative dv/dd (change in speed by change in distance)
1160    /// - speed_m_per_s: the speed at which to evaluate dv/dd (m/s)
1161    /// - grade: the road grade as a decimal fraction
1162    ///
1163    /// RETURN: number, the dv/dd for these conditions
1164    pub fn calc_dvdd(&self, speed_m_per_s: f64, grade: f64) -> f64 {
1165        let v = speed_m_per_s;
1166        if v <= 0.0 {
1167            return 0.0;
1168        }
1169        let (atan_grade_sin, atan_grade_cos) = if grade == 0.0 {
1170            (0.0, 1.0)
1171        } else {
1172            let atan_grade = grade.atan();
1173            (atan_grade.sin(), atan_grade.cos())
1174        };
1175        let g = uc::ACC_GRAV.get::<si::meter_per_second_squared>();
1176        let m = self.mass.get::<si::kilogram>();
1177        let rho_cdfa = self.air_density.get::<si::kilogram_per_cubic_meter>()
1178            * self.drag_coef.get::<si::ratio>()
1179            * self.frontal_area.get::<si::square_meter>();
1180        let rrc = self.wheel_rr_coef.get::<si::ratio>();
1181        -((g / v) * (atan_grade_sin + rrc * atan_grade_cos) + (0.5 * rho_cdfa * (1.0 / m) * v))
1182    }
1183
1184    /// Prevent collision between the vehicle in cyc and the one in cyc0.
1185    /// If a collision will take place, reworks the cyc such that a rendezvous occurs instead.
1186    ///
1187    /// # Arguments
1188    /// - i: int, index for consideration
1189    /// - passing_tol_m: None | float, tolerance for how far we have to go past the lead vehicle to be considered "passing"
1190    ///
1191    /// RETURN: Bool, True if cyc was modified
1192    fn prevent_collisions(&mut self, i: usize, passing_tol: Option<si::Length>) -> bool {
1193        let passing_tol_m = passing_tol.map(|pt| pt.get::<si::meter>()).unwrap_or(1.0);
1194        let pass_info = PassingInfo::from(&self.cyc, &self.cyc0, i, passing_tol);
1195        if !pass_info.passing_detected {
1196            return false;
1197        }
1198        let mut best = RendezvousTrajectory {
1199            found_trajectory: false,
1200            idx: 0,
1201            n: 0,
1202            full_brake_steps: 0,
1203            jerk_m_per_s3: 0.0,
1204            accel0_m_per_s2: 0.0,
1205            accel_spread: 0.0,
1206        };
1207        let a_brake_m_per_s2 = {
1208            let result = self.coast_brake_accel.get::<si::meter_per_second_squared>();
1209            if result > 0.0 {
1210                -result
1211            } else {
1212                result
1213            }
1214        };
1215        for full_brake_steps in 0..4 {
1216            for di in 0..(self.cyc.speed.len() - i) {
1217                let idx = i + di;
1218                if !self.impose_coast[idx] {
1219                    if idx == i {
1220                        break;
1221                    } else {
1222                        continue;
1223                    }
1224                }
1225                let n = pass_info.index - idx + 1 - full_brake_steps;
1226                if n < 2 {
1227                    break;
1228                }
1229                if (idx - 1 + full_brake_steps) >= self.cyc.speed.len() {
1230                    break;
1231                }
1232                let dt = pass_info.time_step_duration.get::<si::second>();
1233                let v_start_m_per_s = self.cyc.speed[idx - 1].get::<si::meter_per_second>();
1234                let dt_full_brake = (self.cyc.time[idx - 1 + full_brake_steps]
1235                    - self.cyc.time[idx - 1])
1236                    .get::<si::second>();
1237                let dv_full_brake = dt_full_brake * a_brake_m_per_s2;
1238                let v_start_jerk_m_per_s = (v_start_m_per_s + dv_full_brake).max(0.0);
1239                let dd_full_brake = 0.5 * (v_start_m_per_s + v_start_jerk_m_per_s) * dt_full_brake;
1240                let d_start_m =
1241                    trapz_step_start_distance(&self.cyc, idx).get::<si::meter>() + dd_full_brake;
1242                let pass_distance_m = pass_info.distance.get::<si::meter>();
1243                if pass_distance_m <= d_start_m {
1244                    continue;
1245                }
1246                let jerk_trajectory = calc_constant_jerk_trajectory(
1247                    n,
1248                    d_start_m,
1249                    v_start_jerk_m_per_s,
1250                    pass_info.distance.get::<si::meter>(),
1251                    pass_info.speed.get::<si::meter_per_second>(),
1252                    dt,
1253                );
1254                let mut accels_m_per_s2 = vec![];
1255                let mut trace_accels_m_per_s2 = vec![];
1256                for ni in 0..n {
1257                    if (ni + idx + full_brake_steps) >= self.cyc.time.len() {
1258                        break;
1259                    }
1260                    accels_m_per_s2.push(accel_for_constant_jerk(
1261                        ni,
1262                        jerk_trajectory.acceleration_m_per_s2,
1263                        jerk_trajectory.jerk_m_per_s3,
1264                        jerk_trajectory.step_duration_s,
1265                    ));
1266                    let index1 = ni + idx + full_brake_steps;
1267                    let index0 = index1 - 1;
1268                    let dvi = (self.cyc.speed[index1] - self.cyc.speed[index0])
1269                        .get::<si::meter_per_second>();
1270                    let dti = (self.cyc.time[index1] - self.cyc.time[index0]).get::<si::second>();
1271                    trace_accels_m_per_s2.push(dvi / dti);
1272                }
1273                let all_sub_coast = trace_accels_m_per_s2
1274                    .iter()
1275                    .copied()
1276                    .zip(accels_m_per_s2.iter().copied())
1277                    .fold(
1278                        true,
1279                        |all_sc_flag: bool, (trace_accel, accel): (f64, f64)| {
1280                            if !all_sc_flag {
1281                                return all_sc_flag;
1282                            }
1283                            trace_accel >= accel
1284                        },
1285                    );
1286                let (min_accel_m_per_s2, max_accel_m_per_s2) = {
1287                    if !accels_m_per_s2.is_empty() {
1288                        let mut a_min = accels_m_per_s2[0];
1289                        let mut a_max = accels_m_per_s2[0];
1290                        for a in &accels_m_per_s2 {
1291                            if *a < a_min {
1292                                a_min = *a;
1293                            }
1294                            if *a > a_max {
1295                                a_max = *a;
1296                            }
1297                        }
1298                        (a_min, a_max)
1299                    } else {
1300                        (0.0, 0.0)
1301                    }
1302                };
1303                let accept = all_sub_coast;
1304                let accel_spread = (max_accel_m_per_s2 - min_accel_m_per_s2).abs();
1305                if accept && (!best.found_trajectory || accel_spread < best.accel_spread) {
1306                    best = RendezvousTrajectory {
1307                        found_trajectory: true,
1308                        idx,
1309                        n,
1310                        full_brake_steps,
1311                        jerk_m_per_s3: jerk_trajectory.jerk_m_per_s3,
1312                        accel0_m_per_s2: jerk_trajectory.acceleration_m_per_s2,
1313                        accel_spread,
1314                    };
1315                }
1316            }
1317            if best.found_trajectory {
1318                break;
1319            }
1320        }
1321        if !best.found_trajectory {
1322            let new_passing_tol_m = if passing_tol_m < 10.0 {
1323                10.0
1324            } else {
1325                passing_tol_m + 5.0
1326            };
1327            if new_passing_tol_m > 60.0 {
1328                return false;
1329            }
1330            return self.prevent_collisions(i, Some(new_passing_tol_m * uc::M));
1331        }
1332        for fbs in 0..best.full_brake_steps {
1333            if (best.idx + fbs) >= self.cyc.time.len() {
1334                break;
1335            }
1336            let dt =
1337                (self.cyc.time[best.idx + fbs] - self.cyc.time[best.idx - 1]).get::<si::second>();
1338            let dv = a_brake_m_per_s2 * dt;
1339            let v_start = self.cyc.speed[best.idx - 1].get::<si::meter_per_second>();
1340            self.cyc.speed[best.idx + fbs] = (v_start + dv).max(0.0) * uc::MPS;
1341            self.impose_coast[best.idx + fbs] = true;
1342            self.coast_delay_index[best.idx + fbs] = 0;
1343        }
1344        self.cyc.modify_by_const_jerk_trajectory(
1345            best.idx + best.full_brake_steps,
1346            best.n,
1347            best.jerk_m_per_s3 * uc::MPS3,
1348            best.accel0_m_per_s2 * uc::MPS2,
1349        );
1350        for idx in (best.idx + best.n)..self.cyc0.speed.len() {
1351            self.impose_coast[idx] = false;
1352            self.coast_delay_index[idx] = 0;
1353        }
1354        true
1355    }
1356}
1357
1358#[cfg(test)]
1359mod tests {
1360    use crate::prelude::SimDrive;
1361
1362    use super::*;
1363
1364    #[test]
1365    fn test_that_coasting_works() {
1366        let udds = crate::drive_cycle::Cycle::from_resource("udds.csv", false).unwrap();
1367        let veh = crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
1368        let mut man = Maneuver::from(&udds, &veh);
1369        man.coast_allow = true;
1370        man.coast_start_speed = 20.0 * uc::MPS;
1371        man.coast_allow_passing = true;
1372        man.apply();
1373        let udds_mod = man.cyc;
1374        assert_eq!(udds_mod.time.len(), udds.time.len());
1375        assert_eq!(udds_mod.speed.len(), udds.speed.len());
1376        assert_eq!(udds_mod.dist.len(), udds.dist.len());
1377        let mut speeds_differ = false;
1378        for idx in 0..udds.time.len() {
1379            speeds_differ = udds_mod.speed[idx] != udds.speed[idx];
1380            if speeds_differ {
1381                break;
1382            }
1383        }
1384        assert!(speeds_differ);
1385    }
1386
1387    #[test]
1388    fn test_advanced_coasting() {
1389        let udds = crate::drive_cycle::Cycle::from_resource("udds.csv", false).unwrap();
1390        let veh = crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
1391        let mut man = Maneuver::from(&udds, &veh);
1392        man.coast_allow = true;
1393        man.coast_start_speed = 0.0 * uc::MPS;
1394        man.coast_brake_start_speed = 20.0 * uc::MPH;
1395        man.coast_brake_accel = -2.5 * uc::MPS2;
1396        man.favor_grade_accuracy = false;
1397        man.coast_allow_passing = true;
1398        man.coast_max_speed = 75.0 * uc::MPH;
1399        man.coast_time_horizon_for_adjustment = 120.0 * uc::S;
1400        man.apply();
1401        let udds_mod = man.cyc;
1402        let mut sd = SimDrive::new(veh, udds_mod, None);
1403        sd.walk().unwrap();
1404    }
1405
1406    #[test]
1407    fn test_cruise() {
1408        let udds = crate::drive_cycle::Cycle::from_resource("udds.csv", false).unwrap();
1409        let vavg = udds.average_speed(true);
1410        let veh = crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
1411        let mut man = Maneuver::from(&udds, &veh);
1412        man.idm_allow = true;
1413        man.idm_desired_speed = vavg;
1414        man.idm_headway = 1.0 * uc::S;
1415        man.idm_minimum_gap = 1.0 * uc::M;
1416        man.idm_delta = 4.0;
1417        man.idm_acceleration = 1.0 * uc::MPS2;
1418        man.idm_deceleration = 2.5 * uc::MPS2;
1419        man.coast_allow = false;
1420        man.apply();
1421        let udds_mod = man.cyc;
1422        let mut sd = SimDrive::new(veh, udds_mod, None);
1423        sd.walk().unwrap();
1424    }
1425
1426    #[test]
1427    fn test_cruise_and_coast() {
1428        let udds = crate::drive_cycle::Cycle::from_resource("udds.csv", false).unwrap();
1429        let vavg = udds.average_speed(true);
1430        let veh = crate::vehicle::Vehicle::from_resource("2012_Ford_Fusion.yaml", false).unwrap();
1431        let mut man = Maneuver::from(&udds, &veh);
1432        man.idm_allow = true;
1433        man.idm_desired_speed = vavg;
1434        man.idm_headway = 1.0 * uc::S;
1435        man.idm_minimum_gap = 1.0 * uc::M;
1436        man.idm_delta = 4.0;
1437        man.idm_acceleration = 1.0 * uc::MPS2;
1438        man.idm_deceleration = 2.5 * uc::MPS2;
1439        man.coast_allow = true;
1440        man.coast_brake_start_speed = 8.9408 * uc::MPS;
1441        man.coast_brake_accel = -2.5 * uc::MPS2;
1442        man.favor_grade_accuracy = true;
1443        man.coast_allow_passing = true;
1444        man.coast_time_horizon_for_adjustment = 120.0 * uc::S;
1445        man.apply();
1446        let udds_mod = man.cyc;
1447        let mut found_coast = false;
1448        for ic in man.impose_coast {
1449            if ic {
1450                found_coast = true;
1451                break;
1452            }
1453        }
1454        assert!(found_coast);
1455        let mut sd = SimDrive::new(veh, udds_mod, None);
1456        sd.walk().unwrap();
1457    }
1458}