giga_segy_out/
utils.rs

1// Copyright (C) 2022 by GiGa infosystems
2//! This module contains the [`CoordinateScalar`] structure which deals with the somewhat unusual
3//! way that SEG-Y uses to express scaling factor of coordinates.
4//!
5//! See the [SEG-Y_r2.0 standard](<https://seg.org/Portals/0/SEG/News%20and%20Resources/Technical%20Standards/seg_y_rev2_0-mar2017.pdf>)
6//! (january 2017), page 17 for more details.
7use num::{One, ToPrimitive, Zero};
8use std::cmp::Ordering;
9use std::fmt::Debug;
10use std::ops::Div;
11
12/// This structure deals with scaling of coordinates.
13///
14/// Since coordinates in  SEG-Y are stored as [`i32`], by default,
15/// this means that decimal points cannot be stored. A scalar
16/// corrects this flaw. Scalars are somewhat unintuitive, so this
17/// utility is provided to convert normal multipliers to scalars.
18///
19/// See the [SEG-Y_r2.0 standard](<https://seg.org/Portals/0/SEG/News%20and%20Resources/Technical%20Standards/seg_y_rev2_0-mar2017.pdf>)
20/// (January 2017), page 17 for more details.
21///
22/// There are two points of note for this function:
23///
24/// * The multiplier should be of the same type as the coordinates
25/// that are being converted.
26///
27/// * SEG-Y stores scalars as [`i16`], so initial multipliers of a high
28/// magnitude, or those from non-integer floats will be handled lossily.
29///
30/// ```
31/// # use giga_segy_out::utils::CoordinateScalar;
32/// // Our coordinate value is 52.55. We want to keep the decimal places,
33/// // So we multiply by 100, which means the final value, 5255 must be multiplied
34/// // by 0.01 for it to be returned to 52.55. So the input here 0.01.
35/// let s = CoordinateScalar::from_multiplier(0.01f64).unwrap();
36///
37/// // `output_a`, which is what will be inserted into the trace header, should be
38/// // about 5255.
39/// let output_a = s.scale(52.55);
40/// // `output_b`, which is what will be inserted into the trace header, should be
41/// // about 5200.
42/// let output_b = s.scale_to_i32(52.).unwrap();
43/// assert_eq!(output_a, 5255.0);
44/// assert_eq!(output_b, 5200);
45///
46/// assert_eq!(s.multiplier(), 0.01);
47/// // The `writeable_scalar` is what must be written in the trace header to get the
48/// // correct final conversion when reading the SEG-Y.
49/// assert_eq!(s.writeable_scalar(), -100);
50/// ```
51#[derive(Clone, Debug, PartialEq)]
52pub struct CoordinateScalar<T>
53where
54    T: ToPrimitive + Debug + Clone + PartialOrd + One + Zero + Div<Output = T>,
55{
56    original_multiplier: T,
57    writeable_scalar: i16,
58}
59
60impl<T> CoordinateScalar<T>
61where
62    T: ToPrimitive + Debug + Clone + PartialOrd + One + Zero + Div<Output = T>,
63{
64    /// Create a new scalar.
65    ///
66    /// * If the value provided is outside of the range, [`None`] is returned.
67    ///
68    /// * A multiplier MUST be a non-negative value.
69    ///
70    /// * If the multiplier is 100, the scalar is -100.
71    ///
72    /// ```
73    /// # use giga_segy_out::utils::CoordinateScalar;
74    /// assert!(CoordinateScalar::from_multiplier(-1i16).is_none());
75    /// assert!(CoordinateScalar::from_multiplier(i16::MAX).is_some());
76    /// assert!(CoordinateScalar::from_multiplier(i16::MAX as i32 + 1).is_none());
77    ///
78    /// let s = CoordinateScalar::from_multiplier(1000.0f32).unwrap();
79    /// assert_eq!(s.multiplier(), 1000.);
80    /// assert_eq!(s.writeable_scalar(), 1000);
81    ///
82    /// let s = CoordinateScalar::from_multiplier(0.001f32).unwrap();
83    /// assert_eq!(s.multiplier(), 0.001);
84    /// assert_eq!(s.writeable_scalar(), -1000);
85    /// ```
86    pub fn from_multiplier(multiplier: T) -> Option<Self> {
87        if let Some(Ordering::Less) = multiplier.partial_cmp(&T::zero()) {
88            return None;
89        }
90
91        match multiplier.partial_cmp(&T::one()) {
92            Some(Ordering::Greater) => multiplier.to_i16(),
93            Some(Ordering::Less) => multiplier
94                .to_f64()
95                // This needs to be rounded otherwise silly things happen.
96                .and_then(|m| (1. / m).round().to_i16().map(|i| -i)),
97            Some(Ordering::Equal) => Some(0),
98            None => None,
99        }
100        .map(|x| Self {
101            original_multiplier: multiplier,
102            writeable_scalar: x,
103        })
104    }
105
106    /// Operate on a value converting to [`i32`] directly.
107    ///
108    /// * This is useful if all coordinates are stored directly as [`i32`].
109    ///
110    /// * If the value cannot be converted to [`i32`] directly, [`None`] is returned.
111    ///
112    /// ```
113    /// # use giga_segy_out::utils::CoordinateScalar;
114    /// let s = CoordinateScalar::from_multiplier(10.0f32).unwrap();
115    /// assert_eq!(s.scale_to_i32(990i16 as f32), Some(99));
116    /// assert_eq!(s.scale_to_i32(i32::MAX as f32 * 11.), None);
117    /// ```
118    pub fn scale_to_i32(&self, x: T) -> Option<i32> {
119        (x / self.original_multiplier.clone()).to_i32()
120    }
121
122    /// Operate on a value.
123    ///
124    /// ```
125    /// # use giga_segy_out::utils::CoordinateScalar;
126    /// let s = CoordinateScalar::from_multiplier(10.0f32).unwrap();
127    /// assert_eq!(s.scale(990i16 as f32), 99.);
128    /// assert_eq!(s.scale(i32::MAX as f32 * 11.), i32::MAX as f32 * 11./10.);
129    /// ```
130    pub fn scale(&self, x: T) -> T {
131        x / self.original_multiplier.clone()
132    }
133
134    /// Get the multiplier.
135    pub fn multiplier(&self) -> T {
136        self.original_multiplier.clone()
137    }
138
139    /// Get the scaler as the final [`i16`] value that is to be written to the SEG-Y [`crate::TraceHeader`].
140    /// ```
141    /// use giga_segy_out::TraceHeader;
142    /// use giga_segy_out::create_headers::CreateTraceHeader;
143    /// use giga_segy_out::utils::CoordinateScalar;
144    ///
145    /// // Oh no! Our coordinates can't be stored as `i32` without losing precision.
146    /// let (x, y) = (39874.34f32, 12312.12f32);
147    /// // ...But if we multiply by 100, they can!
148    /// let s = CoordinateScalar::from_multiplier(0.01f32).unwrap();
149    ///
150    /// let th = TraceHeader::new_2d(
151    ///     s.scale_to_i32(x).unwrap(),
152    ///     s.scale_to_i32(y).unwrap(),
153    ///     s.writeable_scalar()
154    /// );
155    ///
156    /// // Let's check.
157    /// assert_eq!(th.x_ensemble, 3987434);
158    /// assert_eq!(th.y_ensemble, 1231212);
159    /// assert_eq!(th.coordinate_scalar, -100);
160    /// ```
161    pub fn writeable_scalar(&self) -> i16 {
162        self.writeable_scalar
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::num::integer::Roots;
170
171    #[test]
172    fn create_cs1() {
173        let scalar = CoordinateScalar::from_multiplier(0.01f64);
174        let expected = CoordinateScalar {
175            original_multiplier: 0.01f64,
176            writeable_scalar: -100,
177        };
178
179        // A few creation tests.
180        assert_eq!(scalar, Some(expected));
181        let scalar = scalar.unwrap();
182        assert_eq!(scalar.writeable_scalar(), -100);
183        assert_eq!(scalar.multiplier(), 0.01);
184
185        // A few scaling tests. (FP comparisons FTL)
186        assert!(scalar.scale(62.) - 6200. < (0.000000000000001f64).abs());
187        assert_eq!(scalar.scale(0.), 0.);
188        assert_eq!(scalar.scale(1.), 100.);
189
190        assert_eq!(scalar.scale_to_i32(f64::MAX.sqrt()), None);
191        assert_eq!(scalar.scale_to_i32(0.), Some(0));
192        assert_eq!(scalar.scale_to_i32(1.), Some(100));
193        assert_eq!(scalar.scale_to_i32(360000.), Some(36000000));
194    }
195
196    #[test]
197    fn create_cs2() {
198        let scalar = CoordinateScalar::from_multiplier(10000u64);
199        let expected = CoordinateScalar {
200            original_multiplier: 10000u64,
201            writeable_scalar: 10000,
202        };
203
204        // A few creation tests.
205        assert_eq!(scalar, Some(expected));
206        let scalar = scalar.unwrap();
207        assert_eq!(scalar.writeable_scalar(), 10000);
208        assert_eq!(scalar.multiplier(), 10000);
209
210        // A few scaling tests.
211        assert_eq!(scalar.scale(620000), 62);
212        assert_eq!(scalar.scale(0), 0);
213        assert_eq!(scalar.scale(10000), 1);
214
215        assert_eq!(scalar.scale_to_i32(u64::MAX.sqrt() * 1000000), None);
216        assert_eq!(scalar.scale_to_i32(0), Some(0));
217        assert_eq!(scalar.scale_to_i32(12000), Some(1));
218        assert_eq!(scalar.scale_to_i32(360000), Some(36));
219    }
220
221    #[test]
222    fn create_cs3() {
223        let scalar = CoordinateScalar::from_multiplier(1i128);
224        let expected = CoordinateScalar {
225            original_multiplier: 1i128,
226            writeable_scalar: 0,
227        };
228
229        // A few creation tests.
230        assert_eq!(scalar, Some(expected));
231        let scalar = scalar.unwrap();
232        assert_eq!(scalar.writeable_scalar(), 0);
233        assert_eq!(scalar.multiplier(), 1);
234
235        // A few scaling tests.
236        assert_eq!(scalar.scale(62), 62);
237        assert_eq!(scalar.scale(0), 0);
238        assert_eq!(scalar.scale(1), 1);
239
240        assert_eq!(scalar.scale_to_i32(i128::MAX.sqrt()), None);
241        assert_eq!(scalar.scale_to_i32(0), Some(0));
242        assert_eq!(scalar.scale_to_i32(1), Some(1));
243        assert_eq!(scalar.scale_to_i32(36), Some(36));
244    }
245
246    #[test]
247    fn create_cs_fail() {
248        let scalar = CoordinateScalar::from_multiplier(u64::MAX);
249        assert_eq!(scalar, None);
250    }
251
252    #[test]
253    fn create_cs_fail2() {
254        let scalar = CoordinateScalar::from_multiplier(-100i64);
255        assert_eq!(scalar, None);
256    }
257}