1#[cfg(feature = "default")]
4use curl::easy::Easy;
5#[cfg(feature = "default")]
6use directories::ProjectDirs;
7use itertools::Itertools;
8use lazy_static::lazy_static;
9use ndarray::*;
10use regex::Regex;
11use std::collections::HashSet;
12#[cfg(feature = "default")]
13use std::io::Write;
14
15use crate::imports::*;
16#[cfg(feature = "pyo3")]
17use crate::pyo3imports::*;
18
19#[cfg(test)]
20pub fn resources_path() -> PathBuf {
21 let pb = PathBuf::from("../../python/fastsim/resources");
22 assert!(pb.exists());
23 pb
24}
25
26pub const NESTED_STRUCT_ERR: &str = "Setting field value on nested struct not allowed.
28Assign nested struct to own variable, run the `reset_orphaned` method, and then
29modify field value. Then set the nested struct back inside containing struct.";
30
31pub fn diff(x: &Array1<f64>) -> Array1<f64> {
32 concatenate(
33 Axis(0),
34 &[
35 array![0.0].view(),
36 (&x.slice(s![1..]) - &x.slice(s![..-1])).view(),
37 ],
38 )
39 .unwrap()
40}
41
42pub fn add_from(xs: &Array1<f64>, i: usize, val: f64) -> Array1<f64> {
44 let mut ys = Array1::zeros(xs.len());
45 for idx in 0..xs.len() {
46 if idx >= i {
47 ys[idx] = xs[idx] + val;
48 } else {
49 ys[idx] = xs[idx];
50 }
51 }
52 ys
53}
54
55pub fn first_grtr(arr: &[f64], cut: f64) -> Option<usize> {
57 let len = arr.len();
58 if len == 0 {
59 return None;
60 }
61 Some(arr.iter().position(|&x| x > cut).unwrap_or(len - 1)) }
63
64pub fn first_eq(arr: &[f64], cut: f64) -> Option<usize> {
66 let len = arr.len();
67 if len == 0 {
68 return None;
69 }
70 Some(arr.iter().position(|&x| x == cut).unwrap_or(len - 1)) }
72
73pub fn max(a: f64, b: f64) -> f64 {
75 a.max(b)
76}
77
78pub fn min(a: f64, b: f64) -> f64 {
80 a.min(b)
81}
82
83pub fn arrmax(arr: &[f64]) -> f64 {
85 arr.iter().copied().fold(f64::NAN, f64::max)
86}
87
88pub fn arrmin(arr: &[f64]) -> f64 {
90 arr.iter().copied().fold(f64::NAN, f64::min)
91}
92
93pub fn ndarrallzeros(arr: &Array1<f64>) -> bool {
95 arr.iter().all(|x| *x == 0.0)
96}
97
98pub fn ndarrcumsum(arr: &Array1<f64>) -> Array1<f64> {
100 arr.iter()
101 .scan(0.0, |acc, &x| {
102 *acc += x;
103 Some(*acc)
104 })
105 .collect()
106}
107
108pub fn ndarrunique(arr: &Array1<f64>) -> Array1<f64> {
110 let mut set = HashSet::new();
111 let mut new_arr = Vec::new();
112 let x_min = arr.min().unwrap();
113 let x_max = arr.max().unwrap();
114 let dx = if x_max == x_min { 1.0 } else { x_max - x_min };
115 for &x in arr.iter() {
116 let y = (((x - x_min) / dx) * (usize::MAX as f64)) as usize;
117 if !set.contains(&y) {
118 new_arr.push(x);
119 set.insert(y);
120 }
121 }
122 Array1::from_vec(new_arr)
123}
124
125pub fn interpolate(
129 x: &f64,
130 x_data_in: &Array1<f64>,
131 y_data_in: &Array1<f64>,
132 extrapolate: bool,
133) -> anyhow::Result<f64> {
134 ensure!(x_data_in.len() == y_data_in.len());
135 let mut new_x_data = Vec::new();
136 let mut new_y_data = Vec::new();
137 let mut last_x = x_data_in[0];
138 for idx in 0..x_data_in.len() {
139 if idx == 0 || (idx > 0 && x_data_in[idx] > last_x) {
140 last_x = x_data_in[idx];
141 new_x_data.push(x_data_in[idx]);
142 new_y_data.push(y_data_in[idx]);
143 }
144 }
145 let x_data = Array1::from_vec(new_x_data);
146 let y_data = Array1::from_vec(new_y_data);
147 let size = x_data.len();
148
149 let mut i = 0;
150 if x >= &x_data[size - 2] {
151 i = size - 2;
152 } else {
153 while x > &x_data[i + 1] {
154 i += 1;
155 }
156 }
157 let xl = &x_data[i];
158 let mut yl = &y_data[i];
159 let xr = &x_data[i + 1];
160 let mut yr = &y_data[i + 1];
161 if !extrapolate {
162 if x < xl {
163 yr = yl;
164 }
165 if x > xr {
166 yl = yr;
167 }
168 }
169 let dydx = (yr - yl) / (xr - xl);
170 Ok(yl + dydx * (x - xl))
171}
172
173pub fn interpolate_vectors(
177 x: &f64,
178 x_data_in: &Vec<f64>,
179 y_data_in: &Vec<f64>,
180 extrapolate: bool,
181) -> f64 {
182 assert!(x_data_in.len() == y_data_in.len());
183 let mut new_x_data = Vec::new();
184 let mut new_y_data = Vec::new();
185 let mut last_x = x_data_in[0];
186 for idx in 0..x_data_in.len() {
187 if idx == 0 || (idx > 0 && x_data_in[idx] > last_x) {
188 last_x = x_data_in[idx];
189 new_x_data.push(x_data_in[idx]);
190 new_y_data.push(y_data_in[idx]);
191 }
192 }
193 let x_data = new_x_data;
194 let y_data = new_y_data;
195 let size = x_data.len();
196
197 let mut i = 0;
198 if x >= &x_data[size - 2] {
199 i = size - 2;
200 } else {
201 while x > &x_data[i + 1] {
202 i += 1;
203 }
204 }
205 let xl = &x_data[i];
206 let mut yl = &y_data[i];
207 let xr = &x_data[i + 1];
208 let mut yr = &y_data[i + 1];
209 if !extrapolate {
210 if x < xl {
211 yr = yl;
212 }
213 if x > xr {
214 yl = yr;
215 }
216 }
217 let dydx = (yr - yl) / (xr - xl);
218 yl + dydx * (x - xl)
219}
220
221pub fn get_index_permutations(shape: &[usize]) -> Vec<Vec<usize>> {
253 if shape.is_empty() {
254 return vec![vec![]];
255 }
256 shape
257 .iter()
258 .map(|&len| 0..len)
259 .multi_cartesian_product()
260 .collect()
261}
262
263pub fn multilinear(point: &[f64], grid: &[Vec<f64>], values: &ArrayD<f64>) -> anyhow::Result<f64> {
387 let mut n = values.ndim();
389
390 anyhow::ensure!(
392 point.len() == n,
393 "Length of supplied `point` must be same as `values` dimensionality: {point:?} is not {n}-dimensional",
394 );
395 anyhow::ensure!(
396 grid.len() == n,
397 "Length of supplied `grid` must be same as `values` dimensionality: {grid:?} is not {n}-dimensional",
398 );
399 for i in 0..n {
400 anyhow::ensure!(
401 grid[i].len() == values.shape()[i],
402 "Supplied `grid` and `values` are not compatible shapes: dimension {i}, lengths {} != {}",
403 grid[i].len(),
404 values.shape()[i]
405 );
406 anyhow::ensure!(
407 grid[i].windows(2).all(|w| w[0] < w[1]),
408 "Supplied `grid` coordinates must be sorted and non-repeating: dimension {i}, {:?}",
409 grid[i]
410 );
411 anyhow::ensure!(
412 grid[i][0] <= point[i] && point[i] <= *grid[i].last().unwrap(),
413 "Supplied `point` must be within `grid` for dimension {i}: point[{i}] = {:?}, grid[{i}] = {:?}",
414 point[i],
415 grid[i],
416 );
417 }
418
419 let mut point = point.to_vec();
423 let mut grid = grid.to_vec();
424 let mut values_view = values.view();
425 for dim in (0..n).rev() {
426 if let Some(pos) = grid[dim]
428 .iter()
429 .position(|&grid_point| grid_point == point[dim])
430 {
431 point.remove(dim);
432 grid.remove(dim);
433 values_view.index_axis_inplace(Axis(dim), pos);
434 }
435 }
436 if values_view.len() == 1 {
437 return Ok(*values_view.first().unwrap());
439 }
440 n = values_view.ndim();
442
443 let mut lower_idxs = Vec::with_capacity(n);
446 let mut interp_diffs = Vec::with_capacity(n);
447 for dim in 0..n {
448 let lower_idx = grid[dim]
449 .windows(2)
450 .position(|w| w[0] < point[dim] && point[dim] < w[1])
451 .unwrap();
452 let interp_diff =
453 (point[dim] - grid[dim][lower_idx]) / (grid[dim][lower_idx + 1] - grid[dim][lower_idx]);
454 lower_idxs.push(lower_idx);
455 interp_diffs.push(interp_diff);
456 }
457 let mut interp_vals = values_view
461 .slice_each_axis(|ax| {
462 let lower = lower_idxs[ax.axis.0];
463 Slice::from(lower..=lower + 1)
464 })
465 .to_owned();
466 let mut index_permutations = get_index_permutations(interp_vals.shape());
467 for (dim, diff) in interp_diffs.iter().enumerate() {
471 let next_dim = n - 1 - dim;
472 let next_shape = vec![2; next_dim];
473 let next_idxs = get_index_permutations(&next_shape);
476 let mut intermediate_arr = Array::default(next_shape);
477 for i in 0..next_idxs.len() {
478 let l = index_permutations[i].as_slice();
480 let u = index_permutations[next_idxs.len() + i].as_slice();
481 if dim == 0 {
482 anyhow::ensure!(
483 !interp_vals[l].is_nan() && !interp_vals[u].is_nan(),
484 "Surrounding value(s) cannot be NaN:\npoint = {point:?},\ngrid = {grid:?},\nvalues = {values:?}"
485 );
486 }
487 intermediate_arr[next_idxs[i].as_slice()] =
490 interp_vals[l] * (1.0 - diff) + interp_vals[u] * diff;
491 }
492 index_permutations = next_idxs;
493 interp_vals = intermediate_arr;
494 }
495
496 Ok(*interp_vals.first().unwrap())
498}
499
500lazy_static! {
501 static ref TIRE_CODE_REGEX: Regex = Regex::new(
502 r"(?i)[P|LT|ST|T]?((?:[0-9]{2,3}\.)?[0-9]+)/((?:[0-9]{1,2}\.)?[0-9]+) ?[B|D|R]?[x|\-| ]?((?:[0-9]{1,2}\.)?[0-9]+)[A|B|C|D|E|F|G|H|J|L|M|N]?"
503 ).unwrap();
504}
505
506pub fn tire_code_to_radius<S: AsRef<str>>(tire_code: S) -> anyhow::Result<f64> {
531 let tire_code = tire_code.as_ref();
532 let captures = TIRE_CODE_REGEX.captures(tire_code).with_context(|| {
533 format!(
534 "Regex pattern does not match for {:?}: {:?}",
535 tire_code,
536 TIRE_CODE_REGEX.as_str(),
537 )
538 })?;
539 let width_mm: f64 = captures[1].parse()?;
540 let aspect_ratio: f64 = captures[2].parse()?;
541 let rim_diameter_in: f64 = captures[3].parse()?;
542
543 let sidewall_height_mm = width_mm * aspect_ratio / 100.0;
544 let radius_mm = (rim_diameter_in * 25.4) / 2.0 + sidewall_height_mm;
545
546 Ok(radius_mm / 1000.0)
547}
548
549#[cfg(feature = "default")]
551pub fn download_file_from_url(url: &str, file_path: &Path) -> anyhow::Result<()> {
552 let mut handle = Easy::new();
553 handle.follow_location(true)?;
554 handle.url(url)?;
555 let mut buffer = Vec::new();
556 {
557 let mut transfer = handle.transfer();
558 transfer.write_function(|data| {
559 buffer.extend_from_slice(data);
560 Ok(data.len())
561 })?;
562 let result = transfer.perform();
563 if result.is_err() {
564 return Err(anyhow!("Could not download from {}", url));
565 }
566 }
567 println!("Downloaded data from {}; bytes: {}", url, buffer.len());
568 if buffer.is_empty() {
569 return Err(anyhow!("No data available from {url}"));
570 }
571 {
572 let mut file = match File::create(file_path) {
573 Err(why) => {
574 return Err(anyhow!(
575 "couldn't open {}: {}",
576 file_path.to_str().unwrap(),
577 why
578 ))
579 }
580 Ok(file) => file,
581 };
582 file.write_all(buffer.as_slice())?;
583 }
584 Ok(())
585}
586
587#[cfg(feature = "default")]
589pub fn create_project_subdir<P: AsRef<Path>>(subpath: P) -> anyhow::Result<PathBuf> {
590 let proj_dirs = ProjectDirs::from("gov", "NREL", "fastsim").ok_or_else(|| {
591 anyhow!("Could not build path to project directory: \"gov.NREL.fastsim\"")
592 })?;
593 let path = PathBuf::from(proj_dirs.config_dir()).join(subpath);
594 std::fs::create_dir_all(path.as_path())?;
595 Ok(path)
596}
597
598#[cfg(feature = "default")]
600pub fn path_to_cache() -> anyhow::Result<PathBuf> {
601 let proj_dirs = ProjectDirs::from("gov", "NREL", "fastsim").ok_or_else(|| {
602 anyhow!("Could not build path to project directory: \"gov.NREL.fastsim\"")
603 })?;
604 Ok(PathBuf::from(proj_dirs.config_dir()))
605}
606
607#[cfg(feature = "default")]
622pub fn clear_cache<P: AsRef<Path>>(subpath: P) -> anyhow::Result<()> {
623 let path = path_to_cache()?.join(subpath);
624 Ok(std::fs::remove_dir_all(path)?)
625}
626
627#[cfg(feature = "default")]
641pub fn url_to_cache<S: AsRef<str>, P: AsRef<Path>>(url: S, subpath: P) -> anyhow::Result<()> {
642 let url = url::Url::parse(url.as_ref())?;
643 let file_name = url
644 .path_segments()
645 .and_then(|segments| segments.last())
646 .with_context(|| "Could not parse filename from URL: {url:?}")?;
647 let data_subdirectory = create_project_subdir(subpath)
648 .with_context(|| "Could not find or build Fastsim data subdirectory.")?;
649 let file_path = data_subdirectory.join(file_name);
650 download_file_from_url(url.as_ref(), &file_path)?;
651 Ok(())
652}
653
654#[cfg(feature = "pyo3")]
655pub mod array_wrappers {
656 use crate::proc_macros::add_pyo3_api;
657
658 use super::*;
659 #[add_pyo3_api]
661 #[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
662 pub struct Pyo3ArrayU32(Array1<u32>);
663 impl SerdeAPI for Pyo3ArrayU32 {}
664
665 #[add_pyo3_api]
667 #[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
668 pub struct Pyo3ArrayI32(Array1<i32>);
669 impl SerdeAPI for Pyo3ArrayI32 {}
670
671 #[add_pyo3_api]
673 #[derive(Default, Serialize, Deserialize, Clone, PartialEq)]
674 pub struct Pyo3ArrayF64(Array1<f64>);
675 impl SerdeAPI for Pyo3ArrayF64 {}
676
677 #[add_pyo3_api]
679 #[derive(Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
680 pub struct Pyo3ArrayBool(Array1<bool>);
681 impl SerdeAPI for Pyo3ArrayBool {}
682
683 #[add_pyo3_api]
685 #[derive(Default, Serialize, Deserialize, Clone, PartialEq)]
686 pub struct Pyo3VecF64(Vec<f64>);
687
688 impl SerdeAPI for Pyo3VecF64 {}
689}
690
691#[cfg(feature = "pyo3")]
692pub use array_wrappers::*;
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697 use crate::vehicle_utils::NETWORK_TEST_DISABLE_ENV_VAR_NAME;
698 use std::env;
699
700 #[test]
701 fn test_diff() {
702 assert_eq!(diff(&Array1::range(0.0, 3.0, 1.0)), array![0.0, 1.0, 1.0]);
703 }
704
705 #[test]
706 fn test_that_first_eq_finds_the_right_index_when_one_exists() {
707 let xs = [0.0, 1.2, 3.3, 4.4, 6.6];
708 let idx = first_eq(&xs, 3.3).unwrap();
709 let expected_idx = 2;
710 assert_eq!(idx, expected_idx)
711 }
712
713 #[test]
714 fn test_that_first_eq_yields_last_index_when_nothing_found() {
715 let xs = [0.0, 1.2, 3.3, 4.4, 6.6];
716 let idx = first_eq(&xs, 7.0).unwrap();
717 let expected_idx = xs.len() - 1;
718 assert_eq!(idx, expected_idx)
719 }
720
721 #[test]
722 fn test_that_first_grtr_finds_the_right_index_when_one_exists() {
723 let xs = [0.0, 1.2, 3.3, 4.4, 6.6];
724 let idx = first_grtr(&xs, 3.0).unwrap();
725 let expected_idx = 2;
726 assert_eq!(idx, expected_idx)
727 }
728
729 #[test]
730 fn test_that_first_grtr_yields_last_index_when_nothing_found() {
731 let xs = [0.0, 1.2, 3.3, 4.4, 6.6];
732 let idx = first_grtr(&xs, 7.0).unwrap();
733 let expected_idx = xs.len() - 1;
734 assert_eq!(idx, expected_idx)
735 }
736
737 #[test]
738 fn test_ndarrcumsum_expected_output() {
739 let xs = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
740 let expected_ys = Array1::from_vec(vec![0.0, 1.0, 3.0, 6.0]);
741 let ys = ndarrcumsum(&xs);
742 for (i, (ye, y)) in expected_ys.iter().zip(ys.iter()).enumerate() {
743 assert_eq!(ye, y, "unequal at {}", i);
744 }
745 }
746
747 #[test]
748 fn test_add_from_yields_expected_output() {
749 let xs = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
750 let mut expected_ys = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
751 let mut actual_ys = add_from(&xs, 100, 1.0);
752 assert_eq!(expected_ys.len(), actual_ys.len());
753 assert_eq!(expected_ys, actual_ys);
754 expected_ys = Array1::from_vec(vec![1.0, 2.0, 4.0, 5.0, 6.0]);
755 actual_ys = add_from(&xs, 2, 1.0);
756 assert_eq!(expected_ys.len(), actual_ys.len());
757 assert_eq!(expected_ys, actual_ys);
758 }
759
760 #[test]
761 fn test_ndarrunique_works() {
762 let xs = Array1::from_vec(vec![0.0, 1.0, 1.0, 2.0, 10.0, 10.0, 11.0]);
763 let expected = Array1::from_vec(vec![0.0, 1.0, 2.0, 10.0, 11.0]);
764 let actual = ndarrunique(&xs);
765 assert_eq!(expected.len(), actual.len());
766 for (ex, act) in expected.iter().zip(actual.iter()) {
767 assert_eq!(ex, act);
768 }
769 }
770 #[test]
781 fn test_that_interpolation_works() {
782 let xs = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
783 let ys = Array1::from_vec(vec![0.0, 10.0, 20.0, 30.0, 40.0]);
784 let x = 0.5;
785 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
786 let expected_y_lookup = 5.0;
787 assert_eq!(expected_y_lookup, y_lookup);
788 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
789 assert_eq!(expected_y_lookup, y_lookup);
790 }
791
792 #[test]
793 fn test_that_interpolation_works_for_irrational_number() {
794 let xs = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
795 let ys = Array1::from_vec(vec![0.0, 10.0, 20.0, 30.0, 40.0]);
796 let x = 1.0 / 3.0;
797 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
798 let expected_y_lookup = 3.3333333333;
799 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
800 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
801 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
802 }
803
804 #[test]
805 fn test_interpolate_with_small_vectors() {
806 let xs = Array1::from_vec(vec![0.0, 1.0]);
807 let ys = Array1::from_vec(vec![0.0, 10.0]);
808 let x = 0.5;
809 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
810 let expected_y_lookup = 5.0;
811 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
812 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
813 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
814 }
815
816 #[test]
817 fn test_interpolate_when_lookup_is_at_end() {
818 let xs = Array1::from_vec(vec![0.0, 1.0]);
819 let ys = Array1::from_vec(vec![0.0, 10.0]);
820 let x = 1.0;
821 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
822 let expected_y_lookup = 10.0;
823 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
824 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
825 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
826 }
827
828 #[test]
829 fn test_interpolate_when_lookup_is_past_end_without_extrapolate() {
830 let xs = Array1::from_vec(vec![0.0, 1.0]);
831 let ys = Array1::from_vec(vec![0.0, 10.0]);
832 let x = 1.01;
833 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
834 let expected_y_lookup = 10.0;
835 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
836 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
837 assert!((expected_y_lookup - y_lookup).abs() < 1e-6);
838 }
839
840 #[test]
841 fn test_interpolate_with_x_data_that_repeats() {
842 let xs = Array1::from_vec(vec![0.0, 1.0, 1.0]);
843 let ys = Array1::from_vec(vec![0.0, 10.0, 10.0]);
844 let x = 1.0;
845 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
846 let expected_y_lookup = 10.0;
847 assert_eq!(expected_y_lookup, y_lookup);
848 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
849 assert_eq!(expected_y_lookup, y_lookup);
850 }
851
852 #[test]
853 fn test_interpolate_with_non_evenly_spaced_x_data() {
854 let xs = Array1::from_vec(vec![0.0, 10.0, 100.0, 1000.0]);
855 let ys = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
856 let x = 55.0;
857 let y_lookup = interpolate(&x, &xs, &ys, false).unwrap();
858 let expected_y_lookup = 1.5;
859 assert_eq!(expected_y_lookup, y_lookup);
860 let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false);
861 assert_eq!(expected_y_lookup, y_lookup);
862 }
863
864 #[test]
865 fn test_path_to_cache() {
866 let path = path_to_cache().unwrap();
867 println!("{:?}", path);
868 }
869
870 #[test]
871 fn test_clear_cache() {
872 if env::var(NETWORK_TEST_DISABLE_ENV_VAR_NAME).is_ok() {
873 println!("SKIPPING: test_clear_cache");
874 return;
875 }
876 let temp_sub_dir = tempfile::TempDir::new_in(create_project_subdir("").unwrap()).unwrap();
877 let sub_dir_path = temp_sub_dir.path().to_str().unwrap();
878 let still_exists_before = std::fs::metadata(sub_dir_path).is_ok();
879 assert_eq!(still_exists_before, true);
880 url_to_cache("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/assets/2022_Tesla_Model_Y_RWD_example.yaml", "").unwrap();
881 clear_cache(sub_dir_path).unwrap();
882 let still_exists = std::fs::metadata(sub_dir_path).is_ok();
883 assert_eq!(still_exists, false);
884 let path_to_vehicle = path_to_cache()
885 .unwrap()
886 .join("2022_Tesla_Model_Y_RWD_example.yaml");
887 let vehicle_still_exists = std::fs::metadata(&path_to_vehicle).is_ok();
888 assert_eq!(vehicle_still_exists, true);
889 std::fs::remove_file(path_to_vehicle).unwrap();
890 }
891}