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 #[serde(default)]
24 pub cyc: Cycle,
25 #[serde(default)]
27 pub cyc0: Cycle,
28
29 #[serde(default)]
32 pub mass: si::Mass,
33 #[serde(default)]
35 pub air_density: si::MassDensity,
36 #[serde(default)]
38 pub drag_coef: si::Ratio,
39 #[serde(default)]
41 pub frontal_area: si::Area,
42 #[serde(default)]
44 pub wheel_rr_coef: si::Ratio,
45 #[serde(default)]
47 pub wheel_inertia: si::MomentOfInertia,
48 #[serde(default)]
50 pub num_wheels: u8,
51 #[serde(default)]
53 pub wheel_radius: si::Length,
54
55 #[serde(default)]
59 pub ach_speed_max_iter: u32,
60 #[serde(default)]
63 pub ach_speed_tol: si::Ratio,
64 #[serde(default)]
66 pub ach_speed_solver_gain: f64,
67
68 #[serde(default)]
71 pub coast_allow: bool,
72 #[serde(default)]
74 pub coast_start_speed: si::Velocity,
75 #[serde(default)]
77 pub coast_brake_start_speed: si::Velocity,
78 #[serde(default)]
81 pub coast_brake_accel: si::Acceleration,
82 #[serde(default)]
88 pub favor_grade_accuracy: bool,
89 #[serde(default)]
92 pub coast_allow_passing: bool,
93 #[serde(default)]
95 pub coast_max_speed: si::Velocity,
96 #[serde(default)]
99 pub coast_time_horizon_for_adjustment: si::Time,
100
101 #[serde(default)]
105 pub idm_allow: bool,
106 #[serde(default)]
110 pub idm_desired_speed_by_distance: Option<Vec<(si::Length, si::Velocity)>>,
111 #[serde(default)]
114 pub idm_desired_speed: si::Velocity,
115 #[serde(default)]
117 pub idm_headway: si::Time,
118 #[serde(default)]
120 pub idm_minimum_gap: si::Length,
121 #[serde(default)]
123 pub idm_delta: f64,
124 #[serde(default)]
126 pub idm_acceleration: si::Acceleration,
127 #[serde(default)]
129 pub idm_deceleration: si::Acceleration,
130
131 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 fn create_from_py(cyc: &Cycle, veh: &Vehicle) -> PyResult<Self> {
148 Ok(Maneuver::from(cyc, veh))
149 }
150
151 #[pyo3(name = "apply_maneuvers")]
152 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 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 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 let air_density = 1.2 * uc::KGPM3;
251
252 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 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 fn walk(&mut self, cyc_len: usize) {
292 while self.i < cyc_len {
293 self.step();
294 }
295 self.cyc.dist = vec![];
299 self.cyc.elev = vec![];
300 self.cyc.init().unwrap();
301 }
302
303 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 pub fn set_speed_for_target_gap_using_idm(&mut self, i: usize) {
358 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 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 #[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 let s0_m = s0_m.max(0.0);
416 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 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 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 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 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 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 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 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 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 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 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 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 let coast_speed = self.solve_step(i);
648 if self.impose_coast[i - 1] && v1_traj <= v_brake {
649 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 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 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 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 self.cyc.speed[i]
756 .min(max_coast_speed)
757 .max(si::Velocity::ZERO)
758 }
759 }
760
761 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 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 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 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 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 let dtb =
822 -0.5 * brake_start_speed_m_per_s * brake_start_speed_m_per_s / brake_accel_m_per_s2;
823 let dtbi0 = dts0 - dtb;
825 if dtbi0 < 0.0 {
826 return not_found;
827 }
828 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 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 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 pub fn set_coast_delay(&mut self, i: usize) {
919 let speed_tol = 0.01; let dist_tol = 0.1; for idx in i..self.cyc0.speed.len() {
922 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 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 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 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 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 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 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 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 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 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}