geographiclib_rs/
geomath.rs

1#![allow(non_snake_case)]
2#![allow(clippy::excessive_precision)]
3
4pub const DIGITS: u64 = 53;
5pub const TWO: f64 = 2.0;
6
7pub fn get_epsilon() -> f64 {
8    TWO.powi(1 - DIGITS as i32)
9}
10
11pub fn get_min_val() -> f64 {
12    TWO.powi(-1022)
13}
14
15// Square
16pub fn sq(x: f64) -> f64 {
17    x.powi(2)
18}
19
20// We use the built-in impl (f64::cbrt) rather than this.
21// Real cube root
22pub fn cbrt(x: f64) -> f64 {
23    // y = math.pow(abs(x), 1/3.0)
24    let y = x.abs().powf(1.0 / 3.0);
25
26    // return y if x > 0 else (-y if x < 0 else x)
27    if x > 0.0 {
28        y
29    } else if x < 0.0 {
30        -y
31    } else {
32        x
33    }
34}
35
36// Normalize a two-vector
37pub fn norm(x: &mut f64, y: &mut f64) {
38    let r = x.hypot(*y);
39    *x /= r;
40    *y /= r;
41}
42
43// Error free transformation of a sum
44pub fn sum(u: f64, v: f64) -> (f64, f64) {
45    let s = u + v;
46    let up = s - v;
47    let vpp = s - up;
48    let up = up - u;
49    let vpp = vpp - v;
50    let t = -(up + vpp);
51    (s, t)
52}
53
54// Evaluate a polynomial
55pub fn polyval(n: usize, p: &[f64], x: f64) -> f64 {
56    let mut y = p[0];
57    for val in &p[1..=n] {
58        y = y * x + val;
59    }
60    y
61}
62
63// Round an angle so taht small values underflow to 0
64pub fn ang_round(x: f64) -> f64 {
65    // The makes the smallest gap in x = 1/16 - nextafter(1/16, 0) = 1/2^57
66    // for reals = 0.7 pm on the earth if x is an angle in degrees.  (This
67    // is about 1000 times more resolution than we get with angles around 90
68    // degrees.)  We use this to avoid having to deal with near singular
69    // cases when x is non-zero but tiny (e.g., 1.0e-200).
70    let z = 1.0 / 16.0;
71    let mut y = x.abs();
72    // The compiler mustn't "simplify" z - (z - y) to y
73    if y < z {
74        y = z - (z - y);
75    };
76    if x == 0.0 {
77        0.0
78    } else if x < 0.0 {
79        -y
80    } else {
81        y
82    }
83}
84
85/// remainder of x/y in the range [-y/2, y/2]
86fn remainder(x: f64, y: f64) -> f64 {
87    // z = math.fmod(x, y) if Math.isfinite(x) else Math.nan
88    let z = if x.is_finite() { x % y } else { f64::NAN };
89
90    // # On Windows 32-bit with python 2.7, math.fmod(-0.0, 360) = +0.0
91    // # This fixes this bug.  See also Math::AngNormalize in the C++ library.
92    // # sincosd has a similar fix.
93    // z = x if x == 0 else z
94    let z = if x == 0.0 { x } else { z };
95
96    // return (z + y if z < -y/2 else
97    // (z if z < y/2 else z -y))
98    if z < -y / 2.0 {
99        z + y
100    } else if z < y / 2.0 {
101        z
102    } else {
103        z - y
104    }
105}
106
107/// reduce angle to (-180,180]
108pub fn ang_normalize(x: f64) -> f64 {
109    // y = Math.remainder(x, 360)
110    // return 180 if y == -180 else y
111    let y = remainder(x, 360.0);
112    if y == -180.0 {
113        180.0
114    } else {
115        y
116    }
117}
118
119// Replace angles outside [-90,90] with NaN
120pub fn lat_fix(x: f64) -> f64 {
121    if x.abs() > 90.0 {
122        f64::NAN
123    } else {
124        x
125    }
126}
127
128// compute y - x and reduce to [-180,180] accurately
129pub fn ang_diff(x: f64, y: f64) -> (f64, f64) {
130    let (d, t) = sum(ang_normalize(-x), ang_normalize(y));
131    let d = ang_normalize(d);
132    if d == 180.0 && t > 0.0 {
133        sum(-180.0, t)
134    } else {
135        sum(d, t)
136    }
137}
138
139/// Compute sine and cosine of x in degrees
140pub fn sincosd(x: f64) -> (f64, f64) {
141    let (mut r, q) = libm::remquo(x, 90.0);
142
143    r = r.to_radians();
144
145    let (mut sinx, mut cosx) = r.sin_cos();
146
147    (sinx, cosx) = match q as u32 & 3 {
148        0 => (sinx, cosx),
149        1 => (cosx, -sinx),
150        2 => (-sinx, -cosx),
151        3 => (-cosx, sinx),
152        _ => unreachable!(),
153    };
154
155    // special values from F.10.1.12
156    cosx += 0.0;
157
158    // special values from F.10.1.13
159    if sinx == 0.0 {
160        sinx = sinx.copysign(x);
161    }
162    (sinx, cosx)
163}
164
165// Compute atan2(y, x) with result in degrees
166pub fn atan2d(y: f64, x: f64) -> f64 {
167    let mut x = x;
168    let mut y = y;
169    let mut q = if y.abs() > x.abs() {
170        std::mem::swap(&mut x, &mut y);
171        2.0
172    } else {
173        0.0
174    };
175    if x < 0.0 {
176        q += 1.0;
177        x = -x;
178    }
179    let mut ang = y.atan2(x).to_degrees();
180    if q == 1.0 {
181        ang = if y >= 0.0 { 180.0 - ang } else { -180.0 - ang };
182    } else if q == 2.0 {
183        ang = 90.0 - ang;
184    } else if q == 3.0 {
185        ang += -90.0;
186    }
187    ang
188}
189
190pub fn eatanhe(x: f64, es: f64) -> f64 {
191    if es > 0.0 {
192        es * (es * x).atanh()
193    } else {
194        -es * (es * x).atan()
195    }
196}
197
198// Functions that used to be inside Geodesic
199pub fn sin_cos_series(sinp: bool, sinx: f64, cosx: f64, c: &[f64]) -> f64 {
200    let mut k = c.len();
201    let mut n: i64 = k as i64 - if sinp { 1 } else { 0 };
202    let ar: f64 = 2.0 * (cosx - sinx) * (cosx + sinx);
203    let mut y1 = 0.0;
204    let mut y0: f64 = if n & 1 != 0 {
205        k -= 1;
206        c[k]
207    } else {
208        0.0
209    };
210    n /= 2;
211    while n > 0 {
212        n -= 1;
213        k -= 1;
214        y1 = ar * y0 - y1 + c[k];
215        k -= 1;
216        y0 = ar * y1 - y0 + c[k];
217    }
218    if sinp {
219        2.0 * sinx * cosx * y0
220    } else {
221        cosx * (y0 - y1)
222    }
223}
224
225// Solve astroid equation
226pub fn astroid(x: f64, y: f64) -> f64 {
227    let p = sq(x);
228    let q = sq(y);
229    let r = (p + q - 1.0) / 6.0;
230    if !(q == 0.0 && r <= 0.0) {
231        let s = p * q / 4.0;
232        let r2 = sq(r);
233        let r3 = r * r2;
234        let disc = s * (s + 2.0 * r3);
235        let mut u = r;
236        if disc >= 0.0 {
237            let mut t3 = s + r3;
238            t3 += if t3 < 0.0 { -disc.sqrt() } else { disc.sqrt() };
239            let t = cbrt(t3); // we could use built-in T.cbrt
240            u += t + if t != 0.0 { r2 / t } else { 0.0 };
241        } else {
242            let ang = (-disc).sqrt().atan2(-(s + r3));
243            u += 2.0 * r * (ang / 3.0).cos();
244        }
245        let v = (sq(u) + q).sqrt();
246        let uv = if u < 0.0 { q / (v - u) } else { u + v };
247        let w = (uv - q) / (2.0 * v);
248        uv / ((uv + sq(w)).sqrt() + w)
249    } else {
250        0.0
251    }
252}
253
254pub fn _A1m1f(eps: f64, geodesic_order: usize) -> f64 {
255    const COEFF: [f64; 5] = [1.0, 4.0, 64.0, 0.0, 256.0];
256    let m = geodesic_order / 2;
257    let t = polyval(m, &COEFF, sq(eps)) / COEFF[m + 1];
258    (t + eps) / (1.0 - eps)
259}
260
261pub fn _C1f(eps: f64, c: &mut [f64], geodesic_order: usize) {
262    const COEFF: [f64; 18] = [
263        -1.0, 6.0, -16.0, 32.0, -9.0, 64.0, -128.0, 2048.0, 9.0, -16.0, 768.0, 3.0, -5.0, 512.0,
264        -7.0, 1280.0, -7.0, 2048.0,
265    ];
266    let eps2 = sq(eps);
267    let mut d = eps;
268    let mut o = 0;
269    // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
270    // but benching (rust-1.75) shows that it would be slower.
271    #[allow(clippy::needless_range_loop)]
272    for l in 1..=geodesic_order {
273        let m = (geodesic_order - l) / 2;
274        c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1];
275        o += m + 2;
276        d *= eps;
277    }
278}
279
280pub fn _C1pf(eps: f64, c: &mut [f64], geodesic_order: usize) {
281    const COEFF: [f64; 18] = [
282        205.0, -432.0, 768.0, 1536.0, 4005.0, -4736.0, 3840.0, 12288.0, -225.0, 116.0, 384.0,
283        -7173.0, 2695.0, 7680.0, 3467.0, 7680.0, 38081.0, 61440.0,
284    ];
285    let eps2 = sq(eps);
286    let mut d = eps;
287    let mut o = 0;
288    // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
289    // but benching (rust-1.75) shows that it would be slower.
290    #[allow(clippy::needless_range_loop)]
291    for l in 1..=geodesic_order {
292        let m = (geodesic_order - l) / 2;
293        c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1];
294        o += m + 2;
295        d *= eps;
296    }
297}
298
299pub fn _A2m1f(eps: f64, geodesic_order: usize) -> f64 {
300    const COEFF: [f64; 5] = [-11.0, -28.0, -192.0, 0.0, 256.0];
301    let m = geodesic_order / 2;
302    let t = polyval(m, &COEFF, sq(eps)) / COEFF[m + 1];
303    (t - eps) / (1.0 + eps)
304}
305
306pub fn _C2f(eps: f64, c: &mut [f64], geodesic_order: usize) {
307    const COEFF: [f64; 18] = [
308        1.0, 2.0, 16.0, 32.0, 35.0, 64.0, 384.0, 2048.0, 15.0, 80.0, 768.0, 7.0, 35.0, 512.0, 63.0,
309        1280.0, 77.0, 2048.0,
310    ];
311    let eps2 = sq(eps);
312    let mut d = eps;
313    let mut o = 0;
314    // Clippy wants us to turn this into `c.iter_mut().enumerate().take(geodesic_order + 1).skip(1)`
315    // but benching (rust-1.75) shows that it would be slower.
316    #[allow(clippy::needless_range_loop)]
317    for l in 1..=geodesic_order {
318        let m = (geodesic_order - l) / 2;
319        c[l] = d * polyval(m, &COEFF[o..], eps2) / COEFF[o + m + 1];
320        o += m + 2;
321        d *= eps;
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328    use approx::assert_relative_eq;
329    // Results for the assertions are taken by running the python implementation
330
331    #[test]
332    fn test_sincosd() {
333        let res = sincosd(-77.03196);
334        assert_relative_eq!(res.0, -0.9744953925159129);
335        assert_relative_eq!(res.1, 0.22440750870961693);
336
337        let res = sincosd(69.48894);
338        assert_relative_eq!(res.0, 0.9366045700708676);
339        assert_relative_eq!(res.1, 0.3503881837653281);
340        let res = sincosd(-1.0);
341        assert_relative_eq!(res.0, -0.01745240643728351);
342        assert_relative_eq!(res.1, 0.9998476951563913);
343    }
344
345    #[test]
346    fn test__C2f() {
347        let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
348        _C2f(0.12, &mut c, 6);
349        assert_eq!(
350            c,
351            vec![
352                1.0,
353                0.0601087776,
354                0.00270653103,
355                0.000180486,
356                1.4215824e-05,
357                1.22472e-06,
358                1.12266e-07
359            ]
360        )
361    }
362
363    #[test]
364    fn test__A2m1f() {
365        assert_eq!(_A2m1f(0.12, 6), -0.11680607884285714);
366    }
367
368    #[test]
369    fn test__C1pf() {
370        let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
371        _C1pf(0.12, &mut c, 6);
372        assert_eq!(
373            c,
374            vec![
375                1.0,
376                0.059517321000000005,
377                0.004421053215,
378                0.0005074200000000001,
379                6.997613759999999e-05,
380                1.1233080000000001e-05,
381                1.8507366e-06
382            ]
383        )
384    }
385
386    #[test]
387    fn test__C1f() {
388        let mut c = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
389        _C1f(0.12, &mut c, 6);
390        assert_eq!(
391            c,
392            vec![
393                1.0,
394                -0.059676777599999994,
395                -0.000893533122,
396                -3.57084e-05,
397                -2.007504e-06,
398                -1.3607999999999999e-07,
399                -1.0205999999999999e-08
400            ]
401        )
402    }
403
404    #[test]
405    fn test__A1m1f() {
406        assert_eq!(_A1m1f(0.12, 6), 0.1404582405272727);
407    }
408
409    #[test]
410    fn test_astroid() {
411        assert_eq!(astroid(21.0, 12.0), 23.44475767500982);
412    }
413
414    #[test]
415    fn test_sin_cos_series() {
416        assert_eq!(
417            sin_cos_series(
418                false,
419                -0.8928657853278468,
420                0.45032287238256896,
421                &[
422                    0.6660771734724675,
423                    1.5757752625233906e-05,
424                    3.8461688963148916e-09,
425                    1.3040960748120204e-12,
426                    5.252912023008548e-16,
427                    2.367770858285795e-19
428                ],
429            ),
430            0.29993425660538664
431        );
432
433        assert_eq!(
434            sin_cos_series(
435                false,
436                -0.8928657853278468,
437                0.45032287238256896,
438                &[0., 1., 2., 3., 4., 5.],
439            ),
440            1.8998562852254026
441        );
442        assert_eq!(
443            sin_cos_series(
444                true,
445                0.2969032234925426,
446                0.9549075745221299,
447                &[
448                    0.0,
449                    -0.0003561309485314716,
450                    -3.170731714689771e-08,
451                    -7.527972480734327e-12,
452                    -2.5133854116682488e-15,
453                    -1.0025061462383107e-18,
454                    -4.462794158625518e-22
455                ],
456            ),
457            -0.00020196665516199853
458        );
459        assert_eq!(
460            sin_cos_series(
461                true,
462                -0.8928657853278468,
463                0.45032287238256896,
464                &[
465                    0.0,
466                    -0.0003561309485314716,
467                    -3.170731714689771e-08,
468                    -7.527972480734327e-12,
469                    -2.5133854116682488e-15,
470                    -1.0025061462383107e-18,
471                    -4.462794158625518e-22
472                ],
473            ),
474            0.00028635444718997857
475        );
476
477        assert_eq!(
478            sin_cos_series(true, 0.12, 0.21, &[1.0, 2.0]),
479            0.10079999999999999
480        );
481        assert_eq!(
482            sin_cos_series(
483                true,
484                -0.024679833885152578,
485                0.9996954065111039,
486                &[
487                    0.0,
488                    -0.0008355098973052918,
489                    -1.7444619952659748e-07,
490                    -7.286557795511902e-11,
491                    -3.80472772706481e-14,
492                    -2.2251271876594078e-17,
493                    1.2789961247944744e-20
494                ],
495            ),
496            4.124513511893872e-05
497        );
498    }
499
500    // corresponding to tests/signtest.cpp
501    mod sign_test {
502        use super::*;
503        fn is_equiv(x: f64, y: f64) -> bool {
504            (x.is_nan() && y.is_nan()) || (x == y && x.is_sign_positive() == y.is_sign_positive())
505        }
506
507        macro_rules! check_sincosd {
508            ($x: expr, $expected_sin: expr, $expected_cos: expr) => {
509                let (sinx, cosx) = sincosd($x);
510                assert!(
511                    is_equiv(sinx, $expected_sin),
512                    "sinx({}) = {}, but got {}",
513                    $x,
514                    $expected_sin,
515                    sinx
516                );
517                assert!(
518                    is_equiv(cosx, $expected_cos),
519                    "cosx({}) = {}, but got {}",
520                    $x,
521                    $expected_cos,
522                    cosx
523                );
524            };
525        }
526
527        #[test]
528        fn sin_cosd() {
529            check_sincosd!(f64::NEG_INFINITY, f64::NAN, f64::NAN);
530            check_sincosd!(-810.0, -1.0, 0.0);
531            check_sincosd!(-720.0, -0.0, 1.0);
532            check_sincosd!(-630.0, 1.0, 0.0);
533            check_sincosd!(-540.0, -0.0, -1.0);
534            check_sincosd!(-450.0, -1.0, 0.0);
535            check_sincosd!(-360.0, -0.0, 1.0);
536            check_sincosd!(-270.0, 1.0, 0.0);
537            check_sincosd!(-180.0, -0.0, -1.0);
538            check_sincosd!(-90.0, -1.0, 0.0);
539            check_sincosd!(-0.0, -0.0, 1.0);
540            check_sincosd!(0.0, 0.0, 1.0);
541            check_sincosd!(90.0, 1.0, 0.0);
542            check_sincosd!(180.0, 0.0, -1.0);
543            check_sincosd!(270.0, -1.0, 0.0);
544            check_sincosd!(360.0, 0.0, 1.0);
545            check_sincosd!(450.0, 1.0, 0.0);
546            check_sincosd!(540.0, 0.0, -1.0);
547            check_sincosd!(630.0, -1.0, 0.0);
548            check_sincosd!(720.0, 0.0, 1.0);
549            check_sincosd!(810.0, 1.0, 0.0);
550            check_sincosd!(f64::INFINITY, f64::NAN, f64::NAN);
551            check_sincosd!(f64::NAN, f64::NAN, f64::NAN);
552        }
553    }
554}