1use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, FoldWhile, Zip};
17use rayon::iter::{IntoParallelIterator, ParallelExtend, ParallelIterator};
18
19trait Estimator {
20 fn estimate(f_diff: f64) -> f64;
21 fn normalize(v: &mut f64, c: u64);
22}
23
24macro_rules! choose_estimator {
25 ( $estimator_type:expr => $estimator:ident: $code:block ) => {
26 match $estimator_type {
27 'c' => {
28 type $estimator = Cressie;
29
30 $code
31 }
32 _ => {
33 type $estimator = Matheron;
34
35 $code
36 }
37 }
38 };
39}
40
41struct Matheron;
42
43impl Estimator for Matheron {
44 fn estimate(f_diff: f64) -> f64 {
45 f_diff.powi(2)
46 }
47
48 fn normalize(v: &mut f64, c: u64) {
49 let cf = if c == 0 { 1.0 } else { c as f64 };
50 *v /= 2.0 * cf;
51 }
52}
53
54struct Cressie;
55
56impl Estimator for Cressie {
57 fn estimate(f_diff: f64) -> f64 {
58 f_diff.abs().sqrt()
59 }
60
61 fn normalize(v: &mut f64, c: u64) {
62 let cf = if c == 0 { 1.0 } else { c as f64 };
63 *v = 0.5 * (1. / cf * *v).powi(4) / (0.457 + 0.494 / cf + 0.045 / (cf * cf))
64 }
65}
66
67trait Distance {
68 fn dist(lhs: ArrayView1<f64>, rhs: ArrayView1<f64>) -> f64;
69
70 fn check_dim(_dim: usize) {}
71}
72
73macro_rules! choose_distance {
74 ( $distance_type:expr => $distance:ident: $code:block ) => {
75 match $distance_type {
76 'e' => {
77 type $distance = Euclid;
78
79 $code
80 }
81 _ => {
82 type $distance = Haversine;
83
84 $code
85 }
86 }
87 };
88}
89
90struct Euclid;
91
92impl Distance for Euclid {
93 fn dist(lhs: ArrayView1<f64>, rhs: ArrayView1<f64>) -> f64 {
94 Zip::from(lhs)
95 .and(rhs)
96 .fold(0.0, |mut acc, lhs, rhs| {
97 acc += (lhs - rhs).powi(2);
98
99 acc
100 })
101 .sqrt()
102 }
103}
104
105struct Haversine;
106
107impl Distance for Haversine {
108 fn dist(lhs: ArrayView1<f64>, rhs: ArrayView1<f64>) -> f64 {
109 let diff_lat = (lhs[0] - rhs[0]).to_radians();
110 let diff_lon = (lhs[1] - rhs[1]).to_radians();
111
112 let arg = (diff_lat / 2.0).sin().powi(2)
113 + lhs[0].to_radians().cos()
114 * rhs[0].to_radians().cos()
115 * (diff_lon / 2.0).sin().powi(2);
116
117 2.0 * arg.sqrt().atan2((1.0 - arg).sqrt())
118 }
119
120 fn check_dim(dim: usize) {
121 assert_eq!(dim, 2, "Haversine: dim = {dim} != 2");
122 }
123}
124
125pub fn variogram_structured(
137 f: ArrayView2<'_, f64>,
138 estimator_type: char,
139 num_threads: Option<usize>,
140) -> Array1<f64> {
141 fn inner<E: Estimator>(f: ArrayView2<'_, f64>, num_threads: Option<usize>) -> Array1<f64> {
142 let size = f.dim().0;
143
144 let mut variogram = Vec::with_capacity(size);
145
146 variogram.push(0.0);
147
148 rayon::ThreadPoolBuilder::new()
149 .num_threads(num_threads.unwrap_or(rayon::current_num_threads()))
150 .build()
151 .unwrap()
152 .install(|| {
153 variogram.par_extend((1..size).into_par_iter().map(|k| {
154 let mut value = 0.0;
155 let mut count = 0;
156
157 Zip::from(f.slice(s![..size - k, ..]))
158 .and(f.slice(s![k.., ..]))
159 .for_each(|f_i, f_j| {
160 value += E::estimate(f_i - f_j);
161 count += 1;
162 });
163
164 E::normalize(&mut value, count);
165
166 value
167 }))
168 });
169
170 Array1::from_vec(variogram)
171 }
172
173 choose_estimator!(estimator_type => E: {
174 inner::<E>(f, num_threads)
175 })
176}
177
178pub fn variogram_ma_structured(
191 f: ArrayView2<'_, f64>,
192 mask: ArrayView2<'_, bool>,
193 estimator_type: char,
194 num_threads: Option<usize>,
195) -> Array1<f64> {
196 fn inner<E: Estimator>(
197 f: ArrayView2<'_, f64>,
198 mask: ArrayView2<'_, bool>,
199 num_threads: Option<usize>,
200 ) -> Array1<f64> {
201 let size = f.dim().0;
202
203 let mut variogram = Vec::with_capacity(size);
204
205 variogram.push(0.0);
206
207 rayon::ThreadPoolBuilder::new()
208 .num_threads(num_threads.unwrap_or(rayon::current_num_threads()))
209 .build()
210 .unwrap()
211 .install(|| {
212 variogram.par_extend((1..size).into_par_iter().map(|k| {
213 let mut value = 0.0;
214 let mut count = 0;
215
216 Zip::from(f.slice(s![..size - k, ..]))
217 .and(f.slice(s![k.., ..]))
218 .and(mask.slice(s![..size - k, ..]))
219 .and(mask.slice(s![k.., ..]))
220 .for_each(|f_i, f_j, m_i, m_j| {
221 if *m_i || *m_j {
222 return;
223 }
224
225 value += E::estimate(f_i - f_j);
226 count += 1;
227 });
228
229 E::normalize(&mut value, count);
230
231 value
232 }))
233 });
234
235 Array1::from_vec(variogram)
236 }
237
238 choose_estimator!(estimator_type => E: {
239 inner::<E>(f, mask, num_threads)
240 })
241}
242
243fn dir_test(
244 dir: ArrayView1<'_, f64>,
245 pos_i: ArrayView1<'_, f64>,
246 pos_j: ArrayView1<'_, f64>,
247 dist: f64,
248 angles_tol: f64,
249 bandwidth: f64,
250) -> bool {
251 let s_prod = Zip::from(dir)
253 .and(pos_i)
254 .and(pos_j)
255 .fold(0.0, |mut acc, dir, pos_i, pos_j| {
256 acc += (pos_i - pos_j) * dir;
257
258 acc
259 });
260
261 if bandwidth > 0.0 {
263 let b_dist = Zip::from(dir)
264 .and(pos_i)
265 .and(pos_j)
266 .fold(0.0, |mut acc, dir, pos_i, pos_j| {
267 acc += ((pos_i - pos_j) - s_prod * dir).powi(2);
268
269 acc
270 })
271 .sqrt();
272
273 if b_dist >= bandwidth {
274 return false;
275 }
276 }
277
278 if dist > 0.0 {
280 let angle = s_prod.abs() / dist;
282 if angle < 1.0 {
283 if angle.acos() >= angles_tol {
285 return false;
286 }
287 }
288 }
289
290 true
291}
292
293#[allow(clippy::too_many_arguments)]
315pub fn variogram_directional(
316 f: ArrayView2<'_, f64>,
317 bin_edges: ArrayView1<'_, f64>,
318 pos: ArrayView2<'_, f64>,
319 direction: ArrayView2<'_, f64>, angles_tol: f64,
321 bandwidth: f64,
322 separate_dirs: bool,
323 estimator_type: char,
324 num_threads: Option<usize>,
325) -> (Array2<f64>, Array2<u64>) {
326 assert_eq!(
327 pos.dim().0,
328 direction.dim().1,
329 "dim(pos) = {} != dim(direction) = {}",
330 pos.dim().0,
331 direction.dim().1,
332 );
333 assert_eq!(
334 pos.dim().1,
335 f.dim().1,
336 "len(pos) = {} != len(f) = {}",
337 pos.dim().1,
338 f.dim().1,
339 );
340 assert!(
341 bin_edges.dim() > 1,
342 "len(bin_edges) = {} < 2 too small",
343 bin_edges.dim()
344 );
345 assert!(
346 angles_tol > 0.0,
347 "tolerance for angle search masks must be > 0",
348 );
349
350 fn inner<E: Estimator>(
351 f: ArrayView2<'_, f64>,
352 bin_edges: ArrayView1<'_, f64>,
353 pos: ArrayView2<'_, f64>,
354 direction: ArrayView2<'_, f64>,
355 angles_tol: f64,
356 bandwidth: f64,
357 separate_dirs: bool,
358 num_threads: Option<usize>,
359 ) -> (Array2<f64>, Array2<u64>) {
360 let out_size = (direction.dim().0, bin_edges.dim() - 1);
361 let in_size = pos.dim().1 - 1;
362
363 let mut variogram = Array2::<f64>::zeros(out_size);
364 let mut counts = Array2::<u64>::zeros(out_size);
365
366 rayon::ThreadPoolBuilder::new()
367 .num_threads(num_threads.unwrap_or(rayon::current_num_threads()))
368 .build()
369 .unwrap()
370 .install(|| {
371 Zip::from(bin_edges.slice(s![..out_size.1]))
372 .and(bin_edges.slice(s![1..]))
373 .and(variogram.columns_mut())
374 .and(counts.columns_mut())
375 .par_for_each(
376 |lower_bin_edge, upper_bin_edge, mut variogram, mut counts| {
377 Zip::indexed(f.slice(s![.., ..in_size]).columns())
378 .and(pos.slice(s![.., ..in_size]).columns())
379 .for_each(|i, f_i, pos_i| {
380 Zip::from(f.slice(s![.., i + 1..]).columns())
381 .and(pos.slice(s![.., i + 1..]).columns())
382 .for_each(|f_j, pos_j| {
383 let dist = Euclid::dist(pos_i, pos_j);
384 if dist < *lower_bin_edge || dist >= *upper_bin_edge {
385 return; }
387
388 Zip::from(direction.rows())
389 .and(&mut variogram)
390 .and(&mut counts)
391 .fold_while((), |(), dir, variogram, counts| {
392 if !dir_test(
393 dir, pos_i, pos_j, dist, angles_tol,
394 bandwidth,
395 ) {
396 return FoldWhile::Continue(());
397 }
398
399 Zip::from(f_i).and(f_j).for_each(|f_i, f_j| {
400 let f_ij = f_i - f_j;
401 if f_ij.is_nan() {
402 return; }
404
405 *counts += 1;
406 *variogram += E::estimate(f_ij);
407 });
408
409 if separate_dirs {
412 return FoldWhile::Done(());
413 }
414
415 FoldWhile::Continue(())
416 });
417 });
418 });
419
420 Zip::from(variogram)
421 .and(counts)
422 .for_each(|variogram, counts| {
423 E::normalize(variogram, *counts);
424 });
425 },
426 )
427 });
428
429 (variogram, counts)
430 }
431
432 choose_estimator!(estimator_type => E: {
433 inner::<E>(
434 f,
435 bin_edges,
436 pos,
437 direction,
438 angles_tol,
439 bandwidth,
440 separate_dirs,
441 num_threads,
442 )
443 })
444}
445
446pub fn variogram_unstructured(
466 f: ArrayView2<'_, f64>,
467 bin_edges: ArrayView1<'_, f64>,
468 pos: ArrayView2<'_, f64>,
469 estimator_type: char,
470 distance_type: char,
471 num_threads: Option<usize>,
472) -> (Array1<f64>, Array1<u64>) {
473 assert_eq!(
474 pos.dim().1,
475 f.dim().1,
476 "len(pos) = {} != len(f) = {}",
477 pos.dim().1,
478 f.dim().1,
479 );
480 assert!(
481 bin_edges.dim() > 1,
482 "len(bin_edges) = {} < 2 too small",
483 bin_edges.dim()
484 );
485
486 fn inner<E: Estimator, D: Distance>(
487 f: ArrayView2<'_, f64>,
488 bin_edges: ArrayView1<'_, f64>,
489 pos: ArrayView2<'_, f64>,
490 num_threads: Option<usize>,
491 ) -> (Array1<f64>, Array1<u64>) {
492 D::check_dim(pos.dim().0);
493
494 let out_size = bin_edges.dim() - 1;
495 let in_size = pos.dim().1 - 1;
496
497 let mut variogram = Array1::<f64>::zeros(out_size);
498 let mut counts = Array1::<u64>::zeros(out_size);
499
500 rayon::ThreadPoolBuilder::new()
501 .num_threads(num_threads.unwrap_or(rayon::current_num_threads()))
502 .build()
503 .unwrap()
504 .install(|| {
505 Zip::from(bin_edges.slice(s![..out_size]))
506 .and(bin_edges.slice(s![1..]))
507 .and(&mut variogram)
508 .and(&mut counts)
509 .par_for_each(|lower_bin_edge, upper_bin_edge, variogram, counts| {
510 Zip::indexed(f.slice(s![.., ..in_size]).columns())
511 .and(pos.slice(s![.., ..in_size]).columns())
512 .for_each(|i, f_i, pos_i| {
513 Zip::from(f.slice(s![.., i + 1..]).columns())
514 .and(pos.slice(s![.., i + 1..]).columns())
515 .for_each(|f_j, pos_j| {
516 let dist = D::dist(pos_i, pos_j);
517 if dist < *lower_bin_edge || dist >= *upper_bin_edge {
518 return; }
520
521 Zip::from(f_i).and(f_j).for_each(|f_i, f_j| {
522 let f_ij = f_i - f_j;
523 if f_ij.is_nan() {
524 return; }
526
527 *counts += 1;
528 *variogram += E::estimate(f_ij);
529 });
530 });
531 });
532
533 E::normalize(variogram, *counts);
534 })
535 });
536
537 (variogram, counts)
538 }
539
540 choose_estimator!(estimator_type => E: {
541 choose_distance!(distance_type => D: {
542 inner::<E, D>(f, bin_edges, pos, num_threads)
543 })
544 })
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550
551 use approx::assert_ulps_eq;
552 use ndarray::{arr1, arr2, stack, Axis};
553
554 struct SetupStruct {
555 field: Array2<f64>,
556 }
557
558 impl SetupStruct {
559 fn new() -> Self {
560 Self {
561 field: arr2(&[
562 [41.2],
563 [40.2],
564 [39.7],
565 [39.2],
566 [40.1],
567 [38.3],
568 [39.1],
569 [40.0],
570 [41.1],
571 [40.3],
572 ]),
573 }
574 }
575 }
576
577 #[test]
578 fn test_variogram_struct() {
579 let setup = SetupStruct::new();
580
581 assert_ulps_eq!(
582 variogram_structured(setup.field.view(), 'm', None),
583 arr1(&[
584 0.0,
585 0.49166666666666814,
586 0.7625000000000011,
587 1.090714285714288,
588 0.9016666666666685,
589 1.3360000000000025,
590 0.9524999999999989,
591 0.4349999999999996,
592 0.004999999999999788,
593 0.40500000000000513
594 ]),
595 max_ulps = 6
596 );
597 }
598
599 #[test]
600 fn test_variogram_ma_struct() {
601 let setup = SetupStruct::new();
602 let mask1 = arr2(&[
603 [false],
604 [false],
605 [false],
606 [false],
607 [false],
608 [false],
609 [false],
610 [false],
611 [false],
612 [false],
613 ]);
614 let mask2 = arr2(&[
615 [true],
616 [false],
617 [false],
618 [false],
619 [false],
620 [false],
621 [false],
622 [false],
623 [false],
624 [false],
625 ]);
626
627 assert_ulps_eq!(
628 variogram_ma_structured(setup.field.view(), mask1.view(), 'm', None),
629 arr1(&[
630 0.0,
631 0.49166666666666814,
632 0.7625000000000011,
633 1.090714285714288,
634 0.9016666666666685,
635 1.3360000000000025,
636 0.9524999999999989,
637 0.4349999999999996,
638 0.004999999999999788,
639 0.40500000000000513
640 ]),
641 max_ulps = 6
642 );
643 assert_ulps_eq!(
644 variogram_ma_structured(setup.field.view(), mask2.view(), 'm', None),
645 arr1(&[
646 0.0,
647 0.4906250000000017,
648 0.710714285714287,
649 0.9391666666666693,
650 0.9610000000000019,
651 0.6187499999999992,
652 0.5349999999999975,
653 0.29249999999999765,
654 0.004999999999999432,
655 0.0
656 ]),
657 max_ulps = 6
658 );
659 }
660
661 struct SetupUnstruct {
662 pos: Array2<f64>,
663 field: Array2<f64>,
664 bin_edges: Array1<f64>,
665 }
666
667 impl SetupUnstruct {
668 fn new() -> Self {
669 Self {
670 pos: stack![
671 Axis(0),
672 Array1::range(0., 10., 1.),
673 Array1::range(0., 10., 1.)
674 ],
675 field: arr2(&[[
676 -1.2427955,
677 -0.59811704,
678 -0.57745039,
679 0.01531904,
680 -0.26474262,
681 -0.53626347,
682 -0.85106795,
683 -1.96939178,
684 -1.83650493,
685 -1.23548617,
686 ]]),
687 bin_edges: Array1::linspace(0., 5., 4),
688 }
689 }
690 }
691
692 #[test]
693 fn test_variogram_unstruct() {
694 let setup = SetupUnstruct::new();
695 let (gamma, cnts) = variogram_unstructured(
696 setup.field.view(),
697 setup.bin_edges.view(),
698 setup.pos.view(),
699 'm',
700 'e',
701 None,
702 );
703 assert_ulps_eq!(
704 gamma,
705 arr1(&[0.14712242466045536, 0.320522186616688, 0.5136105328106929]),
706 max_ulps = 6,
707 );
708 assert_eq!(cnts, arr1(&[9, 8, 7]),);
709 }
710
711 #[test]
712 fn test_variogram_unstruct_multi_field() {
713 let setup = SetupUnstruct::new();
714 let field2 = arr2(&[[
715 1.2427955,
716 1.59811704,
717 1.57745039,
718 -1.01531904,
719 1.26474262,
720 1.53626347,
721 1.85106795,
722 0.96939178,
723 0.83650493,
724 0.23548617,
725 ]]);
726 let field_multi = arr2(&[
727 [
728 -1.2427955,
729 -0.59811704,
730 -0.57745039,
731 0.01531904,
732 -0.26474262,
733 -0.53626347,
734 -0.85106795,
735 -1.96939178,
736 -1.83650493,
737 -1.23548617,
738 ],
739 [
740 1.2427955,
741 1.59811704,
742 1.57745039,
743 -1.01531904,
744 1.26474262,
745 1.53626347,
746 1.85106795,
747 0.96939178,
748 0.83650493,
749 0.23548617,
750 ],
751 ]);
752 let (gamma, _) = variogram_unstructured(
753 setup.field.view(),
754 setup.bin_edges.view(),
755 setup.pos.view(),
756 'm',
757 'e',
758 None,
759 );
760 let (gamma2, _) = variogram_unstructured(
761 field2.view(),
762 setup.bin_edges.view(),
763 setup.pos.view(),
764 'm',
765 'e',
766 None,
767 );
768 let (gamma_multi, _) = variogram_unstructured(
769 field_multi.view(),
770 setup.bin_edges.view(),
771 setup.pos.view(),
772 'm',
773 'e',
774 None,
775 );
776 let gamma_single = 0.5 * (&gamma + &gamma2);
777 assert_ulps_eq!(gamma_multi, gamma_single, max_ulps = 6,);
778
779 let direction = arr2(&[[0., std::f64::consts::PI], [0., 0.]]);
780 let (gamma, _) = variogram_directional(
781 setup.field.view(),
782 setup.bin_edges.view(),
783 setup.pos.view(),
784 direction.view(),
785 std::f64::consts::PI / 8.,
786 -1.0,
787 false,
788 'm',
789 None,
790 );
791 let (gamma2, _) = variogram_directional(
792 field2.view(),
793 setup.bin_edges.view(),
794 setup.pos.view(),
795 direction.view(),
796 std::f64::consts::PI / 8.,
797 -1.0,
798 false,
799 'm',
800 None,
801 );
802 let (gamma_multi, _) = variogram_directional(
803 field_multi.view(),
804 setup.bin_edges.view(),
805 setup.pos.view(),
806 direction.view(),
807 std::f64::consts::PI / 8.,
808 -1.0,
809 false,
810 'm',
811 None,
812 );
813
814 let gamma_single = 0.5 * (&gamma + &gamma2);
815 assert_ulps_eq!(gamma_multi, gamma_single, max_ulps = 6,);
816 }
817
818 #[test]
819 fn test_variogram_directional() {
820 let setup = SetupUnstruct::new();
821 let direction = arr2(&[[0., std::f64::consts::PI], [0., 0.]]);
822 let (gamma, cnts) = variogram_directional(
823 setup.field.view(),
824 setup.bin_edges.view(),
825 setup.pos.view(),
826 direction.view(),
827 std::f64::consts::PI / 8.,
828 -1.0,
829 false,
830 'm',
831 None,
832 );
833 assert_ulps_eq!(
834 gamma,
835 arr2(&[
836 [0.14712242466045536, 0.320522186616688, 0.5136105328106929],
837 [0., 0., 0.]
838 ]),
839 max_ulps = 6,
840 );
841 assert_eq!(cnts, arr2(&[[9, 8, 7], [0, 0, 0]]),);
842 }
843}