fastsim_core/simdrive/
cyc_mods.rs

1//! SimDrive methods that manipulate cycle on the fly
2
3use super::simdrive_impl::*;
4use super::*;
5use crate::cycle::{
6    accel_array_for_constant_jerk, accel_for_constant_jerk, calc_constant_jerk_trajectory,
7    create_dist_and_target_speeds_by_microtrip, detect_passing, extend_cycle,
8    trapz_distance_for_step, trapz_step_distances, trapz_step_start_distance,
9};
10use crate::simdrive::RustSimDrive;
11use crate::utils::{add_from, max, min, ndarrcumsum, ndarrunique};
12
13impl RustSimDrive {
14    /// Provides the gap-with lead vehicle from start to finish
15    pub fn gap_to_lead_vehicle_m(&self) -> Array1<f64> {
16        // TODO: consider basing on dist_m?
17        let mut gaps_m = ndarrcumsum(&trapz_step_distances(&self.cyc0))
18            - ndarrcumsum(&trapz_step_distances(&self.cyc));
19        if self.sim_params.idm_allow {
20            gaps_m += self.sim_params.idm_minimum_gap_m;
21        }
22        gaps_m
23    }
24
25    /// Sets the intelligent driver model parameters for an eco-cruise driving trajectory.
26    /// This is a convenience method instead of setting the sim_params.idm* parameters yourself.
27    /// - by_microtrip: bool, if True, target speed is set by microtrip, else by cycle
28    /// - extend_fraction: float, the fraction of time to extend the cycle to allow for catch-up
29    ///     of the following vehicle
30    /// - blend_factor: float, a value between 0 and 1; only used of by_microtrip is True, blends
31    ///     between microtrip average speed and microtrip average speed when moving. Must be
32    ///     between 0 and 1 inclusive
33    /// - min_target_speed_m_per_s: float, the minimum speed allowed by the eco-cruise algorithm
34    /// Mutates the current SimDrive object for eco-cruise.
35    pub fn activate_eco_cruise_rust(
36        &mut self,
37        by_microtrip: bool,            // False
38        extend_fraction: f64,          // 0.1
39        blend_factor: f64,             // 0.0
40        min_target_speed_m_per_s: f64, // 8.0
41    ) -> anyhow::Result<()> {
42        self.sim_params.idm_allow = true;
43        if !by_microtrip {
44            self.sim_params.idm_v_desired_m_per_s =
45                if !self.cyc0.time_s.is_empty() && self.cyc0.time_s.last().unwrap() > &0.0 {
46                    self.cyc0
47                        .dist_m()
48                        .slice(s![0..self.cyc0.time_s.len()])
49                        .sum()
50                        / self.cyc0.time_s.last().unwrap()
51                } else {
52                    0.0
53                };
54        } else {
55            ensure!(
56                (0.0..=1.0).contains(&blend_factor),
57                "blend_factor must be between 0 and 1 but got {}",
58                blend_factor
59            );
60            ensure!(
61                min_target_speed_m_per_s >= 0.0,
62                "min_target_speed_m_per_s must be >= 0.0 but got {}",
63                min_target_speed_m_per_s
64            );
65            self.sim_params.idm_v_desired_in_m_per_s_by_distance_m =
66                Some(create_dist_and_target_speeds_by_microtrip(
67                    &self.cyc0,
68                    blend_factor,
69                    min_target_speed_m_per_s,
70                ));
71        }
72        // Extend the duration of the base cycle
73        ensure!(
74            extend_fraction >= 0.0,
75            "extend_fraction must be >= 0.0 but got {}",
76            extend_fraction
77        );
78        if extend_fraction > 0.0 {
79            self.cyc0 = extend_cycle(&self.cyc0, None, Some(extend_fraction));
80            self.cyc = self.cyc0.clone();
81        }
82        Ok(())
83    }
84
85    /// Calculate the next speed by the Intelligent Driver Model
86    /// - i: int, the index
87    /// - a_m_per_s2: number, max acceleration (m/s2)
88    /// - b_m_per_s2: number, max deceleration (m/s2)
89    /// - dt_headway_s: number, the headway between us and the lead vehicle in seconds
90    /// - s0_m: number, the initial gap between us and the lead vehicle in meters
91    /// - v_desired_m_per_s: number, the desired speed in (m/s)
92    /// - delta: number, a shape parameter; typical value is 4.0
93    /// RETURN: number, the next speed (m/s)
94    /// REFERENCE:
95    /// Treiber, Martin and Kesting, Arne. 2013. "Chapter 11: Car-Following Models Based on Driving Strategies".
96    ///     Traffic Flow Dynamics: Data, Models and Simulation. Springer-Verlag. Springer, Berlin, Heidelberg.
97    ///     DOI: <https://doi.org/10.1007/978-3-642-32460-4>.
98    #[allow(clippy::too_many_arguments)]
99    pub fn next_speed_by_idm(
100        &mut self,
101        i: usize,
102        a_m_per_s2: f64,
103        b_m_per_s2: f64,
104        dt_headway_s: f64,
105        s0_m: f64,
106        v_desired_m_per_s: f64,
107        delta: f64,
108    ) -> f64 {
109        if v_desired_m_per_s <= 0.0 {
110            return 0.0;
111        }
112        let a_m_per_s2 = a_m_per_s2.abs();
113        let b_m_per_s2 = b_m_per_s2.abs();
114        let dt_headway_s = max(dt_headway_s, 0.0);
115        // we assume the vehicles start out a "minimum gap" apart
116        let s0_m = max(0.0, s0_m);
117        // DERIVED VALUES
118        let sqrt_ab = (a_m_per_s2 * b_m_per_s2).powf(0.5);
119        let v0_m_per_s = self.mps_ach[i - 1];
120        let v0_lead_m_per_s = self.cyc0.mps[i - 1];
121        let dv0_m_per_s = v0_m_per_s - v0_lead_m_per_s;
122        let d0_lead_m = self.cyc0_cache.trapz_distances_m[(i - 1).max(0)] + s0_m;
123        let d0_m = trapz_step_start_distance(&self.cyc, i);
124        let s_m = max(d0_lead_m - d0_m, 0.01);
125        // IDM EQUATIONS
126        let s_target_m = s0_m
127            + max(
128                0.0,
129                (v0_m_per_s * dt_headway_s) + ((v0_m_per_s * dv0_m_per_s) / (2.0 * sqrt_ab)),
130            );
131        let accel_target_m_per_s2 = a_m_per_s2
132            * (1.0 - ((v0_m_per_s / v_desired_m_per_s).powf(delta)) - ((s_target_m / s_m).powi(2)));
133        max(
134            v0_m_per_s + (accel_target_m_per_s2 * self.cyc.dt_s_at_i(i)),
135            0.0,
136        )
137    }
138
139    /// Set gap
140    /// - i: non-negative integer, the step index
141    /// RETURN: None
142    /// EFFECTS:
143    /// - sets the next speed (m/s)
144    /// EQUATION:
145    /// parameters:
146    ///     - v_desired: the desired speed (m/s)
147    ///     - delta: number, typical value is 4.0
148    ///     - a: max acceleration, (m/s2)
149    ///     - b: max deceleration, (m/s2)
150    /// s = d_lead - d
151    /// dv/dt = a * (1 - (v/v_desired)**delta - (s_desired(v,v-v_lead)/s)**2)
152    /// s_desired(v, dv) = s0 + max(0, v*dt_headway + (v * dv)/(2.0 * sqrt(a*b)))
153    /// REFERENCE:
154    /// Treiber, Martin and Kesting, Arne. 2013. "Chapter 11: Car-Following Models Based on Driving Strategies".
155    ///     Traffic Flow Dynamics: Data, Models and Simulation. Springer-Verlag. Springer, Berlin, Heidelberg.
156    ///     DOI: <https://doi.org/10.1007/978-3-642-32460-4>
157    pub fn set_speed_for_target_gap_using_idm(&mut self, i: usize) {
158        // PARAMETERS
159        let v_desired_m_per_s = if self.idm_target_speed_m_per_s[i] > 0.0 {
160            self.idm_target_speed_m_per_s[i]
161        } else {
162            *self.cyc0.mps.max().unwrap()
163        };
164        // DERIVED VALUES
165        self.cyc.mps[i] = self.next_speed_by_idm(
166            i,
167            self.sim_params.idm_accel_m_per_s2,
168            self.sim_params.idm_decel_m_per_s2,
169            self.sim_params.idm_dt_headway_s,
170            self.sim_params.idm_minimum_gap_m,
171            v_desired_m_per_s,
172            self.sim_params.idm_delta,
173        );
174    }
175
176    /// - i: non-negative integer, the step index
177    /// RETURN: None
178    /// EFFECTS:
179    /// - sets the next speed (m/s)
180    pub fn set_speed_for_target_gap(&mut self, i: usize) {
181        self.set_speed_for_target_gap_using_idm(i);
182    }
183
184    /// Provides a quick estimate for grade based only on the distance traveled
185    /// at the start of the current step. If the grade is constant over the
186    /// step, this is both quick and accurate.
187    /// NOTE:
188    ///     If not allowing coasting (i.e., sim_params.coast_allow == False)
189    ///     and not allowing IDM/following (i.e., self.sim_params.idm_allow == False)
190    ///     then returns self.cyc.grade\[i\]
191    pub fn estimate_grade_for_step(&self, i: usize) -> anyhow::Result<f64> {
192        if self.cyc0_cache.grade_all_zero {
193            return Ok(0.0);
194        }
195        if !self.sim_params.coast_allow && !self.sim_params.idm_allow {
196            return Ok(self.cyc.grade[i]);
197        }
198        self.cyc0_cache
199            .interp_grade(trapz_step_start_distance(&self.cyc, i))
200    }
201
202    /// For situations where cyc can deviate from cyc0, this method
203    /// looks up and accurately interpolates what the average grade over
204    /// the step should be.
205    /// If mps_ach is not None, the mps_ach value is used to predict the
206    /// distance traveled over the step.
207    /// NOTE:
208    ///     If not allowing coasting (i.e., sim_params.coast_allow == False)
209    ///     and not allowing IDM/following (i.e., self.sim_params.idm_allow == False)
210    ///     then returns self.cyc.grade\[i\]
211    pub fn lookup_grade_for_step(&self, i: usize, mps_ach: Option<f64>) -> anyhow::Result<f64> {
212        if self.cyc0_cache.grade_all_zero {
213            return Ok(0.0);
214        }
215        if !self.sim_params.coast_allow && !self.sim_params.idm_allow {
216            return Ok(self.cyc.grade[i]);
217        }
218        match mps_ach {
219            Some(mps_ach) => self.cyc0.average_grade_over_range(
220                trapz_step_start_distance(&self.cyc, i),
221                0.5 * (mps_ach + self.mps_ach[i - 1]) * self.cyc.dt_s_at_i(i),
222                Some(&self.cyc0_cache),
223            ),
224            None => self.cyc0.average_grade_over_range(
225                trapz_step_start_distance(&self.cyc, i),
226                trapz_distance_for_step(&self.cyc, i),
227                Some(&self.cyc0_cache),
228            ),
229        }
230    }
231    pub fn set_time_dilation(&mut self, i: usize) -> anyhow::Result<()> {
232        // if prescribed speed is zero, trace is met to avoid div-by-zero errors and other possible wackiness
233        let mut trace_met = (self.cyc.dist_m().slice(s![0..(i + 1)]).sum()
234            - self.dist_m.slice(s![0..(i + 1)]).sum())
235        .abs()
236            / self.cyc0.dist_m().slice(s![0..(i + 1)]).sum()
237            < self.sim_params.time_dilation_tol
238            || self.cyc.mps[i] == 0.0;
239
240        let mut d_short = vec![];
241        let mut t_dilation = vec![0.0]; // no time dilation initially
242        if !trace_met {
243            self.trace_miss_iters[i] += 1;
244
245            d_short.push(
246                self.cyc0.dist_m().slice(s![0..i + 1]).sum()
247                    - self.dist_m.slice(s![0..i + 1]).sum(),
248            ); // positive if behind trace
249            t_dilation.push(min(
250                max(
251                    d_short.last().unwrap() / self.cyc0.dt_s_at_i(i) / self.mps_ach[i], // initial guess, speed that needed to be achived per speed that was achieved
252                    self.sim_params.min_time_dilation,
253                ),
254                self.sim_params.max_time_dilation,
255            ));
256
257            // add time dilation factor * step size to current and subsequent times
258            self.cyc.time_s = add_from(
259                &self.cyc.time_s,
260                i,
261                self.cyc.dt_s_at_i(i) * t_dilation.last().unwrap(),
262            );
263            self.solve_step(i)?;
264
265            trace_met =
266                    // convergence criteria
267                    (self.cyc0.dist_m().slice(s![0..i+1]).sum() - self.dist_m.slice(s![0..i+1]).sum()).abs() / self.cyc0.dist_m().slice(s![0..i+1]).sum()
268                    < self.sim_params.time_dilation_tol
269                    // exceeding max time dilation
270                    || t_dilation.last().unwrap() >= &self.sim_params.max_time_dilation
271                    // lower than min time dilation
272                    || t_dilation.last().unwrap() <= &self.sim_params.min_time_dilation;
273        }
274        while !trace_met {
275            // iterate newton's method until time dilation has converged or other exit criteria trigger trace_met == True
276            // distance shortfall [m]
277            // correct time steps
278            d_short.push(
279                self.cyc0.dist_m().slice(s![0..i + 1]).sum()
280                    - self.dist_m.slice(s![0..i + 1]).sum(),
281            );
282            t_dilation.push(min(
283                max(
284                    t_dilation.last().unwrap()
285                        - (t_dilation.last().unwrap() - t_dilation[t_dilation.len() - 2])
286                            / (d_short.last().unwrap() - d_short[d_short.len() - 2])
287                            * d_short.last().unwrap(),
288                    self.sim_params.min_time_dilation,
289                ),
290                self.sim_params.max_time_dilation,
291            ));
292            self.cyc.time_s = add_from(
293                &self.cyc.time_s,
294                i,
295                self.cyc.dt_s_at_i(i) * t_dilation.last().unwrap(),
296            );
297
298            self.solve_step(i)?;
299
300            self.trace_miss_iters[i] += 1;
301
302            trace_met =
303                    // convergence criteria
304                    (self.cyc0.dist_m().slice(s![0..i+1]).sum() - self.dist_m.slice(s![0..i+1]).sum()).abs() / self.cyc0.dist_m().slice(s![0..i+1]).sum()
305                    < self.sim_params.time_dilation_tol
306                    // max iterations
307                    || self.trace_miss_iters[i] >= self.sim_params.max_trace_miss_iters
308                    // exceeding max time dilation
309                    || t_dilation.last().unwrap() >= &self.sim_params.max_time_dilation
310                    // lower than min time dilation
311                    || t_dilation.last().unwrap() <= &self.sim_params.min_time_dilation;
312        }
313        Ok(())
314    }
315
316    // Calculates the derivative dv/dd (change in speed by change in distance)
317    // - v: number, the speed at which to evaluate dv/dd (m/s)
318    // - grade: number, the road grade as a decimal fraction
319    // RETURN: number, the dv/dd for these conditions
320    fn calc_dvdd(&self, v: f64, grade: f64) -> f64 {
321        if v <= 0.0 {
322            0.0
323        } else {
324            let (atan_grade_sin, atan_grade_cos) = if grade == 0.0 {
325                (0.0, 1.0)
326            } else {
327                let atan_g = grade.atan();
328                (atan_g.sin(), atan_g.cos())
329            };
330            let g = self.props.a_grav_mps2;
331            let m = self.veh.veh_kg;
332            let rho_cdfa =
333                self.props.air_density_kg_per_m3 * self.veh.drag_coef * self.veh.frontal_area_m2;
334            let rrc = self.veh.wheel_rr_coef;
335            -1.0 * ((g / v) * (atan_grade_sin + rrc * atan_grade_cos)
336                + (0.5 * rho_cdfa * (1.0 / m) * v))
337        }
338    }
339
340    fn apply_coast_trajectory(&mut self, coast_traj: CoastTrajectory) -> anyhow::Result<()> {
341        if coast_traj.found_trajectory {
342            let num_speeds = match coast_traj.speeds_m_per_s {
343                Some(speeds_m_per_s) => {
344                    for (di, &new_speed) in speeds_m_per_s.iter().enumerate() {
345                        let idx = coast_traj.start_idx + di;
346                        if idx >= self.mps_ach.len() {
347                            break;
348                        }
349                        self.cyc.mps[idx] = new_speed;
350                    }
351                    speeds_m_per_s.len()
352                }
353                None => 0,
354            };
355            let (_, n) = self.cyc.modify_with_braking_trajectory(
356                self.sim_params.coast_brake_accel_m_per_s2,
357                coast_traj.start_idx + num_speeds,
358                coast_traj.distance_to_brake_m,
359            )?;
360            for di in 0..(self.cyc0.mps.len() - coast_traj.start_idx) {
361                let idx = coast_traj.start_idx + di;
362                self.impose_coast[idx] = di < num_speeds + n;
363            }
364        }
365        Ok(())
366    }
367
368    /// Generate a coast trajectory without actually modifying the cycle.
369    /// This can be used to calculate the distance to stop via coast using
370    /// actual time-stepping and dynamically changing grade.
371    fn generate_coast_trajectory(&self, i: usize) -> anyhow::Result<CoastTrajectory> {
372        let v0 = self.mps_ach[i - 1];
373        let v_brake = self.sim_params.coast_brake_start_speed_m_per_s;
374        let a_brake = self.sim_params.coast_brake_accel_m_per_s2;
375        assert![a_brake <= 0.0];
376        let ds = &self.cyc0_cache.trapz_distances_m;
377        let d0 = trapz_step_start_distance(&self.cyc, i);
378        let mut distances_m = Vec::with_capacity(ds.len());
379        let mut grade_by_distance = Vec::with_capacity(ds.len());
380        for idx in 0..ds.len() {
381            if ds[idx] >= d0 {
382                distances_m.push(ds[idx] - d0);
383                grade_by_distance.push(self.cyc0.grade[idx])
384            }
385        }
386        if distances_m.is_empty() {
387            return Ok(CoastTrajectory {
388                found_trajectory: false,
389                distance_to_stop_via_coast_m: 0.0,
390                start_idx: 0,
391                speeds_m_per_s: None,
392                distance_to_brake_m: None,
393            });
394        }
395        let distances_m = Array::from_vec(distances_m);
396        let grade_by_distance = Array::from_vec(grade_by_distance);
397        // distance traveled while stopping via friction-braking (i.e., distance to brake)
398        if v0 <= v_brake {
399            return Ok(CoastTrajectory {
400                found_trajectory: true,
401                distance_to_stop_via_coast_m: -0.5 * v0 * v0 / a_brake,
402                start_idx: i,
403                speeds_m_per_s: None,
404                distance_to_brake_m: None,
405            });
406        }
407        let dtb = -0.5 * v_brake * v_brake / a_brake;
408        let mut d = 0.0;
409        let d_max = distances_m.last().unwrap() - dtb;
410        let unique_grades = ndarrunique(&grade_by_distance);
411        let unique_grade = if unique_grades.len() == 1 {
412            Some(unique_grades[0])
413        } else {
414            None
415        };
416        let has_unique_grade = unique_grade.is_some();
417        let max_iter = 180;
418        let iters_per_step = if self.sim_params.favor_grade_accuracy {
419            2
420        } else {
421            1
422        };
423        let mut new_speeds_m_per_s = Vec::with_capacity(max_iter as usize);
424        let mut v = v0;
425        let mut iter = 0;
426        let mut idx = i;
427        let dts0 = self
428            .cyc0
429            .calc_distance_to_next_stop_from(d0, Some(&self.cyc0_cache));
430        while v > v_brake && v >= 0.0 && d <= d_max && iter < max_iter && idx < self.mps_ach.len() {
431            let dt_s = self.cyc0.dt_s_at_i(idx);
432            let mut gr = match unique_grade {
433                Some(g) => g,
434                None => self.cyc0_cache.interp_grade(d + d0)?,
435            };
436            let mut k = self.calc_dvdd(v, gr);
437            let mut v_next = v * (1.0 + 0.5 * k * dt_s) / (1.0 - 0.5 * k * dt_s);
438            let mut vavg = 0.5 * (v + v_next);
439            let mut dd: f64;
440            for _ in 0..iters_per_step {
441                k = self.calc_dvdd(vavg, gr);
442                v_next = v * (1.0 + 0.5 * k * dt_s) / (1.0 - 0.5 * k * dt_s);
443                vavg = 0.5 * (v + v_next);
444                dd = vavg * dt_s;
445                if self.sim_params.favor_grade_accuracy {
446                    gr = match unique_grade {
447                        Some(g) => g,
448                        None => self.cyc0.average_grade_over_range(
449                            d + d0,
450                            dd,
451                            Some(&self.cyc0_cache),
452                        )?,
453                    };
454                }
455            }
456            if k >= 0.0 && has_unique_grade {
457                // there is no solution for coastdown -- speed will never decrease
458                return Ok(CoastTrajectory {
459                    found_trajectory: false,
460                    distance_to_stop_via_coast_m: 0.0,
461                    start_idx: 0,
462                    speeds_m_per_s: None,
463                    distance_to_brake_m: None,
464                });
465            }
466            if v_next <= v_brake {
467                break;
468            }
469            vavg = 0.5 * (v + v_next);
470            dd = vavg * dt_s;
471            let dtb = -0.5 * v_next * v_next / a_brake;
472            d += dd;
473            new_speeds_m_per_s.push(v_next);
474            v = v_next;
475            if d + dtb > dts0 {
476                break;
477            }
478            iter += 1;
479            idx += 1;
480        }
481        if iter < max_iter && idx < self.mps_ach.len() {
482            let dtb = -0.5 * v * v / a_brake;
483            let dtb_target = min(max(dts0 - d, 0.5 * dtb), 2.0 * dtb);
484            let dtsc = d + dtb_target;
485            return Ok(CoastTrajectory {
486                found_trajectory: true,
487                distance_to_stop_via_coast_m: dtsc,
488                start_idx: i,
489                speeds_m_per_s: Some(new_speeds_m_per_s),
490                distance_to_brake_m: Some(dtb_target),
491            });
492        }
493        Ok(CoastTrajectory {
494            found_trajectory: false,
495            distance_to_stop_via_coast_m: 0.0,
496            start_idx: 0,
497            speeds_m_per_s: None,
498            distance_to_brake_m: None,
499        })
500    }
501
502    /// Calculate the distance to stop via coasting in meters.
503    /// - i: non-negative-integer, the current index
504    /// RETURN: non-negative-number or -1.0
505    /// - if -1.0, it means there is no solution to a coast-down distance.
506    ///     This can happen due to being too close to the given
507    ///     stop or perhaps due to coasting downhill
508    /// - if a non-negative-number, the distance in meters that the vehicle
509    ///     would freely coast if unobstructed. Accounts for grade between
510    ///     the current point and end-point
511    fn calc_distance_to_stop_coast_v2(&self, i: usize) -> anyhow::Result<f64> {
512        let not_found = -1.0;
513        let v0 = self.cyc.mps[i - 1];
514        let v_brake = self.sim_params.coast_brake_start_speed_m_per_s;
515        let a_brake = self.sim_params.coast_brake_accel_m_per_s2;
516        let ds = &self.cyc0_cache.trapz_distances_m;
517        let gs = &self.cyc0.grade;
518        assert!(
519            ds.len() == gs.len(),
520            "Assumed length of ds and gs the same but actually ds.len():{} and gs.len():{}",
521            ds.len(),
522            gs.len()
523        );
524        let d0 = trapz_step_start_distance(&self.cyc, i);
525        let mut grade_by_distance = Vec::with_capacity(ds.len());
526        for idx in 0..ds.len() {
527            if ds[idx] >= d0 {
528                grade_by_distance.push(gs[idx]);
529            }
530        }
531        let grade_by_distance = Array::from_vec(grade_by_distance);
532        let veh_mass_kg = self.veh.veh_kg;
533        let air_density_kg_per_m3 = self.props.air_density_kg_per_m3;
534        let cdfa_m2 = self.veh.drag_coef * self.veh.frontal_area_m2;
535        let rrc = self.veh.wheel_rr_coef;
536        let gravity_m_per_s2 = self.props.a_grav_mps2;
537        // distance traveled while stopping via friction-braking (i.e., distance to brake)
538        let dtb = -0.5 * v_brake * v_brake / a_brake;
539        if v0 <= v_brake {
540            return Ok(-0.5 * v0 * v0 / a_brake);
541        }
542        let unique_grades = ndarrunique(&grade_by_distance);
543        if unique_grades.len() == 1 {
544            // if there is only one grade, there may be a closed-form solution
545            let unique_grade = unique_grades[0];
546            let theta = unique_grade.atan();
547            let c1 = gravity_m_per_s2 * (theta.sin() + rrc * theta.cos());
548            let c2 = (air_density_kg_per_m3 * cdfa_m2) / (2.0 * veh_mass_kg);
549            let v02 = v0 * v0;
550            let vb2 = v_brake * v_brake;
551            let mut d = not_found;
552            let a1 = c1 + c2 * v02;
553            let b1 = c1 + c2 * vb2;
554            if c2 == 0.0 {
555                if c1 > 0.0 {
556                    d = (1.0 / (2.0 * c1)) * (v02 - vb2);
557                }
558            } else if a1 > 0.0 && b1 > 0.0 {
559                d = (1.0 / (2.0 * c2)) * (a1.ln() - b1.ln());
560            }
561            if d != not_found {
562                return Ok(d + dtb);
563            }
564        }
565        let ct = self.generate_coast_trajectory(i)?;
566        if ct.found_trajectory {
567            Ok(ct.distance_to_stop_via_coast_m)
568        } else {
569            Ok(not_found)
570        }
571    }
572
573    /// - i: non-negative integer, the current position in cyc
574    /// RETURN: Bool if vehicle should initiate coasting
575    /// Coast logic is that the vehicle should coast if it is within coasting distance of a stop:
576    /// - if distance to coast from start of step is <= distance to next stop
577    /// - AND distance to coast from end of step (using prescribed speed) is > distance to next stop
578    /// - ALSO, vehicle must have been at or above the coast brake start speed at beginning of step
579    /// - AND, must be at least 4 x distances-to-break away
580    fn should_impose_coast(&self, i: usize) -> anyhow::Result<bool> {
581        if self.sim_params.coast_start_speed_m_per_s > 0.0 {
582            return Ok(self.cyc.mps[i] >= self.sim_params.coast_start_speed_m_per_s);
583        }
584        let v0 = self.mps_ach[i - 1];
585        if v0 < self.sim_params.coast_brake_start_speed_m_per_s {
586            return Ok(false);
587        }
588        // distance to stop by coasting from start of step (i-1)
589        let dtsc0 = self.calc_distance_to_stop_coast_v2(i)?;
590        if dtsc0 < 0.0 {
591            return Ok(false);
592        }
593        // distance to next stop (m)
594        let d0 = trapz_step_start_distance(&self.cyc, i);
595        let dts0 = self
596            .cyc0
597            .calc_distance_to_next_stop_from(d0, Some(&self.cyc0_cache));
598        let dtb = -0.5 * v0 * v0 / self.sim_params.coast_brake_accel_m_per_s2;
599        Ok(dtsc0 >= dts0 && dts0 >= (4.0 * dtb))
600    }
601
602    /// Calculate next rendezvous trajectory for eco-coasting
603    /// - i: non-negative integer, the index into cyc for the end of start-of-step
604    ///     (i.e., the step that may be modified; should be i)
605    /// - min_accel_m__s2: number, the minimum acceleration permitted (m/s2)
606    /// - max_accel_m__s2: number, the maximum acceleration permitted (m/s2)
607    /// RETURN: (Tuple
608    ///     found_rendezvous: Bool, if True the remainder of the data is valid; if False, no rendezvous found
609    ///     n: positive integer, the number of steps ahead to rendezvous at
610    ///     jerk_m__s3: number, the Jerk or first-derivative of acceleration (m/s3)
611    ///     accel_m__s2: number, the initial acceleration of the trajectory (m/s2)
612    /// )
613    /// If no rendezvous exists within the scope, the returned tuple has False for the first item.
614    /// Otherwise, returns the next closest rendezvous in time/space
615    fn calc_next_rendezvous_trajectory(
616        &self,
617        i: usize,
618        min_accel_m_per_s2: f64,
619        max_accel_m_per_s2: f64,
620    ) -> anyhow::Result<(bool, usize, f64, f64)> {
621        let tol = 1e-6;
622        // v0 is where n=0, i.e., idx-1
623        let v0 = self.cyc.mps[i - 1];
624        let brake_start_speed_m_per_s = self.sim_params.coast_brake_start_speed_m_per_s;
625        let brake_accel_m_per_s2 = self.sim_params.coast_brake_accel_m_per_s2;
626        let time_horizon_s = max(self.sim_params.coast_time_horizon_for_adjustment_s, 1.0);
627        // distance_horizon_m = 1000.0
628        let not_found_n = 0;
629        let not_found_jerk_m_per_s3 = 0.0;
630        let not_found_accel_m_per_s2 = 0.0;
631        let not_found = (
632            false,
633            not_found_n,
634            not_found_jerk_m_per_s3,
635            not_found_accel_m_per_s2,
636        );
637        if v0 < (brake_start_speed_m_per_s + tol) {
638            // don't process braking
639            return Ok(not_found);
640        }
641        let (min_accel_m_per_s2, max_accel_m_per_s2) = if min_accel_m_per_s2 > max_accel_m_per_s2 {
642            (max_accel_m_per_s2, min_accel_m_per_s2)
643        } else {
644            (min_accel_m_per_s2, max_accel_m_per_s2)
645        };
646        let num_samples = self.cyc.mps.len();
647        let d0 = trapz_step_start_distance(&self.cyc, i);
648        // a_proposed = (v1 - v0) / dt
649        // distance to stop from start of time-step
650        let dts0 = self
651            .cyc0
652            .calc_distance_to_next_stop_from(d0, Some(&self.cyc0_cache));
653        if dts0 < 0.0 {
654            // no stop to coast towards or we're there...
655            return Ok(not_found);
656        }
657        let dt = self.cyc.dt_s_at_i(i);
658        // distance to brake from the brake start speed (m/s)
659        let dtb =
660            -0.5 * brake_start_speed_m_per_s * brake_start_speed_m_per_s / brake_accel_m_per_s2;
661        // distance to brake initiation from start of time-step (m)
662        let dtbi0 = dts0 - dtb;
663        if dtbi0 < 0.0 {
664            return Ok(not_found);
665        }
666        // Now, check rendezvous trajectories
667        let mut step_idx = i;
668        let mut dt_plan = 0.0;
669        let mut r_best_found = false;
670        let mut r_best_n = 0;
671        let mut r_best_jerk_m_per_s3 = 0.0;
672        let mut r_best_accel_m_per_s2 = 0.0;
673        let mut r_best_accel_spread_m_per_s2 = 0.0;
674        while dt_plan <= time_horizon_s && step_idx < num_samples {
675            dt_plan += self.cyc0.dt_s_at_i(step_idx);
676            let step_ahead = step_idx - (i - 1);
677            if step_ahead == 1 {
678                // for brake init rendezvous
679                let accel = (brake_start_speed_m_per_s - v0) / dt;
680                let v1 = max(0.0, v0 + accel * dt);
681                let dd_proposed = ((v0 + v1) / 2.0) * dt;
682                if (v1 - brake_start_speed_m_per_s).abs() < tol && (dtbi0 - dd_proposed).abs() < tol
683                {
684                    r_best_found = true;
685                    r_best_n = 1;
686                    r_best_jerk_m_per_s3 = 0.0;
687                    r_best_accel_m_per_s2 = accel;
688                    break;
689                }
690            } else {
691                // rendezvous trajectory for brake-start -- assumes fixed time-steps
692                if dtbi0 > 0.0 {
693                    let (r_bi_jerk_m_per_s3, r_bi_accel_m_per_s2) = calc_constant_jerk_trajectory(
694                        step_ahead,
695                        0.0,
696                        v0,
697                        dtbi0,
698                        brake_start_speed_m_per_s,
699                        dt,
700                    )?;
701                    if r_bi_accel_m_per_s2 < max_accel_m_per_s2
702                        && r_bi_accel_m_per_s2 > min_accel_m_per_s2
703                        && r_bi_jerk_m_per_s3 >= 0.0
704                    {
705                        let as_bi = accel_array_for_constant_jerk(
706                            step_ahead,
707                            r_bi_accel_m_per_s2,
708                            r_bi_jerk_m_per_s3,
709                            dt,
710                        );
711                        let as_bi_min = as_bi.to_vec().into_iter().reduce(f64::min).unwrap_or(0.0);
712                        let as_bi_max = as_bi.to_vec().into_iter().reduce(f64::max).unwrap_or(0.0);
713                        let accel_spread = (as_bi_max - as_bi_min).abs();
714                        let flag = (as_bi_max < (max_accel_m_per_s2 + 1e-6)
715                            && as_bi_min > (min_accel_m_per_s2 - 1e-6))
716                            && (!r_best_found || (accel_spread < r_best_accel_spread_m_per_s2));
717                        if flag {
718                            r_best_found = true;
719                            r_best_n = step_ahead;
720                            r_best_accel_m_per_s2 = r_bi_accel_m_per_s2;
721                            r_best_jerk_m_per_s3 = r_bi_jerk_m_per_s3;
722                            r_best_accel_spread_m_per_s2 = accel_spread;
723                        }
724                    }
725                }
726            }
727            step_idx += 1;
728        }
729        if r_best_found {
730            return Ok((
731                r_best_found,
732                r_best_n,
733                r_best_jerk_m_per_s3,
734                r_best_accel_m_per_s2,
735            ));
736        }
737        Ok(not_found)
738    }
739
740    /// Coast Delay allows us to represent coasting to a stop when the lead
741    /// vehicle has already moved on from that stop.  In this case, the coasting
742    /// vehicle need not dwell at this or any stop while it is lagging behind
743    /// the lead vehicle in distance. Instead, the vehicle comes to a stop and
744    /// resumes mimicing the lead-vehicle trace at the first time-step the
745    /// lead-vehicle moves past the stop-distance. This index is the "coast delay index".
746    ///
747    /// Arguments
748    /// ---------
749    /// - i: integer, the step index
750    /// NOTE: Resets the coast_delay_index to 0 and calculates and sets the next
751    /// appropriate coast_delay_index if appropriate
752    fn set_coast_delay(&mut self, i: usize) {
753        let speed_tol = 0.01; // m/s
754        let dist_tol = 0.1; // meters
755        for idx in i..self.cyc.len() {
756            self.coast_delay_index[idx] = 0; // clear all future coast-delays
757        }
758        let mut coast_delay = None;
759        if !self.sim_params.idm_allow && self.cyc.mps[i] < speed_tol {
760            let d0 = trapz_step_start_distance(&self.cyc, i);
761            let d0_lv = self.cyc0_cache.trapz_distances_m[i - 1];
762            let dtlv0 = d0_lv - d0;
763            if dtlv0.abs() > dist_tol {
764                let mut d_lv = 0.0;
765                let mut min_dtlv = None;
766                for (idx, (&dd, &v)) in trapz_step_distances(&self.cyc0)
767                    .iter()
768                    .zip(self.cyc0.mps.iter())
769                    .enumerate()
770                {
771                    d_lv += dd;
772                    let dtlv = (d_lv - d0).abs();
773                    if v < speed_tol && (min_dtlv.is_none() || dtlv <= min_dtlv.unwrap()) {
774                        if min_dtlv.is_none()
775                            || dtlv < min_dtlv.unwrap()
776                            || (d0 < d0_lv && min_dtlv.unwrap() == dtlv)
777                        {
778                            let i_i32 = i32::try_from(i).unwrap();
779                            let idx_i32 = i32::try_from(idx).unwrap();
780                            coast_delay = Some(i_i32 - idx_i32);
781                        }
782                        min_dtlv = Some(dtlv);
783                    }
784                    if min_dtlv.is_some() && dtlv > min_dtlv.unwrap() {
785                        break;
786                    }
787                }
788            }
789        }
790        if let Some(cd) = coast_delay {
791            if cd < 0 {
792                let mut new_cd = cd;
793                for idx in i..self.cyc0.mps.len() {
794                    self.coast_delay_index[idx] = new_cd;
795                    new_cd += 1;
796                    if new_cd == 0 {
797                        break;
798                    }
799                }
800            } else {
801                for idx in i..self.cyc0.mps.len() {
802                    self.coast_delay_index[idx] = cd;
803                }
804            }
805        }
806    }
807
808    /// Prevent collision between the vehicle in cyc and the one in cyc0.
809    /// If a collision will take place, reworks the cyc such that a rendezvous occurs instead.
810    /// Arguments
811    /// - i: int, index for consideration
812    /// - passing_tol_m: None | float, tolerance for how far we have to go past the lead vehicle to be considered "passing"
813    /// RETURN: Bool, True if cyc was modified
814    fn prevent_collisions(&mut self, i: usize, passing_tol_m: Option<f64>) -> anyhow::Result<bool> {
815        let passing_tol_m = passing_tol_m.unwrap_or(1.0);
816        let collision = detect_passing(&self.cyc, &self.cyc0, i, Some(passing_tol_m));
817        if !collision.has_collision {
818            return Ok(false);
819        }
820        let mut best = RendezvousTrajectory {
821            found_trajectory: false,
822            idx: 0,
823            n: 0,
824            full_brake_steps: 0,
825            jerk_m_per_s3: 0.0,
826            accel0_m_per_s2: 0.0,
827            accel_spread: 0.0,
828        };
829        let a_brake_m_per_s2 = self.sim_params.coast_brake_accel_m_per_s2;
830        assert!(
831            a_brake_m_per_s2 < 0.0,
832            "brake acceleration must be negative; got {} m/s2",
833            a_brake_m_per_s2
834        );
835        for full_brake_steps in 0..4 {
836            for di in 0..(self.mps_ach.len() - i) {
837                let idx = i + di;
838                if !self.impose_coast[idx] {
839                    if idx == i {
840                        break;
841                    } else {
842                        continue;
843                    }
844                }
845                let n = collision.idx - idx + 1 - full_brake_steps;
846                if n < 2 {
847                    break;
848                }
849                if (idx - 1 + full_brake_steps) >= self.cyc.len() {
850                    break;
851                }
852                let dt = collision.time_step_duration_s;
853                let v_start_m_per_s = self.cyc.mps[idx - 1];
854                let dt_full_brake =
855                    self.cyc.time_s[idx - 1 + full_brake_steps] - self.cyc.time_s[idx - 1];
856                let dv_full_brake = dt_full_brake * a_brake_m_per_s2;
857                let v_start_jerk_m_per_s = max(v_start_m_per_s + dv_full_brake, 0.0);
858                let dd_full_brake = 0.5 * (v_start_m_per_s + v_start_jerk_m_per_s) * dt_full_brake;
859                let d_start_m = trapz_step_start_distance(&self.cyc, idx) + dd_full_brake;
860                if collision.distance_m <= d_start_m {
861                    continue;
862                }
863                let (jerk_m_per_s3, accel0_m_per_s2) = calc_constant_jerk_trajectory(
864                    n,
865                    d_start_m,
866                    v_start_jerk_m_per_s,
867                    collision.distance_m,
868                    collision.speed_m_per_s,
869                    dt,
870                )?;
871                let mut accels_m_per_s2 = vec![];
872                let mut trace_accels_m_per_s2 = vec![];
873                for ni in 0..n {
874                    if (ni + idx + full_brake_steps) >= self.cyc.len() {
875                        break;
876                    }
877                    accels_m_per_s2.push(accel_for_constant_jerk(
878                        ni,
879                        accel0_m_per_s2,
880                        jerk_m_per_s3,
881                        dt,
882                    ));
883                    trace_accels_m_per_s2.push(
884                        (self.cyc.mps[ni + idx + full_brake_steps]
885                            - self.cyc.mps[ni + idx - 1 + full_brake_steps])
886                            / self.cyc.dt_s()[ni + idx + full_brake_steps],
887                    );
888                }
889                let all_sub_coast = trace_accels_m_per_s2
890                    .iter()
891                    .copied()
892                    .zip(accels_m_per_s2.iter().copied())
893                    .fold(
894                        true,
895                        |all_sc_flag: bool, (trace_accel, accel): (f64, f64)| {
896                            if !all_sc_flag {
897                                return all_sc_flag;
898                            }
899                            trace_accel >= accel
900                        },
901                    );
902                let accels_ndarr = Array1::from(accels_m_per_s2.clone());
903                let min_accel_m_per_s2 = accels_ndarr.min()?;
904                let max_accel_m_per_s2 = accels_ndarr.max()?;
905                let accept = all_sub_coast;
906                let accel_spread = (max_accel_m_per_s2 - min_accel_m_per_s2).abs();
907                if accept && (!best.found_trajectory || accel_spread < best.accel_spread) {
908                    best = RendezvousTrajectory {
909                        found_trajectory: true,
910                        idx,
911                        n,
912                        full_brake_steps,
913                        jerk_m_per_s3,
914                        accel0_m_per_s2,
915                        accel_spread,
916                    };
917                }
918            }
919            if best.found_trajectory {
920                break;
921            }
922        }
923        if !best.found_trajectory {
924            let new_passing_tol_m = if passing_tol_m < 10.0 {
925                10.0
926            } else {
927                passing_tol_m + 5.0
928            };
929            if new_passing_tol_m > 60.0 {
930                return Ok(false);
931            }
932            return self.prevent_collisions(i, Some(new_passing_tol_m));
933        }
934        for fbs in 0..best.full_brake_steps {
935            if (best.idx + fbs) >= self.cyc.len() {
936                break;
937            }
938            let dt = self.cyc.time_s[best.idx + fbs] - self.cyc.time_s[best.idx - 1];
939            let dv = a_brake_m_per_s2 * dt;
940            let v_start = self.cyc.mps[best.idx - 1];
941            self.cyc.mps[best.idx + fbs] = max(v_start + dv, 0.0);
942            self.impose_coast[best.idx + fbs] = true;
943            self.coast_delay_index[best.idx + fbs] = 0;
944        }
945        self.cyc.modify_by_const_jerk_trajectory(
946            best.idx + best.full_brake_steps,
947            best.n,
948            best.jerk_m_per_s3,
949            best.accel0_m_per_s2,
950        );
951        for idx in (best.idx + best.n)..self.cyc0.mps.len() {
952            self.impose_coast[idx] = false;
953            self.coast_delay_index[idx] = 0;
954        }
955        Ok(true)
956    }
957
958    /// Placeholder for method to impose coasting.
959    /// Might be good to include logic for deciding when to coast.
960    /// Solve for the next-step speed that will yield a zero roadload
961    pub fn set_coast_speed(&mut self, i: usize) -> anyhow::Result<()> {
962        let tol = 1e-6;
963        let v0 = self.mps_ach[i - 1];
964        if v0 > tol && !self.impose_coast[i] && self.should_impose_coast(i)? {
965            let ct = self.generate_coast_trajectory(i)?;
966            if ct.found_trajectory {
967                let d = ct.distance_to_stop_via_coast_m;
968                if d < 0.0 {
969                    for idx in i..self.cyc0.mps.len() {
970                        self.impose_coast[idx] = false;
971                    }
972                } else {
973                    self.apply_coast_trajectory(ct)?;
974                }
975                if !self.sim_params.coast_allow_passing {
976                    self.prevent_collisions(i, None)?;
977                }
978            }
979        }
980        if !self.impose_coast[i] {
981            if !self.sim_params.idm_allow {
982                let i_i32 = i32::try_from(i).ok();
983                let target_idx = match i_i32 {
984                    Some(v) => Some(v - self.coast_delay_index[i]),
985                    None => None,
986                };
987                let target_idx = match target_idx {
988                    Some(ti) => {
989                        if ti < 0 {
990                            Some(0)
991                        } else {
992                            usize::try_from(ti).ok()
993                        }
994                    }
995                    None => None,
996                };
997                if let Some(ti) = target_idx {
998                    self.cyc.mps[i] = self.cyc0.mps[cmp::min(ti, self.cyc0.mps.len() - 1)];
999                }
1000            }
1001            return Ok(());
1002        }
1003        let v1_traj = self.cyc.mps[i];
1004        if v0 > self.sim_params.coast_brake_start_speed_m_per_s {
1005            if self.sim_params.coast_allow_passing {
1006                // we could be coasting downhill so could in theory go to a higher speed
1007                // since we can pass, allow vehicle to go up to max coasting speed (m/s)
1008                // the solver will show us what we can actually achieve
1009                self.cyc.mps[i] = self.sim_params.coast_max_speed_m_per_s;
1010            } else {
1011                self.cyc.mps[i] = min(v1_traj, self.sim_params.coast_max_speed_m_per_s);
1012            }
1013        }
1014        // Solve for the actual coasting speed
1015        self.solve_step(i)?;
1016        self.newton_iters[i] = 0; // reset newton iters
1017        self.cyc.mps[i] = self.mps_ach[i];
1018        let accel_proposed = (self.cyc.mps[i] - self.cyc.mps[i - 1]) / self.cyc.dt_s_at_i(i);
1019        if self.cyc.mps[i] < tol {
1020            for idx in i..self.cyc0.mps.len() {
1021                self.impose_coast[idx] = false;
1022            }
1023            self.set_coast_delay(i);
1024            self.cyc.mps[i] = 0.0;
1025            return Ok(());
1026        }
1027        if (self.cyc.mps[i] - v1_traj).abs() > tol {
1028            let mut adjusted_current_speed = false;
1029            let brake_speed_start_tol_m_per_s = 0.1;
1030            if self.cyc.mps[i]
1031                < (self.sim_params.coast_brake_start_speed_m_per_s - brake_speed_start_tol_m_per_s)
1032            {
1033                let (_, num_steps) = self.cyc.modify_with_braking_trajectory(
1034                    self.sim_params.coast_brake_accel_m_per_s2,
1035                    i,
1036                    None,
1037                )?;
1038                for idx in i..self.cyc.len() {
1039                    self.impose_coast[idx] = idx < (i + num_steps);
1040                }
1041                adjusted_current_speed = true;
1042            } else {
1043                let (traj_found, traj_n, traj_jerk_m_per_s3, traj_accel_m_per_s2) = self
1044                    .calc_next_rendezvous_trajectory(
1045                        i,
1046                        self.sim_params.coast_brake_accel_m_per_s2,
1047                        min(accel_proposed, 0.0),
1048                    )?;
1049                if traj_found {
1050                    // adjust cyc to perform the trajectory
1051                    let final_speed_m_per_s = self.cyc.modify_by_const_jerk_trajectory(
1052                        i,
1053                        traj_n,
1054                        traj_jerk_m_per_s3,
1055                        traj_accel_m_per_s2,
1056                    );
1057                    for idx in i..self.cyc0.mps.len() {
1058                        self.impose_coast[idx] = idx < (i + traj_n);
1059                    }
1060                    adjusted_current_speed = true;
1061                    let i_for_brake = i + traj_n;
1062                    if (final_speed_m_per_s - self.sim_params.coast_brake_start_speed_m_per_s).abs()
1063                        < 0.1
1064                    {
1065                        let (_, num_steps) = self.cyc.modify_with_braking_trajectory(
1066                            self.sim_params.coast_brake_accel_m_per_s2,
1067                            i_for_brake,
1068                            None,
1069                        )?;
1070                        for idx in i_for_brake..self.cyc0.mps.len() {
1071                            self.impose_coast[idx] = idx < i_for_brake + num_steps;
1072                        }
1073                        adjusted_current_speed = true;
1074                    } else {
1075                        #[cfg(feature = "logging")]
1076                        log::warn!(
1077                            "final_speed_m_per_s={} not close to coast_brake_start_speed={} for i={}; i_for_brake={}, traj_n={}",
1078                            final_speed_m_per_s,
1079                            self.sim_params.coast_brake_start_speed_m_per_s,
1080                            i,
1081                            i_for_brake,
1082                            traj_n
1083                        );
1084                    }
1085                }
1086            }
1087            if adjusted_current_speed {
1088                if !self.sim_params.coast_allow_passing {
1089                    self.prevent_collisions(i, None)?;
1090                }
1091                self.solve_step(i)?;
1092                self.newton_iters[i] = 0; // reset newton iters
1093                self.cyc.mps[i] = self.mps_ach[i];
1094            }
1095        }
1096        Ok(())
1097    }
1098}