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}