1use crate::imports::*;
2use paste::paste;
3use regex::Regex;
4
5pub mod interp;
6pub use interp::*;
7pub mod tracked_state;
8pub use tracked_state::*;
9
10pub const DIRECT_SET_ERR: &str = "Setting field value directly not allowed";
12
13pub fn return_true() -> bool {
15 true
16}
17
18pub fn is_sorted<T: std::cmp::PartialOrd>(data: &[T]) -> bool {
21 data.windows(2).all(|w| w[0] <= w[1])
22}
23
24#[cfg(feature = "web")]
29#[allow(dead_code)]
30pub(crate) fn download_file<S: AsRef<str>, P: AsRef<Path>>(
31 url: S,
32 filepath: P,
33) -> anyhow::Result<()> {
34 let url = url::Url::parse(url.as_ref())?;
35 let filepath = filepath.as_ref();
36 let filepath = if filepath.extension().is_none() {
37 let filename = url
39 .path_segments()
40 .and_then(|segments| segments.last())
41 .with_context(|| "Could not parse filename from last URL segment: {url:?}")?;
42 filepath.join(filename)
43 } else {
44 filepath.to_path_buf()
45 };
46 let mut rdr = ureq::get(url.as_ref()).call()?.into_reader();
47 let mut wtr = File::create(filepath)?;
48 std::io::copy(&mut rdr, &mut wtr)?;
49 Ok(())
50}
51
52#[allow(unused)]
53fn find_interp_indices(query: &f64, axis: &[f64]) -> anyhow::Result<(usize, usize)> {
56 let axis_size = axis.len();
57 match axis
58 .windows(2)
59 .position(|w| query >= &w[0] && query < &w[1])
60 {
61 Some(p) => {
62 if query == &axis[p] {
63 Ok((p, p))
64 } else if query == &axis[p + 1] {
65 Ok((p + 1, p + 1))
66 } else {
67 Ok((p, p + 1))
68 }
69 }
70 None => {
71 if query <= &axis[0] {
72 Ok((0, 0))
73 } else if query >= &axis[axis_size - 1] {
74 Ok((axis_size - 1, axis_size - 1))
75 } else {
76 bail!("Unable to find where the query fits in the values, check grid.")
77 }
78 }
79 }
80}
81
82#[allow(unused)]
83fn compute_interp_diff(value: &f64, lower: &f64, upper: &f64) -> f64 {
85 if lower == upper {
86 0.0
87 } else {
88 (value - lower) / (upper - lower)
89 }
90}
91
92impl<T> SerdeAPI for Extrapolate<T> where T: Serialize + for<'de> Deserialize<'de> {}
93impl<T> Init for Extrapolate<T> {}
94
95pub fn abs_checked_x_val(x_val: f64, x_data: &[f64]) -> anyhow::Result<f64> {
97 if *x_data
98 .first()
99 .with_context(|| format!("{}\nExpected `first` to return `Some`.", format_dbg!()))?
100 == 0.
101 {
102 Ok(x_val.abs())
103 } else {
104 Ok(x_val)
105 }
106}
107
108pub const COMP_EPSILON: f64 = 1e-8;
110
111pub fn almost_eq(val1: f64, val2: f64, epsilon: Option<f64>) -> bool {
114 let epsilon = epsilon.unwrap_or(COMP_EPSILON);
115 ((val2 - val1) / (val1 + val2)).abs() < epsilon || (val2 - val1).abs() < epsilon
116}
117
118pub fn almost_gt(val1: f64, val2: f64, epsilon: Option<f64>) -> bool {
119 let epsilon = epsilon.unwrap_or(COMP_EPSILON);
120 val1 > val2 * (1.0 + epsilon)
121}
122
123pub fn almost_lt(val1: f64, val2: f64, epsilon: Option<f64>) -> bool {
124 let epsilon = epsilon.unwrap_or(COMP_EPSILON);
125 val1 < val2 * (1.0 - epsilon)
126}
127
128pub fn almost_ge(val1: f64, val2: f64, epsilon: Option<f64>) -> bool {
130 let epsilon = epsilon.unwrap_or(COMP_EPSILON);
131 val1 > val2 * (1.0 - epsilon) || val1 > val2 - epsilon
132}
133
134pub fn almost_le(val1: f64, val2: f64, epsilon: Option<f64>) -> bool {
136 let epsilon = epsilon.unwrap_or(COMP_EPSILON);
137 val1 < val2 * (1.0 + epsilon) || val1 < val2 + epsilon
138}
139
140lazy_static! {
141 static ref TIRE_CODE_REGEX: Regex = Regex::new(
142 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]?"
143 ).expect("Failed compile tire code regex");
144}
145
146pub fn tire_code_to_radius<S: AsRef<str>>(tire_code: S) -> anyhow::Result<f64> {
171 let tire_code = tire_code.as_ref();
172 let captures = TIRE_CODE_REGEX.captures(tire_code).with_context(|| {
173 format!(
174 "Regex pattern does not match for {:?}: {:?}",
175 tire_code,
176 TIRE_CODE_REGEX.as_str(),
177 )
178 })?;
179 let width_mm: f64 = captures[1].parse()?;
180 let aspect_ratio: f64 = captures[2].parse()?;
181 let rim_diameter_in: f64 = captures[3].parse()?;
182
183 let sidewall_height_mm = width_mm * aspect_ratio / 100.0;
184 let radius_mm = (rim_diameter_in * 25.4) / 2.0 + sidewall_height_mm;
185
186 Ok(radius_mm / 1000.0)
187}
188
189make_uom_cmp_fn!(almost_eq);
190make_uom_cmp_fn!(almost_gt);
191make_uom_cmp_fn!(almost_lt);
192make_uom_cmp_fn!(almost_ge);
193make_uom_cmp_fn!(almost_le);
194
195#[derive(IsVariant, derive_more::From, TryInto)]
196pub(crate) enum InterpRange {
197 ZeroThroughOne,
198 NegativeOneThroughOne,
199 Either,
200}
201
202pub(crate) fn check_interp_frac_data(
207 data: &[f64],
208 interp_range: InterpRange,
209) -> anyhow::Result<InterpRange> {
210 check_monotonicity(data).with_context(|| anyhow!(format_dbg!()))?;
211 let min = data.first().with_context(|| {
212 anyhow!(
213 "{}\nProblem extracting first element of `data`",
214 format_dbg!()
215 )
216 })?;
217 let max = data.last().with_context(|| {
218 anyhow!(
219 "{}\nProblem extracting first element of `data`",
220 format_dbg!()
221 )
222 })?;
223 match interp_range {
224 InterpRange::ZeroThroughOne => {
225 ensure!(
226 *min == 0. && *max == 1.,
227 "data min ({}) and max ({}) must be zero and one, respectively.",
228 min,
229 max
230 );
231 }
232 InterpRange::NegativeOneThroughOne => {
233 ensure!(
234 *min == -1. && *max == 1.,
235 "data min ({}) and max ({}) must be zero and one, respectively.",
236 min,
237 max
238 );
239 }
240 InterpRange::Either => {
241 ensure!(
242 (*min == -1. || *min == 0.) && *max == 1.,
243 "data min ({}) and max ({}) must be zero or negative one and one, respectively.",
244 min,
245 max
246 );
247 }
248 }
249 if *min == 0. && *max == 1. {
250 Ok(InterpRange::ZeroThroughOne)
251 } else {
252 Ok(InterpRange::NegativeOneThroughOne)
253 }
254}
255
256pub fn check_monotonicity(data: &[f64]) -> anyhow::Result<()> {
258 ensure!(
259 data.windows(2).all(|w| w[0] < w[1]),
260 format_dbg!("{}\n`data` must be monotonically increasing")
261 );
262 Ok(())
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268 #[test]
269 fn test_linspace() {
270 assert_eq!(Vec::linspace(0.0, 1.0, 3), vec![0.0, 0.5, 1.0]);
271 }
272
273 #[test]
274 fn test_almost_gt_zero() {
275 assert!(almost_gt(1e-9, 0.0, None));
276 assert!(!almost_gt(0.0, 1e-9, None));
277 assert!(almost_gt(1e-7, 0.0, None));
278 assert!(!almost_gt(0.0, 1e-7, None));
279 }
280
281 #[test]
282 fn test_almost_ge_zero() {
283 assert!(almost_ge(1e-9, 0.0, None));
284 assert!(almost_ge(0.0, 1e-9, None));
285 assert!(almost_ge(1e-7, 0.0, None));
286 assert!(!almost_ge(0.0, 1e-7, None));
287 }
288
289 #[test]
290 fn test_almost_eq_zero() {
291 assert!(almost_eq(0.0, 1e-9, None));
292 assert!(almost_eq(1e-9, 0.0, None));
293 assert!(!almost_eq(0.0, 1e-7, None));
294 assert!(!almost_eq(1e-7, 0.0, None));
295 }
296
297 #[test]
298 fn test_almost_le_zero() {
299 assert!(almost_le(1e-9, 0.0, None));
300 assert!(almost_le(0.0, 1e-9, None));
301 assert!(!almost_le(1e-7, 0.0, None));
302 assert!(almost_le(0.0, 1e-7, None));
303 }
304
305 #[test]
306 fn test_almost_lt_zero() {
307 assert!(!almost_lt(1e-9, 0.0, None));
308 assert!(almost_lt(0.0, 1e-9, None));
309 assert!(!almost_lt(1e-7, 0.0, None));
310 assert!(almost_lt(0.0, 1e-7, None));
311 }
312
313 #[test]
314 fn test_almost_gt_large() {
315 assert!(!almost_gt(1e9 * (1.0 + 1e-9), 1e9, None));
316 assert!(!almost_gt(1e9, 1e9 * (1.0 + 1e-9), None));
317 assert!(almost_gt(1e9 * (1.0 + 1e-7), 1e9, None));
318 assert!(!almost_gt(1e9, 1e9 * (1.0 + 1e-7), None));
319 }
320
321 #[test]
322 fn test_almost_ge_large() {
323 assert!(almost_ge(1e9 * (1.0 + 1e-9), 1e9, None));
324 assert!(almost_ge(1e9, 1e9 * (1.0 + 1e-9), None));
325 assert!(almost_ge(1e9 * (1.0 + 1e-7), 1e9, None));
326 assert!(!almost_ge(1e9, 1e9 * (1.0 + 1e-7), None));
327 }
328
329 #[test]
330 fn test_almost_eq_large() {
331 assert!(almost_eq(1e9 * (1.0 + 1e-9), 1e9, None));
332 assert!(almost_eq(1e9, 1e9 * (1.0 + 1e-9), None));
333 assert!(!almost_eq(1e9 * (1.0 + 1e-7), 1e9, None));
334 assert!(!almost_eq(1e9, 1e9 * (1.0 + 1e-7), None));
335 }
336
337 #[test]
338 fn test_almost_le_large() {
339 assert!(almost_le(1e9 * (1.0 + 1e-9), 1e9, None));
340 assert!(almost_le(1e9, 1e9 * (1.0 + 1e-9), None));
341 assert!(!almost_le(1e9 * (1.0 + 1e-7), 1e9, None));
342 assert!(almost_le(1e9, 1e9 * (1.0 + 1e-7), None));
343 }
344
345 #[test]
346 fn test_almost_lt_large() {
347 assert!(!almost_lt(1e9 * (1.0 + 1e-9), 1e9, None));
348 assert!(!almost_lt(1e9, 1e9 * (1.0 + 1e-9), None));
349 assert!(!almost_lt(1e9 * (1.0 + 1e-7), 1e9, None));
350 assert!(almost_lt(1e9, 1e9 * (1.0 + 1e-7), None));
351 }
352}