1 use crate::coord::ranged1d::types::RangedCoordusize; 2 use crate::coord::ranged1d::{ 3 AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter, 4 }; 5 use std::cmp::{Ordering, PartialOrd}; 6 use std::marker::PhantomData; 7 use std::ops::{Add, Range, Sub}; 8 9 /// The type marker used to denote the rounding method. 10 /// Since we are mapping any range to a discrete range thus not all values are 11 /// perfect mapped to the grid points. In this case, this type marker gives hints 12 /// for the linspace coord for how to treat the non-grid-point values. 13 pub trait LinspaceRoundingMethod<V> { 14 /// Search for the value within the given values array and rounding method 15 /// 16 /// - `values`: The values we want to search 17 /// - `target`: The target value 18 /// - `returns`: The index if we found the matching item, otherwise none search(values: &[V], target: &V) -> Option<usize>19 fn search(values: &[V], target: &V) -> Option<usize>; 20 } 21 22 /// This type marker means linspace do the exact match for searching 23 /// which means if there's no value strictly equals to the target, the coord spec 24 /// reports not found result. 25 #[derive(Clone)] 26 pub struct Exact<V>(PhantomData<V>); 27 28 impl<V: PartialOrd> LinspaceRoundingMethod<V> for Exact<V> { search(values: &[V], target: &V) -> Option<usize>29 fn search(values: &[V], target: &V) -> Option<usize> { 30 values.iter().position(|x| target == x) 31 } 32 } 33 34 /// This type marker means we round up the value. Which means we try to find a 35 /// minimal value in the values array that is greater or equal to the target. 36 #[derive(Clone)] 37 pub struct Ceil<V>(PhantomData<V>); 38 39 impl<V: PartialOrd> LinspaceRoundingMethod<V> for Ceil<V> { search(values: &[V], target: &V) -> Option<usize>40 fn search(values: &[V], target: &V) -> Option<usize> { 41 let ascending = if values.len() < 2 { 42 true 43 } else { 44 values[0].partial_cmp(&values[1]) == Some(Ordering::Less) 45 }; 46 47 match values.binary_search_by(|probe| { 48 if ascending { 49 probe.partial_cmp(target).unwrap() 50 } else { 51 target.partial_cmp(probe).unwrap() 52 } 53 }) { 54 Ok(idx) => Some(idx), 55 Err(idx) => { 56 let offset = if ascending { 0 } else { 1 }; 57 58 if idx < offset || idx >= values.len() + offset { 59 return None; 60 } 61 Some(idx - offset) 62 } 63 } 64 } 65 } 66 67 /// This means we use the round down. Which means we try to find a 68 /// maximum value in the values array that is less or equal to the target. 69 #[derive(Clone)] 70 pub struct Floor<V>(PhantomData<V>); 71 72 impl<V: PartialOrd> LinspaceRoundingMethod<V> for Floor<V> { search(values: &[V], target: &V) -> Option<usize>73 fn search(values: &[V], target: &V) -> Option<usize> { 74 let ascending = if values.len() < 2 { 75 true 76 } else { 77 values[0].partial_cmp(&values[1]) == Some(Ordering::Less) 78 }; 79 80 match values.binary_search_by(|probe| { 81 if ascending { 82 probe.partial_cmp(target).unwrap() 83 } else { 84 target.partial_cmp(probe).unwrap() 85 } 86 }) { 87 Ok(idx) => Some(idx), 88 Err(idx) => { 89 let offset = if ascending { 1 } else { 0 }; 90 91 if idx < offset || idx >= values.len() + offset { 92 return None; 93 } 94 Some(idx - offset) 95 } 96 } 97 } 98 } 99 100 /// This means we use the rounding. Which means we try to find the closet 101 /// value in the array that matches the target 102 #[derive(Clone)] 103 pub struct Round<V, S>(PhantomData<(V, S)>); 104 105 impl<V, S> LinspaceRoundingMethod<V> for Round<V, S> 106 where 107 V: Add<S, Output = V> + PartialOrd + Sub<V, Output = S> + Clone, 108 S: PartialOrd + Clone, 109 { search(values: &[V], target: &V) -> Option<usize>110 fn search(values: &[V], target: &V) -> Option<usize> { 111 let ascending = if values.len() < 2 { 112 true 113 } else { 114 values[0].partial_cmp(&values[1]) == Some(Ordering::Less) 115 }; 116 117 match values.binary_search_by(|probe| { 118 if ascending { 119 probe.partial_cmp(target).unwrap() 120 } else { 121 target.partial_cmp(probe).unwrap() 122 } 123 }) { 124 Ok(idx) => Some(idx), 125 Err(idx) => { 126 if idx == 0 { 127 return Some(0); 128 } 129 130 if idx == values.len() { 131 return Some(idx - 1); 132 } 133 134 let left_delta = if ascending { 135 target.clone() - values[idx - 1].clone() 136 } else { 137 values[idx - 1].clone() - target.clone() 138 }; 139 let right_delta = if ascending { 140 values[idx].clone() - target.clone() 141 } else { 142 target.clone() - values[idx].clone() 143 }; 144 145 if left_delta.partial_cmp(&right_delta) == Some(Ordering::Less) { 146 Some(idx - 1) 147 } else { 148 Some(idx) 149 } 150 } 151 } 152 } 153 } 154 155 /// The coordinate combinator that transform a continous coordinate to a discrete coordinate 156 /// to a discrete coordinate by a giving step. 157 /// 158 /// For example, range `0f32..100f32` is a continuous coordinate, thus this prevent us having a 159 /// histogram on it since Plotters doesn't know how to segment the range into buckets. 160 /// In this case, to get a histogram, we need to split the original range to a 161 /// set of discrete buckets (for example, 0.5 per bucket). 162 /// 163 /// The linspace decorate abstracting this method. For example, we can have a discrete coordinate: 164 /// `(0f32..100f32).step(0.5)`. 165 /// 166 /// Linspace also supports different types of bucket matching method - This configuration alters the behavior of 167 /// [DiscreteCoord::index_of](../trait.DiscreteCoord.html#tymethod.index_of) for Linspace coord spec 168 /// - **Flooring**, the value falls into the nearst bucket smaller than it. See [Linspace::use_floor](struct.Linspace.html#method.use_floor) 169 /// - **Round**, the value falls into the nearst bucket. See [Linearspace::use_round](struct.Linspace.html#method.use_round) 170 /// - **Ceiling**, the value falls into the nearst bucket larger than itself. See [Linspace::use_ceil](struct.Linspace.html#method.use_ceil) 171 /// - **Exact Matchting**, the value must be exactly same as the butcket value. See [Linspace::use_exact](struct.Linspace.html#method.use_exact) 172 #[derive(Clone)] 173 pub struct Linspace<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> 174 where 175 T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, 176 { 177 step: S, 178 inner: T, 179 grid_value: Vec<T::ValueType>, 180 _phatom: PhantomData<R>, 181 } 182 183 impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Linspace<T, S, R> 184 where 185 T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, 186 { compute_grid_values(&mut self)187 fn compute_grid_values(&mut self) { 188 let range = self.inner.range(); 189 190 match ( 191 range.start.partial_cmp(&range.end), 192 (range.start.clone() + self.step.clone()).partial_cmp(&range.end), 193 ) { 194 (Some(a), Some(b)) if a != b || a == Ordering::Equal || b == Ordering::Equal => (), 195 (Some(a), Some(_)) => { 196 let mut current = range.start; 197 while current.partial_cmp(&range.end) == Some(a) { 198 self.grid_value.push(current.clone()); 199 current = current + self.step.clone(); 200 } 201 } 202 _ => (), 203 } 204 } 205 206 /// Set the linspace use the round up method for value matching 207 /// 208 /// - **returns**: The newly created linspace that uses new matching method use_ceil(self) -> Linspace<T, S, Ceil<T::ValueType>>209 pub fn use_ceil(self) -> Linspace<T, S, Ceil<T::ValueType>> { 210 Linspace { 211 step: self.step, 212 inner: self.inner, 213 grid_value: self.grid_value, 214 _phatom: PhantomData, 215 } 216 } 217 218 /// Set the linspace use the round down method for value matching 219 /// 220 /// - **returns**: The newly created linspace that uses new matching method use_floor(self) -> Linspace<T, S, Floor<T::ValueType>>221 pub fn use_floor(self) -> Linspace<T, S, Floor<T::ValueType>> { 222 Linspace { 223 step: self.step, 224 inner: self.inner, 225 grid_value: self.grid_value, 226 _phatom: PhantomData, 227 } 228 } 229 230 /// Set the linspace use the best match method for value matching 231 /// 232 /// - **returns**: The newly created linspace that uses new matching method use_round(self) -> Linspace<T, S, Round<T::ValueType, S>> where T::ValueType: Sub<T::ValueType, Output = S>, S: PartialOrd,233 pub fn use_round(self) -> Linspace<T, S, Round<T::ValueType, S>> 234 where 235 T::ValueType: Sub<T::ValueType, Output = S>, 236 S: PartialOrd, 237 { 238 Linspace { 239 step: self.step, 240 inner: self.inner, 241 grid_value: self.grid_value, 242 _phatom: PhantomData, 243 } 244 } 245 246 /// Set the linspace use the exact match method for value matching 247 /// 248 /// - **returns**: The newly created linspace that uses new matching method use_exact(self) -> Linspace<T, S, Exact<T::ValueType>> where T::ValueType: Sub<T::ValueType, Output = S>, S: PartialOrd,249 pub fn use_exact(self) -> Linspace<T, S, Exact<T::ValueType>> 250 where 251 T::ValueType: Sub<T::ValueType, Output = S>, 252 S: PartialOrd, 253 { 254 Linspace { 255 step: self.step, 256 inner: self.inner, 257 grid_value: self.grid_value, 258 _phatom: PhantomData, 259 } 260 } 261 } 262 263 impl<T, R, S, RM> ValueFormatter<T> for Linspace<R, S, RM> 264 where 265 R: Ranged<ValueType = T> + ValueFormatter<T>, 266 RM: LinspaceRoundingMethod<T>, 267 T: Add<S, Output = T> + PartialOrd + Clone, 268 S: Clone, 269 { format(value: &T) -> String270 fn format(value: &T) -> String { 271 R::format(value) 272 } 273 } 274 275 impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Ranged for Linspace<T, S, R> 276 where 277 T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, 278 { 279 type FormatOption = NoDefaultFormatting; 280 type ValueType = T::ValueType; 281 range(&self) -> Range<T::ValueType>282 fn range(&self) -> Range<T::ValueType> { 283 self.inner.range() 284 } 285 map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32286 fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 { 287 self.inner.map(value, limit) 288 } 289 key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<T::ValueType>290 fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<T::ValueType> { 291 if self.grid_value.is_empty() { 292 return vec![]; 293 } 294 let idx_range: RangedCoordusize = (0..(self.grid_value.len() - 1)).into(); 295 296 idx_range 297 .key_points(hint) 298 .into_iter() 299 .map(|x| self.grid_value[x].clone()) 300 .collect() 301 } 302 } 303 304 impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> DiscreteRanged 305 for Linspace<T, S, R> 306 where 307 T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone, 308 { size(&self) -> usize309 fn size(&self) -> usize { 310 self.grid_value.len() 311 } 312 index_of(&self, value: &T::ValueType) -> Option<usize>313 fn index_of(&self, value: &T::ValueType) -> Option<usize> { 314 R::search(self.grid_value.as_ref(), value) 315 } 316 from_index(&self, idx: usize) -> Option<T::ValueType>317 fn from_index(&self, idx: usize) -> Option<T::ValueType> { 318 self.grid_value.get(idx).map(Clone::clone) 319 } 320 } 321 322 pub trait IntoLinspace: AsRangedCoord { 323 /// Set the step value, make a linspace coordinate from the given range. 324 /// By default the matching method use the exact match 325 /// 326 /// - `val`: The step value 327 /// - **returns*: The newly created linspace step<S: Clone>(self, val: S) -> Linspace<Self::CoordDescType, S, Exact<Self::Value>> where Self::Value: Add<S, Output = Self::Value> + PartialOrd + Clone,328 fn step<S: Clone>(self, val: S) -> Linspace<Self::CoordDescType, S, Exact<Self::Value>> 329 where 330 Self::Value: Add<S, Output = Self::Value> + PartialOrd + Clone, 331 { 332 let mut ret = Linspace { 333 step: val, 334 inner: self.into(), 335 grid_value: vec![], 336 _phatom: PhantomData, 337 }; 338 339 ret.compute_grid_values(); 340 341 ret 342 } 343 } 344 345 impl<T: AsRangedCoord> IntoLinspace for T {} 346 347 #[cfg(test)] 348 mod test { 349 use super::*; 350 351 #[test] test_float_linspace()352 fn test_float_linspace() { 353 let coord = (0.0f64..100.0f64).step(0.1); 354 355 assert_eq!(coord.map(&23.12, (0, 10000)), 2312); 356 assert_eq!(coord.range(), 0.0..100.0); 357 assert_eq!(coord.key_points(100000).len(), 1001); 358 assert_eq!(coord.size(), 1001); 359 assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230)); 360 assert!((coord.from_index(230).unwrap() - 23.0).abs() < 1e-5); 361 } 362 363 #[test] test_rounding_methods()364 fn test_rounding_methods() { 365 let coord = (0.0f64..100.0f64).step(1.0); 366 367 assert_eq!(coord.index_of(&1.0), Some(1)); 368 assert_eq!(coord.index_of(&1.2), None); 369 370 let coord = coord.use_floor(); 371 assert_eq!(coord.index_of(&1.0), Some(1)); 372 assert_eq!(coord.index_of(&1.2), Some(1)); 373 assert_eq!(coord.index_of(&23.9), Some(23)); 374 assert_eq!(coord.index_of(&10000.0), Some(99)); 375 assert_eq!(coord.index_of(&-1.0), None); 376 377 let coord = coord.use_ceil(); 378 assert_eq!(coord.index_of(&1.0), Some(1)); 379 assert_eq!(coord.index_of(&1.2), Some(2)); 380 assert_eq!(coord.index_of(&23.9), Some(24)); 381 assert_eq!(coord.index_of(&10000.0), None); 382 assert_eq!(coord.index_of(&-1.0), Some(0)); 383 384 let coord = coord.use_round(); 385 assert_eq!(coord.index_of(&1.0), Some(1)); 386 assert_eq!(coord.index_of(&1.2), Some(1)); 387 assert_eq!(coord.index_of(&1.7), Some(2)); 388 assert_eq!(coord.index_of(&23.9), Some(24)); 389 assert_eq!(coord.index_of(&10000.0), Some(99)); 390 assert_eq!(coord.index_of(&-1.0), Some(0)); 391 392 let coord = (0.0f64..-100.0f64).step(-1.0); 393 394 assert_eq!(coord.index_of(&-1.0), Some(1)); 395 assert_eq!(coord.index_of(&-1.2), None); 396 397 let coord = coord.use_floor(); 398 assert_eq!(coord.index_of(&-1.0), Some(1)); 399 assert_eq!(coord.index_of(&-1.2), Some(2)); 400 assert_eq!(coord.index_of(&-23.9), Some(24)); 401 assert_eq!(coord.index_of(&-10000.0), None); 402 assert_eq!(coord.index_of(&1.0), Some(0)); 403 404 let coord = coord.use_ceil(); 405 assert_eq!(coord.index_of(&-1.0), Some(1)); 406 assert_eq!(coord.index_of(&-1.2), Some(1)); 407 assert_eq!(coord.index_of(&-23.9), Some(23)); 408 assert_eq!(coord.index_of(&-10000.0), Some(99)); 409 assert_eq!(coord.index_of(&1.0), None); 410 411 let coord = coord.use_round(); 412 assert_eq!(coord.index_of(&-1.0), Some(1)); 413 assert_eq!(coord.index_of(&-1.2), Some(1)); 414 assert_eq!(coord.index_of(&-1.7), Some(2)); 415 assert_eq!(coord.index_of(&-23.9), Some(24)); 416 assert_eq!(coord.index_of(&-10000.0), Some(99)); 417 assert_eq!(coord.index_of(&1.0), Some(0)); 418 } 419 420 #[cfg(feature = "chrono")] 421 #[test] test_duration_linspace()422 fn test_duration_linspace() { 423 use chrono::Duration; 424 let coord = (Duration::seconds(0)..Duration::seconds(100)).step(Duration::milliseconds(1)); 425 426 assert_eq!(coord.size(), 100_000); 427 assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230)); 428 assert_eq!(coord.key_points(10000000).len(), 100_000); 429 assert_eq!(coord.range(), Duration::seconds(0)..Duration::seconds(100)); 430 assert_eq!(coord.map(&Duration::seconds(25), (0, 100_000)), 25000); 431 } 432 } 433