1use std::collections::HashMap;
4
5use crate::imports::*;
7use crate::params::*;
8use crate::proc_macros::add_pyo3_api;
9#[cfg(feature = "pyo3")]
10use crate::pyo3imports::*;
11use crate::utils::*;
12
13#[cfg_attr(feature = "pyo3", pyfunction)]
14pub fn calc_constant_jerk_trajectory(
26 n: usize,
27 d0: f64,
28 v0: f64,
29 dr: f64,
30 vr: f64,
31 dt: f64,
32) -> anyhow::Result<(f64, f64)> {
33 ensure!(n > 1);
34 ensure!(dr > d0);
35 let n = n as f64;
36 let ddr = dr - d0;
37 let dvr = vr - v0;
38 let k = (dvr - (2.0 * ddr / (n * dt)) + 2.0 * v0)
39 / (0.5 * n * (n - 1.0) * dt
40 - (1.0 / 3.0) * (n - 1.0) * (n - 2.0) * dt
41 - 0.5 * (n - 1.0) * dt * dt);
42 let a0 = ((ddr / dt)
43 - n * v0
44 - ((1.0 / 6.0) * n * (n - 1.0) * (n - 2.0) * dt + 0.25 * n * (n - 1.0) * dt * dt) * k)
45 / (0.5 * n * n * dt);
46 Ok((k, a0))
47}
48
49#[cfg_attr(feature = "pyo3", pyfunction)]
50pub fn dist_for_constant_jerk(n: usize, d0: f64, v0: f64, a0: f64, k: f64, dt: f64) -> f64 {
64 let n = n as f64;
65 let term1 = dt
66 * ((n * v0)
67 + (0.5 * n * (n - 1.0) * a0 * dt)
68 + ((1.0 / 6.0) * k * dt * (n - 2.0) * (n - 1.0) * n));
69 let term2 = 0.5 * dt * dt * ((n * a0) + (0.5 * n * (n - 1.0) * k * dt));
70 d0 + term1 + term2
71}
72
73#[cfg_attr(feature = "pyo3", pyfunction)]
74pub fn speed_for_constant_jerk(n: usize, v0: f64, a0: f64, k: f64, dt: f64) -> f64 {
90 let n = n as f64;
91 v0 + (n * a0 * dt) + (0.5 * n * (n - 1.0) * k * dt)
92}
93
94#[cfg_attr(feature = "pyo3", pyfunction)]
95pub fn accel_for_constant_jerk(n: usize, a0: f64, k: f64, dt: f64) -> f64 {
108 let n = n as f64;
109 a0 + (n * k * dt)
110}
111
112pub fn accel_array_for_constant_jerk(nmax: usize, a0: f64, k: f64, dt: f64) -> Array1<f64> {
114 let mut accels = Vec::new();
115 for n in 0..nmax {
116 accels.push(accel_for_constant_jerk(n, a0, k, dt));
117 }
118 Array1::from_vec(accels)
119}
120
121pub fn average_step_speeds(cyc: &RustCycle) -> Array1<f64> {
123 let mut result = Vec::with_capacity(cyc.len());
124 result.push(0.0);
125 for i in 1..cyc.len() {
126 result.push(0.5 * (cyc.mps[i] + cyc.mps[i - 1]));
127 }
128 Array1::from_vec(result)
129}
130
131pub fn average_step_speed_at(cyc: &RustCycle, i: usize) -> f64 {
134 0.5 * (cyc.mps[i] + cyc.mps[i - 1])
135}
136
137pub fn trapz_step_distances(cyc: &RustCycle) -> Array1<f64> {
140 average_step_speeds(cyc) * cyc.dt_s()
141}
142
143pub fn trapz_step_distances_primitive(time_s: &Array1<f64>, mps: &Array1<f64>) -> Array1<f64> {
144 let mut delta_dists_m = Vec::with_capacity(time_s.len());
145 delta_dists_m.push(0.0);
146 for i in 1..time_s.len() {
147 delta_dists_m.push((time_s[i] - time_s[i - 1]) * 0.5 * (mps[i] + mps[i - 1]));
148 }
149 Array1::from_vec(delta_dists_m)
150}
151
152pub fn trapz_step_start_distance(cyc: &RustCycle, i: usize) -> f64 {
156 let mut dist_m = 0.0;
157 for i in 1..i {
158 dist_m += (cyc.time_s[i] - cyc.time_s[i - 1]) * 0.5 * (cyc.mps[i] + cyc.mps[i - 1]);
159 }
160 dist_m
161}
162
163pub fn trapz_distance_for_step(cyc: &RustCycle, i: usize) -> f64 {
166 average_step_speed_at(cyc, i) * cyc.dt_s_at_i(i)
167}
168
169pub fn trapz_distance_over_range(cyc: &RustCycle, i_start: usize, i_end: usize) -> f64 {
172 trapz_step_distances(cyc).slice(s![i_start..i_end]).sum()
173}
174
175pub fn time_spent_moving(cyc: &RustCycle, stopped_speed_m_per_s: Option<f64>) -> f64 {
180 let mut t_move_s = 0.0;
181 let stopped_speed_m_per_s = stopped_speed_m_per_s.unwrap_or(0.0);
182 for idx in 1..cyc.len() {
183 let dt = cyc.time_s[idx] - cyc.time_s[idx - 1];
184 let vavg = (cyc.mps[idx] + cyc.mps[idx - 1]) / 2.0;
185 if vavg > stopped_speed_m_per_s {
186 t_move_s += dt;
187 }
188 }
189 t_move_s
190}
191
192pub fn to_microtrips(cycle: &RustCycle, stop_speed_m_per_s: Option<f64>) -> Vec<RustCycle> {
202 let stop_speed_m_per_s = stop_speed_m_per_s.unwrap_or(1e-6);
203 let mut microtrips = Vec::new();
204 let ts = cycle.time_s.to_vec();
205 let vs = cycle.mps.to_vec();
206 let gs = cycle.grade.to_vec();
207 let rs = cycle.road_type.to_vec();
208 let mut mt_ts = Vec::new();
209 let mut mt_vs = Vec::new();
210 let mut mt_gs = Vec::new();
211 let mut mt_rs = Vec::new();
212 let mut moving = false;
213 for idx in 0..ts.len() {
214 let t = ts[idx];
215 let v = vs[idx];
216 let g = gs[idx];
217 let r = rs[idx];
218 if v > stop_speed_m_per_s && !moving && mt_ts.len() > 1 {
219 let last_idx = mt_ts.len() - 1;
220 let last_t = mt_ts[last_idx];
221 let last_v = mt_vs[last_idx];
222 let last_g = mt_gs[last_idx];
223 let last_r = mt_rs[last_idx];
224 mt_ts = mt_ts.iter().map(|t| -> f64 { t - mt_ts[0] }).collect();
225 microtrips.push(RustCycle {
226 time_s: Array::from_vec(mt_ts),
227 mps: Array::from_vec(mt_vs),
228 grade: Array::from_vec(mt_gs),
229 road_type: Array::from_vec(mt_rs),
230 name: cycle.name.clone(),
231 orphaned: false,
232 });
233 mt_ts = vec![last_t];
234 mt_vs = vec![last_v];
235 mt_gs = vec![last_g];
236 mt_rs = vec![last_r];
237 }
238 mt_ts.push(t);
239 mt_vs.push(v);
240 mt_gs.push(g);
241 mt_rs.push(r);
242 moving = v > stop_speed_m_per_s;
243 }
244 if !mt_ts.is_empty() {
245 mt_ts = mt_ts.iter().map(|t| -> f64 { t - mt_ts[0] }).collect();
246 microtrips.push(RustCycle {
247 time_s: Array::from_vec(mt_ts),
248 mps: Array::from_vec(mt_vs),
249 grade: Array::from_vec(mt_gs),
250 road_type: Array::from_vec(mt_rs),
251 name: cycle.name.clone(),
252 orphaned: false,
253 });
254 }
255 microtrips
256}
257
258pub fn create_dist_and_target_speeds_by_microtrip(
273 cyc: &RustCycle,
274 blend_factor: f64,
275 min_target_speed_mps: f64,
276) -> Vec<(f64, f64)> {
277 let blend_factor = blend_factor.clamp(0.0, 1.0);
278 let mut dist_and_tgt_speeds = Vec::new();
279 let microtrips = to_microtrips(cyc, None);
281 let mut dist_at_start_of_microtrip_m = 0.0;
282 for mt_cyc in microtrips {
283 let mt_dist_m = mt_cyc.dist_m().sum();
284 let mt_time_s = mt_cyc.time_s.last().unwrap() - mt_cyc.time_s.first().unwrap();
285 let mt_moving_time_s = time_spent_moving(&mt_cyc, None);
286 let mt_avg_spd_m_per_s = if mt_time_s > 0.0 {
287 mt_dist_m / mt_time_s
288 } else {
289 0.0
290 };
291 let mt_moving_avg_spd_m_per_s = if mt_moving_time_s > 0.0 {
292 mt_dist_m / mt_moving_time_s
293 } else {
294 0.0
295 };
296 let mt_target_spd_m_per_s =
297 (blend_factor * (mt_moving_avg_spd_m_per_s - mt_avg_spd_m_per_s) + mt_avg_spd_m_per_s)
298 .min(mt_moving_avg_spd_m_per_s)
299 .max(mt_avg_spd_m_per_s);
300 if mt_dist_m > 0.0 {
301 dist_and_tgt_speeds.push((
302 dist_at_start_of_microtrip_m,
303 mt_target_spd_m_per_s.max(min_target_speed_mps),
304 ));
305 dist_at_start_of_microtrip_m += mt_dist_m;
306 }
307 }
308 dist_and_tgt_speeds
309}
310
311pub fn extend_cycle(
318 cyc: &RustCycle,
319 absolute_time_s: Option<f64>, time_fraction: Option<f64>, ) -> RustCycle {
322 let absolute_time_s = absolute_time_s.unwrap_or(0.0);
323 let time_fraction = time_fraction.unwrap_or(0.0);
324 let mut ts = cyc.time_s.to_vec();
325 let mut vs = cyc.mps.to_vec();
326 let mut gs = cyc.grade.to_vec();
327 let mut rs = cyc.road_type.to_vec();
328 let extra_time_s = (absolute_time_s + (time_fraction * ts.last().unwrap())).round() as i32;
329 if extra_time_s == 0 {
330 return cyc.clone();
331 }
332 let dt = 1;
333 let t_end = *ts.last().unwrap();
334 let mut idx = 1;
335 while dt * idx <= extra_time_s {
336 let dt_extra = (dt * idx) as f64;
337 ts.push(t_end + dt_extra);
338 vs.push(0.0);
339 gs.push(0.0);
340 rs.push(0.0);
341 idx += 1;
342 }
343 RustCycle {
344 time_s: Array::from_vec(ts),
345 mps: Array::from_vec(vs),
346 grade: Array::from_vec(gs),
347 road_type: Array::from_vec(rs),
348 name: cyc.name.clone(),
349 orphaned: false,
350 }
351}
352
353#[cfg(feature = "pyo3")]
354#[allow(unused)]
355pub fn register(_py: Python<'_>, m: &Bound<PyModule>) -> anyhow::Result<()> {
356 m.add_function(wrap_pyfunction!(calc_constant_jerk_trajectory, m)?)?;
357 m.add_function(wrap_pyfunction!(accel_for_constant_jerk, m)?)?;
358 m.add_function(wrap_pyfunction!(speed_for_constant_jerk, m)?)?;
359 m.add_function(wrap_pyfunction!(dist_for_constant_jerk, m)?)?;
360 Ok(())
361}
362
363#[derive(Default, PartialEq, Clone, Debug, Deserialize, Serialize)]
364pub struct RustCycleElement {
365 #[serde(alias = "cycSecs")]
367 pub time_s: f64,
368 #[serde(alias = "cycMps")]
370 pub mps: f64,
371 #[serde(alias = "cycGrade")]
373 pub grade: Option<f64>,
374 #[serde(alias = "cycRoadType")]
376 pub road_type: Option<f64>,
377}
378
379#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
380#[add_pyo3_api(
381 #[new]
382 pub fn __new__(
383 cyc: &RustCycle,
384 ) -> Self {
385 Self::new(cyc)
386 }
387)]
388pub struct RustCycleCache {
389 pub grade_all_zero: bool,
390 pub trapz_step_distances_m: Array1<f64>,
391 pub trapz_distances_m: Array1<f64>,
392 pub trapz_elevations_m: Array1<f64>,
393 pub stops: Array1<bool>,
394 interp_ds: Array1<f64>,
395 interp_is: Array1<f64>,
396 interp_hs: Array1<f64>,
397 grades: Array1<f64>,
398}
399
400impl SerdeAPI for RustCycleCache {}
401
402impl RustCycleCache {
403 pub fn new(cyc: &RustCycle) -> Self {
404 let tol = 1e-6;
405 let num_items = cyc.len();
406 let grade_all_zero = cyc.grade.iter().all(|g| *g == 0.0);
407 let trapz_step_distances_m = trapz_step_distances(cyc);
408 let trapz_distances_m = ndarrcumsum(&trapz_step_distances_m);
409 let trapz_elevations_m = if grade_all_zero {
410 Array::zeros(num_items)
411 } else {
412 let xs = Array::from_iter(
413 cyc.grade
414 .iter()
415 .zip(&trapz_step_distances_m)
416 .map(|(g, dd)| g.atan().cos() * dd * g),
417 );
418 ndarrcumsum(&xs)
419 };
420 let stops = Array::from_iter(cyc.mps.iter().map(|v| v <= &tol));
421 let mut interp_ds = Vec::with_capacity(num_items);
422 let mut interp_is = Vec::with_capacity(num_items);
423 let mut interp_hs = Vec::with_capacity(num_items);
424 for idx in 0..num_items {
425 let d = trapz_distances_m[idx];
426 if interp_ds.is_empty() || d > *interp_ds.last().unwrap() {
427 interp_ds.push(d);
428 interp_is.push(idx as f64);
429 interp_hs.push(trapz_elevations_m[idx]);
430 }
431 }
432 let interp_ds = Array::from_vec(interp_ds);
433 let interp_is = Array::from_vec(interp_is);
434 let interp_hs = Array::from_vec(interp_hs);
435 Self {
436 grade_all_zero,
437 trapz_step_distances_m,
438 trapz_distances_m,
439 trapz_elevations_m,
440 stops,
441 interp_ds,
442 interp_is,
443 interp_hs,
444 grades: cyc.grade.clone(),
445 }
446 }
447
448 pub fn interp_grade(&self, dist_m: f64) -> anyhow::Result<f64> {
451 let v = if self.grade_all_zero {
452 0.0
453 } else if dist_m <= self.interp_ds[0] {
454 self.grades[0]
455 } else if dist_m > *self.interp_ds.last().unwrap() {
456 *self.grades.last().unwrap()
457 } else {
458 let raw_idx = interpolate(&dist_m, &self.interp_ds, &self.interp_is, false)
459 .with_context(|| format_dbg!())?;
460 let idx = raw_idx.ceil() as usize;
461 self.grades[idx]
462 };
463 Ok(v)
464 }
465
466 pub fn interp_elevation(&self, dist_m: f64) -> anyhow::Result<f64> {
468 if self.grade_all_zero {
469 Ok(0.0)
470 } else {
471 interpolate(&dist_m, &self.interp_ds, &self.interp_hs, false)
472 .with_context(|| format_dbg!())
473 }
474 }
475}
476
477#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
478#[add_pyo3_api(
479 pub fn __len__(&self) -> usize {
480 self.len()
481 }
482
483 #[allow(clippy::type_complexity)]
484 pub fn __getnewargs__(&self) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>, &str) {
485 (self.time_s.to_vec(), self.mps.to_vec(), self.grade.to_vec(), self.road_type.to_vec(), &self.name)
486 }
487
488 #[staticmethod]
489 #[pyo3(name = "from_csv")]
490 #[pyo3(signature = (filepath, skip_init=None))]
491 pub fn from_csv_py(filepath: &Bound<PyAny>, skip_init: Option<bool>) -> anyhow::Result<Self> {
492 Self::from_csv_file(PathBuf::extract_bound(filepath)?, skip_init.unwrap_or_default())
493 }
494
495 pub fn to_rust(&self) -> Self {
496 self.clone()
497 }
498
499 #[staticmethod]
500 #[pyo3(signature = (dict, skip_init=None))]
501 pub fn from_dict(dict: &Bound<PyDict>, skip_init: Option<bool>) -> PyResult<Self> {
502 let time_s = Array::from_vec(dict.get_item("time_s")?.with_context(|| format_dbg!())?.extract()?);
503 let cyc_len = time_s.len();
504 let mut cyc = Self {
505 time_s,
506 mps: Array::from_vec(dict.get_item("mps")?.with_context(|| format_dbg!())?.extract()?),
507 grade: if let Ok(Some(item_res)) = dict.get_item("grade") {
508 if let Ok(grade) = item_res.extract() {
509 Array::from_vec(grade)
510 } else {
511 Array::default(cyc_len)
512 }
513 } else {
514 Array::default(cyc_len)
515 },
516 road_type: if let Ok(Some(item_res)) = dict.get_item("road_type") {
517 if let Ok(road_type) = item_res.extract() {
518 Array::from_vec(road_type)
519 } else {
520 Array::default(cyc_len)
521 }
522 } else {
523 Array::default(cyc_len)
524 },
525 name: if let Ok(Some(item_res)) = dict.get_item("name") {
526 String::extract_bound(&item_res).unwrap_or_default()
527 } else {
528 Default::default()
529 },
530 orphaned: false,
531 };
532 if !skip_init.unwrap_or_default() {
533 cyc.init()?;
534 }
535 Ok(cyc)
536 }
537
538 pub fn to_dict<'py>(&self, py: Python<'py>) -> anyhow::Result<Bound<'py, PyDict>> {
539 let dict = PyDict::new(py);
540 dict.set_item("time_s", self.time_s.to_vec())?;
541 dict.set_item("mps", self.mps.to_vec())?;
542 dict.set_item("grade", self.grade.to_vec())?;
543 dict.set_item("road_type", self.road_type.to_vec())?;
544 dict.set_item("name", self.name.clone())?;
545 Ok(dict)
546 }
547
548 #[pyo3(name = "to_csv")]
549 pub fn to_csv_py(&self) -> PyResult<String> {
550 self.to_csv().map_err(|e| PyIOError::new_err(format!("{:?}", e)))
551 }
552
553 #[pyo3(name = "modify_by_const_jerk_trajectory")]
554 pub fn modify_by_const_jerk_trajectory_py(
555 &mut self,
556 idx: usize,
557 n: usize,
558 jerk_m_per_s3: f64,
559 accel0_m_per_s2: f64,
560 ) -> f64 {
561 self.modify_by_const_jerk_trajectory(idx, n, jerk_m_per_s3, accel0_m_per_s2)
562 }
563
564 #[pyo3(name = "modify_with_braking_trajectory")]
565 #[pyo3(signature = (brake_accel_m_per_s2, idx, dts_m=None))]
566 pub fn modify_with_braking_trajectory_py(
567 &mut self,
568 brake_accel_m_per_s2: f64,
569 idx: usize,
570 dts_m: Option<f64>
571 ) -> anyhow::Result<(f64, usize)> {
572 self.modify_with_braking_trajectory(brake_accel_m_per_s2, idx, dts_m)
573 }
574
575 #[pyo3(name = "calc_distance_to_next_stop_from")]
576 pub fn calc_distance_to_next_stop_from_py(&self, distance_m: f64) -> f64 {
577 self.calc_distance_to_next_stop_from(distance_m, None)
578 }
579
580 #[pyo3(name = "average_grade_over_range")]
581 pub fn average_grade_over_range_py(
582 &self,
583 distance_start_m: f64,
584 delta_distance_m: f64,
585 ) -> anyhow::Result<f64> {
586 self.average_grade_over_range(distance_start_m, delta_distance_m, None)
587 }
588
589 #[pyo3(name = "build_cache")]
590 pub fn build_cache_py(&self) -> RustCycleCache {
591 self.build_cache()
592 }
593
594 #[pyo3(name = "dt_s_at_i")]
595 pub fn dt_s_at_i_py(&self, i: usize) -> f64 {
596 if i == 0 {
597 0.0
598 } else {
599 self.dt_s_at_i(i)
600 }
601 }
602
603 #[getter]
604 pub fn get_mph(&self) -> Vec<f64> {
605 (&self.mps * crate::params::MPH_PER_MPS).to_vec()
606 }
607 #[setter]
608 pub fn set_mph(&mut self, new_value: Vec<f64>) {
609 self.mps = Array::from_vec(new_value) / MPH_PER_MPS;
610 }
611 #[getter]
612 pub fn get_dt_s(&self) -> Vec<f64> {
614 self.dt_s().to_vec()
615 }
616 #[getter]
617 pub fn get_dist_m(&self) -> Vec<f64> {
619 self.dist_m().to_vec()
620 }
621 #[getter]
622 pub fn get_delta_elev_m(&self) -> Vec<f64> {
623 self.delta_elev_m().to_vec()
624 }
625
626 #[pyo3(name = "list_resources")]
627 pub fn list_resources_py(&self) -> Vec<String> {
629 RustCycle::list_resources()
630 }
631)]
632pub struct RustCycle {
640 #[serde(alias = "cycSecs")]
642 pub time_s: Array1<f64>,
643 #[serde(alias = "cycMps")]
645 pub mps: Array1<f64>,
646 #[serde(alias = "cycGrade")]
648 #[serde(default)]
649 pub grade: Array1<f64>,
650 #[serde(alias = "cycRoadType")]
652 #[serde(default)]
653 pub road_type: Array1<f64>,
654 pub name: String,
655 #[serde(skip)]
656 pub orphaned: bool,
657}
658
659impl SerdeAPI for RustCycle {
660 const ACCEPTED_BYTE_FORMATS: &'static [&'static str] = &["yaml", "json", "toml", "bin", "csv"];
661 const ACCEPTED_STR_FORMATS: &'static [&'static str] = &["yaml", "json", "toml", "csv"];
662 const RESOURCE_PREFIX: &'static str = "cycles";
663 const CACHE_FOLDER: &'static str = "cycles";
664
665 fn init(&mut self) -> anyhow::Result<()> {
666 self.init_checks()
667 }
668
669 fn to_writer<W: std::io::Write>(&self, mut wtr: W, format: &str) -> anyhow::Result<()> {
670 match format.trim_start_matches('.').to_lowercase().as_str() {
671 "yaml" | "yml" => serde_yaml::to_writer(wtr, self)?,
672 "json" => serde_json::to_writer(wtr, self)?,
673 "toml" => wtr.write_all(self.to_toml()?.as_bytes())?,
674 #[cfg(feature = "bincode")]
675 "bin" => bincode::serialize_into(wtr, self)?,
676 "csv" => {
677 let mut wtr = csv::Writer::from_writer(wtr);
678 for i in 0..self.len() {
679 wtr.serialize(RustCycleElement {
680 time_s: self.time_s[i],
681 mps: self.mps[i],
682 grade: Some(self.grade[i]),
683 road_type: Some(self.road_type[i]),
684 })?;
685 }
686 wtr.flush()?
687 }
688 _ => bail!(
689 "Unsupported format {format:?}, must be one of {:?}",
690 Self::ACCEPTED_BYTE_FORMATS
691 ),
692 }
693 Ok(())
694 }
695
696 fn to_str(&self, format: &str) -> anyhow::Result<String> {
697 Ok(
698 match format.trim_start_matches('.').to_lowercase().as_str() {
699 "yaml" | "yml" => self.to_yaml()?,
700 "json" => self.to_json()?,
701 "toml" => self.to_toml()?,
702 "csv" => self.to_csv()?,
703 _ => {
704 bail!(
705 "Unsupported format {format:?}, must be one of {:?}",
706 Self::ACCEPTED_STR_FORMATS
707 )
708 }
709 },
710 )
711 }
712
713 fn from_str<S: AsRef<str>>(contents: S, format: &str, skip_init: bool) -> anyhow::Result<Self> {
716 Ok(
717 match format.trim_start_matches('.').to_lowercase().as_str() {
718 "yaml" | "yml" => Self::from_yaml(contents, skip_init)?,
719 "json" => Self::from_json(contents, skip_init)?,
720 "toml" => Self::from_toml(contents, skip_init)?,
721 "csv" => Self::from_reader(contents.as_ref().as_bytes(), "csv", skip_init)?,
722 _ => bail!(
723 "Unsupported format {format:?}, must be one of {:?}",
724 Self::ACCEPTED_STR_FORMATS
725 ),
726 },
727 )
728 }
729
730 fn from_reader<R: std::io::Read>(
731 mut rdr: R,
732 format: &str,
733 skip_init: bool,
734 ) -> anyhow::Result<Self> {
735 let mut deserialized = match format.trim_start_matches('.').to_lowercase().as_str() {
736 "yaml" | "yml" => serde_yaml::from_reader(rdr)?,
737 "json" => serde_json::from_reader(rdr)?,
738 "toml" => {
739 let mut buf = String::new();
740 rdr.read_to_string(&mut buf)?;
741 Self::from_toml(buf, skip_init)?
742 }
743 #[cfg(feature = "bincode")]
744 "bin" => bincode::deserialize_from(rdr)?,
745 "csv" => {
746 let mut cyc = Self::default();
748 let mut rdr = csv::Reader::from_reader(rdr);
749 for result in rdr.deserialize() {
750 cyc.push(result?);
751 }
752 cyc
753 }
754 _ => {
755 bail!(
756 "Unsupported format {format:?}, must be one of {:?}",
757 Self::ACCEPTED_BYTE_FORMATS
758 )
759 }
760 };
761 if !skip_init {
762 deserialized.init()?;
763 }
764 Ok(deserialized)
765 }
766}
767
768impl TryFrom<HashMap<String, Vec<f64>>> for RustCycle {
769 type Error = anyhow::Error;
770
771 fn try_from(hashmap: HashMap<String, Vec<f64>>) -> anyhow::Result<Self> {
772 let time_s = Array::from_vec(
773 hashmap
774 .get("time_s")
775 .with_context(|| format!("`time_s` not in HashMap: {hashmap:?}"))?
776 .to_owned(),
777 );
778 let cyc_len = time_s.len();
779 let mut cyc = Self {
780 time_s,
781 mps: Array::from_vec(
782 hashmap
783 .get("mps")
784 .with_context(|| format!("`mps` not in HashMap: {hashmap:?}"))?
785 .to_owned(),
786 ),
787 grade: Array::from_vec(
788 hashmap
789 .get("grade")
790 .unwrap_or(&vec![0.0; cyc_len])
791 .to_owned(),
792 ),
793 road_type: Array::from_vec(
794 hashmap
795 .get("road_type")
796 .unwrap_or(&vec![0.0; cyc_len])
797 .to_owned(),
798 ),
799 name: String::default(),
800 orphaned: false,
801 };
802 cyc.init()?;
803 Ok(cyc)
804 }
805}
806
807impl From<RustCycle> for HashMap<String, Vec<f64>> {
808 fn from(cyc: RustCycle) -> Self {
809 HashMap::from([
810 ("time_s".into(), cyc.time_s.to_vec()),
811 ("mps".into(), cyc.mps.to_vec()),
812 ("grade".into(), cyc.grade.to_vec()),
813 ("road_type".into(), cyc.road_type.to_vec()),
814 ])
815 }
816}
817
818impl RustCycle {
820 fn init_checks(&self) -> anyhow::Result<()> {
821 ensure!(!self.is_empty(), "Deserialized cycle is empty");
822 ensure!(self.is_sorted(), "Deserialized cycle is not sorted in time");
823 ensure!(
824 self.are_fields_equal_length(),
825 "Deserialized cycle has unequal field lengths\ntime_s: {}\nmps: {}\ngrade: {}\nroad_type: {}",
826 self.time_s.len(),
827 self.mps.len(),
828 self.grade.len(),
829 self.road_type.len(),
830 );
831 Ok(())
832 }
833
834 pub fn from_csv_file<P: AsRef<Path>>(filepath: P, skip_init: bool) -> anyhow::Result<Self> {
836 let filepath = filepath.as_ref();
837 let name = filepath
838 .file_stem()
839 .and_then(OsStr::to_str)
840 .with_context(|| format!("Could not parse cycle name from filepath: {filepath:?}"))?
841 .to_string();
842 let mut cyc = Self::from_file(filepath, skip_init)?;
843 cyc.name = name;
844 Ok(cyc)
845 }
846
847 pub fn from_csv_str<S: AsRef<str>>(
849 csv_str: S,
850 name: String,
851 skip_init: bool,
852 ) -> anyhow::Result<Self> {
853 let mut cyc = Self::from_str(csv_str, "csv", skip_init)?;
854 cyc.name = name;
855 Ok(cyc)
856 }
857
858 pub fn to_csv(&self) -> anyhow::Result<String> {
860 let mut buf = Vec::with_capacity(self.len());
861 self.to_writer(&mut buf, "csv")?;
862 Ok(String::from_utf8(buf)?)
863 }
864
865 pub fn build_cache(&self) -> RustCycleCache {
866 RustCycleCache::new(self)
867 }
868
869 pub fn push(&mut self, cyc_elem: RustCycleElement) {
870 self.time_s
871 .append(Axis(0), array![cyc_elem.time_s].view())
872 .unwrap();
873 self.mps
874 .append(Axis(0), array![cyc_elem.mps].view())
875 .unwrap();
876 if let Some(grade) = cyc_elem.grade {
877 self.grade.append(Axis(0), array![grade].view()).unwrap();
878 }
879 if let Some(road_type) = cyc_elem.road_type {
880 self.road_type
881 .append(Axis(0), array![road_type].view())
882 .unwrap();
883 }
884 }
885
886 pub fn len(&self) -> usize {
887 self.time_s.len()
888 }
889
890 pub fn is_empty(&self) -> bool {
891 self.len() == 0
892 }
893
894 pub fn is_sorted(&self) -> bool {
895 self.time_s
896 .as_slice()
897 .unwrap()
898 .windows(2)
899 .all(|window| window[0] < window[1])
900 }
901
902 pub fn are_fields_equal_length(&self) -> bool {
903 let cyc_len = self.len();
904 [self.mps.len(), self.grade.len(), self.road_type.len()]
905 .iter()
906 .all(|len| len == &cyc_len)
907 }
908
909 pub fn test_cyc() -> Self {
910 Self {
911 time_s: Array::range(0.0, 10.0, 1.0),
912 mps: Array::range(0.0, 10.0, 1.0),
913 grade: Array::zeros(10),
914 road_type: Array::zeros(10),
915 name: String::from("test"),
916 orphaned: false,
917 }
918 }
919
920 pub fn average_grade_over_range(
928 &self,
929 distance_start_m: f64,
930 delta_distance_m: f64,
931 cache: Option<&RustCycleCache>,
932 ) -> anyhow::Result<f64> {
933 let tol = 1e-6;
934 match &cache {
935 Some(rcc) => {
936 let v = if rcc.grade_all_zero {
937 0.0
938 } else if delta_distance_m <= tol {
939 rcc.interp_grade(distance_start_m)?
940 } else {
941 let e0 = rcc.interp_elevation(distance_start_m)?;
942 let e1 = rcc.interp_elevation(distance_start_m + delta_distance_m)?;
943 ((e1 - e0) / delta_distance_m).asin().tan()
944 };
945 Ok(v)
946 }
947 None => {
948 let grade_all_zero = {
949 let mut all0 = true;
950 for idx in 0..self.len() {
951 if self.grade[idx] != 0.0 {
952 all0 = false;
953 break;
954 }
955 }
956 all0
957 };
958 let v = if grade_all_zero {
959 0.0
960 } else {
961 let delta_dists = trapz_step_distances(self);
962 let trapz_distances_m = ndarrcumsum(&delta_dists);
963 if delta_distance_m <= tol {
964 if distance_start_m <= trapz_distances_m[0] {
965 return Ok(self.grade[0]);
966 }
967 let max_idx = self.len() - 1;
968 if distance_start_m > trapz_distances_m[max_idx] {
969 return Ok(self.grade[max_idx]);
970 }
971 for idx in 1..self.time_s.len() {
972 if distance_start_m > trapz_distances_m[idx - 1]
973 && distance_start_m <= trapz_distances_m[idx]
974 {
975 return Ok(self.grade[idx]);
976 }
977 }
978 self.grade[max_idx]
979 } else {
980 let trapz_elevations_m = ndarrcumsum(
984 &(self.grade.mapv(|g| g.atan().cos()) * delta_dists * &self.grade),
985 );
986 let e0 = interpolate(
987 &distance_start_m,
988 &trapz_distances_m,
989 &trapz_elevations_m,
990 false,
991 )
992 .with_context(|| format_dbg!())?;
993 let e1 = interpolate(
994 &(distance_start_m + delta_distance_m),
995 &trapz_distances_m,
996 &trapz_elevations_m,
997 false,
998 )
999 .with_context(|| format_dbg!())?;
1000 ((e1 - e0) / delta_distance_m).asin().tan()
1001 }
1002 };
1003 Ok(v)
1004 }
1005 }
1006 }
1007
1008 pub fn calc_distance_to_next_stop_from(
1013 &self,
1014 distance_m: f64,
1015 cache: Option<&RustCycleCache>,
1016 ) -> f64 {
1017 let tol = 1e-6;
1018 match cache {
1019 Some(rcc) => {
1020 for (&dist, &v) in rcc.trapz_distances_m.iter().zip(self.mps.iter()) {
1021 if (v < tol) && (dist > (distance_m + tol)) {
1022 return dist - distance_m;
1023 }
1024 }
1025 rcc.trapz_distances_m.last().unwrap() - distance_m
1026 }
1027 None => {
1028 let ds = ndarrcumsum(&trapz_step_distances(self));
1029 for (&dist, &v) in ds.iter().zip(self.mps.iter()) {
1030 if (v < tol) && (dist > (distance_m + tol)) {
1031 return dist - distance_m;
1032 }
1033 }
1034 ds.last().unwrap() - distance_m
1035 }
1036 }
1037 }
1038
1039 pub fn modify_by_const_jerk_trajectory(
1051 &mut self,
1052 i: usize,
1053 n: usize,
1054 jerk_m_per_s3: f64,
1055 accel0_m_per_s2: f64,
1056 ) -> f64 {
1057 if n == 0 {
1058 return 0.0;
1059 }
1060 let num_samples = self.mps.len();
1061 if i >= num_samples {
1062 if num_samples > 0 {
1063 return self.mps[num_samples - 1];
1064 }
1065 return 0.0;
1066 }
1067 let v0 = self.mps[i - 1];
1068 let dt = self.dt_s_at_i(i);
1069 let mut v = v0;
1070 for ni in 1..(n + 1) {
1071 let idx_to_set = (i - 1) + ni;
1072 if idx_to_set >= num_samples {
1073 break;
1074 }
1075 v = speed_for_constant_jerk(ni, v0, accel0_m_per_s2, jerk_m_per_s3, dt);
1076 self.mps[idx_to_set] = max(v, 0.0);
1077 }
1078 v
1079 }
1080
1081 pub fn modify_with_braking_trajectory(
1092 &mut self,
1093 brake_accel_m_per_s2: f64,
1094 i: usize,
1095 dts_m: Option<f64>,
1096 ) -> anyhow::Result<(f64, usize)> {
1097 ensure!(brake_accel_m_per_s2 < 0.0);
1098 if i >= self.time_s.len() {
1099 return Ok((*self.mps.last().unwrap(), 0));
1100 }
1101 let v0 = self.mps[i - 1];
1102 let dt = self.dt_s_at_i(i);
1103 let dts_m = match dts_m {
1105 Some(value) => {
1106 if value > 0.0 {
1107 value
1108 } else {
1109 -0.5 * v0 * v0 / brake_accel_m_per_s2
1110 }
1111 }
1112 None => -0.5 * v0 * v0 / brake_accel_m_per_s2,
1113 };
1114 if dts_m <= 0.0 {
1115 return Ok((v0, 0));
1116 }
1117 let tts_s = -v0 / brake_accel_m_per_s2;
1119 let n = (tts_s / dt).round() as usize;
1121 let n = if n < 2 { 2 } else { n }; let (jerk_m_per_s3, accel_m_per_s2) =
1123 calc_constant_jerk_trajectory(n, 0.0, v0, dts_m, 0.0, dt)?;
1124 Ok((
1125 self.modify_by_const_jerk_trajectory(i, n, jerk_m_per_s3, accel_m_per_s2),
1126 n,
1127 ))
1128 }
1129
1130 pub fn dt_s(&self) -> Array1<f64> {
1132 diff(&self.time_s)
1133 }
1134
1135 pub fn dt_s_at_i(&self, i: usize) -> f64 {
1137 self.time_s[i] - self.time_s[i - 1]
1138 }
1139
1140 pub fn dist_m(&self) -> Array1<f64> {
1142 &self.mps * self.dt_s()
1143 }
1144
1145 pub fn mph_at_i(&self, i: usize) -> f64 {
1147 self.mps[i] * MPH_PER_MPS
1148 }
1149
1150 pub fn delta_elev_m(&self) -> Array1<f64> {
1152 ndarrcumsum(&(self.dist_m() * &self.grade))
1153 }
1154}
1155
1156pub struct PassingInfo {
1157 pub has_collision: bool,
1159 pub idx: usize,
1161 pub num_steps: usize,
1163 pub start_distance_m: f64,
1165 pub distance_m: f64,
1167 pub start_speed_m_per_s: f64,
1169 pub speed_m_per_s: f64,
1171 pub time_step_duration_s: f64,
1173}
1174
1175pub fn detect_passing(
1184 cyc: &RustCycle,
1185 cyc0: &RustCycle,
1186 i: usize,
1187 dist_tol_m: Option<f64>,
1188) -> PassingInfo {
1189 if i >= cyc.len() {
1190 return PassingInfo {
1191 has_collision: false,
1192 idx: 0,
1193 num_steps: 0,
1194 start_distance_m: 0.0,
1195 distance_m: 0.0,
1196 start_speed_m_per_s: 0.0,
1197 speed_m_per_s: 0.0,
1198 time_step_duration_s: 1.0,
1199 };
1200 }
1201 let zero_speed_tol_m_per_s = 1e-6;
1202 let dist_tol_m = dist_tol_m.unwrap_or(0.1);
1203 let mut v0 = cyc.mps[i - 1];
1204 let d0 = trapz_step_start_distance(cyc, i);
1205 let mut v0_lv = cyc0.mps[i - 1];
1206 let d0_lv = trapz_step_start_distance(cyc0, i);
1207 let mut d = d0;
1208 let mut d_lv = d0_lv;
1209 let mut rendezvous_idx = None;
1210 let mut rendezvous_num_steps = 0;
1211 let mut rendezvous_distance_m = 0.0;
1212 let mut rendezvous_speed_m_per_s = 0.0;
1213 for di in 0..(cyc.mps.len() - i) {
1214 let idx = i + di;
1215 let v = cyc.mps[idx];
1216 let v_lv = cyc0.mps[idx];
1217 let vavg = (v + v0) * 0.5;
1218 let vavg_lv = (v_lv + v0_lv) * 0.5;
1219 let dd = vavg * cyc.dt_s_at_i(idx);
1220 let dd_lv = vavg_lv * cyc0.dt_s_at_i(idx);
1221 d += dd;
1222 d_lv += dd_lv;
1223 let dtlv = d_lv - d;
1224 v0 = v;
1225 v0_lv = v_lv;
1226 if di > 0 && dtlv < -dist_tol_m {
1227 rendezvous_idx = Some(idx);
1228 rendezvous_num_steps = di + 1;
1229 rendezvous_distance_m = d_lv;
1230 rendezvous_speed_m_per_s = v_lv;
1231 break;
1232 }
1233 if v <= zero_speed_tol_m_per_s {
1234 break;
1235 }
1236 }
1237 PassingInfo {
1238 has_collision: rendezvous_idx.is_some(),
1239 idx: rendezvous_idx.unwrap_or(0),
1240 num_steps: rendezvous_num_steps,
1241 start_distance_m: d0,
1242 distance_m: rendezvous_distance_m,
1243 start_speed_m_per_s: cyc.mps[i - 1],
1244 speed_m_per_s: rendezvous_speed_m_per_s,
1245 time_step_duration_s: cyc.dt_s_at_i(i),
1246 }
1247}
1248
1249#[cfg(test)]
1250mod tests {
1251 use super::*;
1252
1253 #[test]
1254 fn test_dist() {
1255 let cyc = RustCycle::test_cyc();
1256 assert_eq!(cyc.dist_m().sum(), 45.0);
1257 }
1258
1259 #[test]
1260 fn test_average_speeds_and_distances() {
1261 let cyc = RustCycle {
1262 time_s: array![0.0, 10.0, 30.0, 34.0, 40.0],
1263 mps: array![0.0, 10.0, 10.0, 0.0, 0.0],
1264 grade: Array::zeros(5),
1265 road_type: Array::zeros(5),
1266 name: String::from("test"),
1267 orphaned: false,
1268 };
1269 let avg_mps = average_step_speeds(&cyc);
1270 let expected_avg_mps = Array::from_vec(vec![0.0, 5.0, 10.0, 5.0, 0.0]);
1271 assert_eq!(expected_avg_mps.len(), avg_mps.len());
1272 for (expected, actual) in expected_avg_mps.iter().zip(avg_mps.iter()) {
1273 assert_eq!(expected, actual);
1274 }
1275 let dist_m = trapz_step_distances(&cyc);
1276 let expected_dist_m = Array::from_vec(vec![0.0, 50.0, 200.0, 20.0, 0.0]);
1277 assert_eq!(expected_dist_m.len(), dist_m.len());
1278 for (expected, actual) in expected_dist_m.iter().zip(dist_m.iter()) {
1279 assert_eq!(expected, actual);
1280 }
1281 }
1282
1283 #[test]
1284 fn test_loading_a_cycle_from_the_filesystem() {
1285 let cyc_file_path = resources_path().join("cycles/udds.csv");
1286 let expected_udds_length = 1370;
1287 let cyc = RustCycle::from_csv_file(cyc_file_path, false).unwrap();
1288 let num_entries = cyc.len();
1289 assert_eq!(cyc.name, String::from("udds"));
1290 assert!(num_entries > 0);
1291 assert_eq!(num_entries, cyc.len());
1292 assert_eq!(num_entries, cyc.mps.len());
1293 assert_eq!(num_entries, cyc.grade.len());
1294 assert_eq!(num_entries, cyc.road_type.len());
1295 assert_eq!(num_entries, expected_udds_length);
1296 }
1297
1298 #[test]
1299 fn test_str_serde() {
1300 let cyc = RustCycle::test_cyc();
1301 for format in RustCycle::ACCEPTED_STR_FORMATS {
1302 let csv_str = cyc.to_str(format).unwrap();
1303 RustCycle::from_str(&csv_str, format, false).unwrap();
1304 }
1305 }
1306}