1use ferray_core::dimension::{Dimension, IxDyn};
32use ferray_core::dtype::Element;
33use ferray_core::error::{FerrayError, FerrayResult};
34use ferray_core::{Array, Ix1, Ix2};
35use num_traits::Float;
36
37use crate::MaskedArray;
38
39pub const NOMASK: bool = false;
46
47impl<T, D> MaskedArray<T, D>
52where
53 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
54 D: Dimension,
55{
56 pub fn prod(&self) -> FerrayResult<T> {
61 let one = num_traits::one::<T>();
62 let p = self
63 .data()
64 .iter()
65 .zip(self.mask().iter())
66 .filter(|(_, m)| !**m)
67 .fold(one, |acc, (v, _)| acc * *v);
68 Ok(p)
69 }
70}
71
72impl<T, D> MaskedArray<T, D>
73where
74 T: Element + Copy + std::ops::Add<Output = T> + num_traits::Zero,
75 D: Dimension,
76{
77 pub fn cumsum_flat(&self) -> FerrayResult<MaskedArray<T, Ix1>> {
84 let zero = num_traits::zero::<T>();
85 let n = self.size();
86 let data: Vec<T> = self
87 .data()
88 .iter()
89 .zip(self.mask().iter())
90 .scan(zero, |acc, (v, m)| {
91 if !*m {
92 *acc = *acc + *v;
93 }
94 Some(*acc)
95 })
96 .collect();
97 let mask: Vec<bool> = self.mask().iter().copied().collect();
98 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
99 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
100 MaskedArray::new(data_arr, mask_arr)
101 }
102}
103
104impl<T, D> MaskedArray<T, D>
105where
106 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
107 D: Dimension,
108{
109 pub fn cumprod_flat(&self) -> FerrayResult<MaskedArray<T, Ix1>> {
115 let one = num_traits::one::<T>();
116 let n = self.size();
117 let data: Vec<T> = self
118 .data()
119 .iter()
120 .zip(self.mask().iter())
121 .scan(one, |acc, (v, m)| {
122 if !*m {
123 *acc = *acc * *v;
124 }
125 Some(*acc)
126 })
127 .collect();
128 let mask: Vec<bool> = self.mask().iter().copied().collect();
129 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
130 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
131 MaskedArray::new(data_arr, mask_arr)
132 }
133}
134
135impl<T, D> MaskedArray<T, D>
136where
137 T: Element + Copy + PartialOrd,
138 D: Dimension,
139{
140 pub fn argmin(&self) -> FerrayResult<usize> {
145 self.data()
146 .iter()
147 .zip(self.mask().iter())
148 .enumerate()
149 .filter(|(_, (_, m))| !**m)
150 .min_by(|(_, (a, _)), (_, (b, _))| {
151 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
152 })
153 .map(|(i, _)| i)
154 .ok_or_else(|| FerrayError::invalid_value("argmin: all elements are masked"))
155 }
156
157 pub fn argmax(&self) -> FerrayResult<usize> {
162 self.data()
163 .iter()
164 .zip(self.mask().iter())
165 .enumerate()
166 .filter(|(_, (_, m))| !**m)
167 .max_by(|(_, (a, _)), (_, (b, _))| {
168 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
169 })
170 .map(|(i, _)| i)
171 .ok_or_else(|| FerrayError::invalid_value("argmax: all elements are masked"))
172 }
173
174 pub fn ptp(&self) -> FerrayResult<T>
179 where
180 T: std::ops::Sub<Output = T>,
181 {
182 let mut iter = self
183 .data()
184 .iter()
185 .zip(self.mask().iter())
186 .filter(|(_, m)| !**m)
187 .map(|(v, _)| *v);
188 let first = iter
189 .next()
190 .ok_or_else(|| FerrayError::invalid_value("ptp: all elements are masked"))?;
191 let mut lo = first;
192 let mut hi = first;
193 for v in iter {
194 if v < lo {
195 lo = v;
196 }
197 if v > hi {
198 hi = v;
199 }
200 }
201 Ok(hi - lo)
202 }
203}
204
205impl<T, D> MaskedArray<T, D>
206where
207 T: Element
208 + Copy
209 + PartialOrd
210 + num_traits::FromPrimitive
211 + std::ops::Add<Output = T>
212 + std::ops::Div<Output = T>,
213 D: Dimension,
214{
215 pub fn median(&self) -> FerrayResult<T> {
221 let mut vals: Vec<T> = self
222 .data()
223 .iter()
224 .zip(self.mask().iter())
225 .filter(|(_, m)| !**m)
226 .map(|(v, _)| *v)
227 .collect();
228 if vals.is_empty() {
229 return Err(FerrayError::invalid_value(
230 "median: all elements are masked",
231 ));
232 }
233 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
234 let n = vals.len();
235 if n % 2 == 1 {
236 Ok(vals[n / 2])
237 } else {
238 let two = T::from_u8(2).unwrap();
239 Ok((vals[n / 2 - 1] + vals[n / 2]) / two)
240 }
241 }
242}
243
244impl<T, D> MaskedArray<T, D>
245where
246 T: Element + Copy + Float,
247 D: Dimension,
248{
249 pub fn average(&self, weights: Option<&Array<T, D>>) -> FerrayResult<T> {
259 let Some(w) = weights else {
260 return self.mean();
261 };
262 if w.shape() != self.shape() {
263 return Err(FerrayError::shape_mismatch(format!(
264 "average: weights shape {:?} differs from array shape {:?}",
265 w.shape(),
266 self.shape(),
267 )));
268 }
269 let zero = <T as num_traits::Zero>::zero();
270 let mut wsum = zero;
271 let mut acc = zero;
272 for ((v, m), wi) in self.data().iter().zip(self.mask().iter()).zip(w.iter()) {
273 if !*m {
274 wsum = wsum + *wi;
275 acc = acc + *v * *wi;
276 }
277 }
278 if wsum == zero {
279 return Err(FerrayError::invalid_value(
280 "average: weight sum is zero (or all elements are masked)",
281 ));
282 }
283 Ok(acc / wsum)
284 }
285
286 pub fn anom(&self) -> FerrayResult<MaskedArray<T, D>> {
294 let m = self.mean()?;
295 let data: Vec<T> = self.data().iter().map(|v| *v - m).collect();
296 let data_arr = Array::from_vec(self.data().dim().clone(), data)?;
297 let mask_arr: Array<bool, D> = Array::from_vec(
298 self.mask().dim().clone(),
299 self.mask().iter().copied().collect(),
300 )?;
301 MaskedArray::new(data_arr, mask_arr)
302 }
303}
304
305pub fn masked_all<T, D>(shape: D) -> FerrayResult<MaskedArray<T, D>>
316where
317 T: Element + Copy,
318 D: Dimension,
319{
320 let data = Array::<T, D>::from_elem(shape.clone(), T::zero())?;
321 let mask = Array::<bool, D>::from_elem(shape, true)?;
322 MaskedArray::new(data, mask)
323}
324
325pub fn masked_all_like<T, U, D>(reference: &Array<U, D>) -> FerrayResult<MaskedArray<T, D>>
332where
333 T: Element + Copy,
334 U: Element,
335 D: Dimension,
336{
337 masked_all(reference.dim().clone())
338}
339
340pub fn masked_values<T, D>(
349 arr: &Array<T, D>,
350 value: T,
351 rtol: T,
352 atol: T,
353) -> FerrayResult<MaskedArray<T, D>>
354where
355 T: Element + Copy + Float,
356 D: Dimension,
357{
358 let threshold = atol + rtol * value.abs();
359 let mask: Vec<bool> = arr
360 .iter()
361 .map(|x| (*x - value).abs() <= threshold)
362 .collect();
363 let mask_arr = Array::from_vec(arr.dim().clone(), mask)?;
364 let data_arr = arr.clone();
365 MaskedArray::new(data_arr, mask_arr)
366}
367
368pub fn mask_or<D: Dimension>(
383 a: &Array<bool, D>,
384 b: &Array<bool, D>,
385) -> FerrayResult<Array<bool, D>> {
386 if a.shape() != b.shape() {
387 return Err(FerrayError::shape_mismatch(format!(
388 "mask_or: shapes {:?} and {:?} differ",
389 a.shape(),
390 b.shape()
391 )));
392 }
393 let data: Vec<bool> = a.iter().zip(b.iter()).map(|(x, y)| *x || *y).collect();
394 Array::from_vec(a.dim().clone(), data)
395}
396
397pub fn make_mask<D: Dimension>(values: &[bool], shape: D) -> FerrayResult<Array<bool, D>> {
405 Array::from_vec(shape, values.to_vec())
406}
407
408pub fn make_mask_none<D: Dimension>(shape: D) -> FerrayResult<Array<bool, D>> {
415 Array::from_elem(shape, false)
416}
417
418pub fn ma_concatenate<T>(
431 a: &MaskedArray<T, IxDyn>,
432 b: &MaskedArray<T, IxDyn>,
433 axis: usize,
434) -> FerrayResult<MaskedArray<T, IxDyn>>
435where
436 T: Element + Copy,
437{
438 let cat_data =
439 ferray_core::manipulation::concatenate(&[a.data().clone(), b.data().clone()], axis)?;
440 let cat_mask =
441 ferray_core::manipulation::concatenate(&[a.mask().clone(), b.mask().clone()], axis)?;
442 MaskedArray::new(cat_data, cat_mask)
443}
444
445impl<T, D> MaskedArray<T, D>
446where
447 T: Element + Copy + PartialOrd,
448 D: Dimension,
449{
450 pub fn clip(&self, a_min: T, a_max: T) -> FerrayResult<MaskedArray<T, D>> {
456 let data: Vec<T> = self
457 .data()
458 .iter()
459 .zip(self.mask().iter())
460 .map(|(v, m)| {
461 if *m {
462 *v
463 } else if *v < a_min {
464 a_min
465 } else if *v > a_max {
466 a_max
467 } else {
468 *v
469 }
470 })
471 .collect();
472 let data_arr = Array::from_vec(self.data().dim().clone(), data)?;
473 let mask_arr: Array<bool, D> = Array::from_vec(
474 self.mask().dim().clone(),
475 self.mask().iter().copied().collect(),
476 )?;
477 MaskedArray::new(data_arr, mask_arr)
478 }
479}
480
481impl<T, D> MaskedArray<T, D>
482where
483 T: Element + Copy,
484 D: Dimension,
485{
486 pub fn repeat(&self, repeats: usize) -> FerrayResult<MaskedArray<T, Ix1>> {
494 let n = self.size() * repeats;
495 let mut data = Vec::with_capacity(n);
496 let mut mask = Vec::with_capacity(n);
497 for (v, m) in self.data().iter().zip(self.mask().iter()) {
498 for _ in 0..repeats {
499 data.push(*v);
500 mask.push(*m);
501 }
502 }
503 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
504 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
505 MaskedArray::new(data_arr, mask_arr)
506 }
507
508 pub fn atleast_1d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
513 let shape = self.shape();
514 let new_shape: Vec<usize> = if shape.is_empty() {
515 vec![1]
516 } else {
517 shape.to_vec()
518 };
519 let data: Vec<T> = self.data().iter().copied().collect();
520 let mask: Vec<bool> = self.mask().iter().copied().collect();
521 let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
522 let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
523 MaskedArray::new(data_arr, mask_arr)
524 }
525
526 pub fn atleast_2d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
531 let shape = self.shape();
532 let new_shape: Vec<usize> = match shape.len() {
533 0 => vec![1, 1],
534 1 => vec![1, shape[0]],
535 _ => shape.to_vec(),
536 };
537 let data: Vec<T> = self.data().iter().copied().collect();
538 let mask: Vec<bool> = self.mask().iter().copied().collect();
539 let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
540 let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
541 MaskedArray::new(data_arr, mask_arr)
542 }
543
544 pub fn atleast_3d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
549 let shape = self.shape();
550 let new_shape: Vec<usize> = match shape.len() {
551 0 => vec![1, 1, 1],
552 1 => vec![1, shape[0], 1],
553 2 => vec![shape[0], shape[1], 1],
554 _ => shape.to_vec(),
555 };
556 let data: Vec<T> = self.data().iter().copied().collect();
557 let mask: Vec<bool> = self.mask().iter().copied().collect();
558 let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
559 let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
560 MaskedArray::new(data_arr, mask_arr)
561 }
562
563 pub fn expand_dims(&self, axis: usize) -> FerrayResult<MaskedArray<T, IxDyn>> {
568 let data_exp = ferray_core::manipulation::expand_dims(self.data(), axis)?;
569 let mask_exp = ferray_core::manipulation::expand_dims(self.mask(), axis)?;
570 MaskedArray::new(data_exp, mask_exp)
571 }
572}
573
574impl<T> MaskedArray<T, Ix2>
575where
576 T: Element + Copy + num_traits::Zero,
577{
578 pub fn diagonal(&self, k: isize) -> FerrayResult<MaskedArray<T, Ix1>> {
585 let shape = self.shape();
586 let (rows, cols) = (shape[0], shape[1]);
587 let (start_r, start_c) = if k >= 0 {
588 (0usize, k as usize)
589 } else {
590 (-k as usize, 0usize)
591 };
592 let mut data = Vec::new();
593 let mut mask = Vec::new();
594 let data_slice = self.data().as_slice();
595 let mask_slice = self.mask().as_slice();
596 let mut r = start_r;
597 let mut c = start_c;
598 while r < rows && c < cols {
599 let flat = r * cols + c;
600 if let (Some(ds), Some(ms)) = (data_slice, mask_slice) {
602 data.push(ds[flat]);
603 mask.push(ms[flat]);
604 } else {
605 data.push(*self.data().iter().nth(flat).expect("flat index in bounds"));
606 mask.push(*self.mask().iter().nth(flat).expect("flat index in bounds"));
607 }
608 r += 1;
609 c += 1;
610 }
611 let n = data.len();
612 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
613 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
614 MaskedArray::new(data_arr, mask_arr)
615 }
616}
617
618impl<T, D> MaskedArray<T, D>
623where
624 T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
625 D: Dimension,
626{
627 pub fn ma_dot_flat(&self, other: &MaskedArray<T, D>) -> FerrayResult<T> {
633 if self.size() != other.size() {
634 return Err(FerrayError::shape_mismatch(format!(
635 "ma_dot_flat: lhs.size()={} != rhs.size()={}",
636 self.size(),
637 other.size(),
638 )));
639 }
640 let zero = <T as num_traits::Zero>::zero();
641 let s = self
642 .data()
643 .iter()
644 .zip(self.mask().iter())
645 .zip(other.data().iter().zip(other.mask().iter()))
646 .fold(
647 zero,
648 |acc, ((a, ma), (b, mb))| {
649 if *ma || *mb { acc } else { acc + *a * *b }
650 },
651 );
652 Ok(s)
653 }
654}
655
656impl<T> MaskedArray<T, Ix2>
657where
658 T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
659{
660 pub fn trace(&self, k: isize) -> FerrayResult<T> {
663 let diag = self.diagonal(k)?;
664 let zero = <T as num_traits::Zero>::zero();
665 let s = diag
666 .data()
667 .iter()
668 .zip(diag.mask().iter())
669 .filter(|(_, m)| !**m)
670 .fold(zero, |acc, (v, _)| acc + *v);
671 Ok(s)
672 }
673}
674
675pub fn ma_unique<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<T, Ix1>>
687where
688 T: Element + Copy + PartialOrd,
689 D: Dimension,
690{
691 let mut vals: Vec<T> = ma
692 .data()
693 .iter()
694 .zip(ma.mask().iter())
695 .filter(|(_, m)| !**m)
696 .map(|(v, _)| *v)
697 .collect();
698 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
699 vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
700 let n = vals.len();
701 Array::from_vec(Ix1::new([n]), vals)
702}
703
704pub fn ma_isin<T, D>(
714 ma: &MaskedArray<T, D>,
715 test_values: &[T],
716) -> FerrayResult<MaskedArray<bool, D>>
717where
718 T: Element + Copy + PartialEq,
719 D: Dimension,
720{
721 let data: Vec<bool> = ma
722 .data()
723 .iter()
724 .map(|v| test_values.iter().any(|t| t == v))
725 .collect();
726 let data_arr = Array::from_vec(ma.data().dim().clone(), data)?;
727 let mask_arr: Array<bool, D> =
728 Array::from_vec(ma.mask().dim().clone(), ma.mask().iter().copied().collect())?;
729 MaskedArray::new(data_arr, mask_arr)
730}
731
732pub fn ma_in1d<T>(
739 ma: &MaskedArray<T, Ix1>,
740 test_values: &[T],
741) -> FerrayResult<MaskedArray<bool, Ix1>>
742where
743 T: Element + Copy + PartialEq,
744{
745 ma_isin(ma, test_values)
746}
747
748pub fn ma_apply_along_axis<T, F>(
763 ma: &MaskedArray<T, IxDyn>,
764 axis: usize,
765 mut f: F,
766) -> FerrayResult<MaskedArray<T, IxDyn>>
767where
768 T: Element + Copy,
769 F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
770{
771 let shape = ma.shape().to_vec();
772 if axis >= shape.len() {
773 return Err(FerrayError::axis_out_of_bounds(axis, shape.len()));
774 }
775 let lane_len = shape[axis];
776 let out_shape: Vec<usize> = shape
777 .iter()
778 .enumerate()
779 .filter(|&(i, _)| i != axis)
780 .map(|(_, &s)| s)
781 .collect();
782 let out_size: usize = out_shape.iter().product::<usize>().max(1);
783
784 let ndim = shape.len();
785 let mut strides = vec![1usize; ndim];
786 for i in (0..ndim.saturating_sub(1)).rev() {
787 strides[i] = strides[i + 1] * shape[i + 1];
788 }
789
790 let data: Vec<T> = ma.data().iter().copied().collect();
791 let mask: Vec<bool> = ma.mask().iter().copied().collect();
792
793 let mut out_data = Vec::with_capacity(out_size);
794 let mut out_mask = Vec::with_capacity(out_size);
795 let mut out_multi = vec![0usize; out_shape.len()];
796
797 for _ in 0..out_size {
798 let mut lane_data = Vec::with_capacity(lane_len);
800 let mut lane_mask = Vec::with_capacity(lane_len);
801 let mut full_idx = vec![0usize; ndim];
802 let mut oi = 0;
804 for (d, slot) in full_idx.iter_mut().enumerate() {
805 if d == axis {
806 continue;
807 }
808 *slot = out_multi[oi];
809 oi += 1;
810 }
811 for j in 0..lane_len {
812 full_idx[axis] = j;
813 let flat: usize = full_idx
814 .iter()
815 .zip(strides.iter())
816 .map(|(i, s)| i * s)
817 .sum();
818 lane_data.push(data[flat]);
819 lane_mask.push(mask[flat]);
820 }
821 let lane_data_arr = Array::from_vec(Ix1::new([lane_len]), lane_data)?;
822 let lane_mask_arr = Array::from_vec(Ix1::new([lane_len]), lane_mask)?;
823 let lane_ma = MaskedArray::new(lane_data_arr, lane_mask_arr)?;
824 let (val, masked) = f(&lane_ma)?;
825 out_data.push(val);
826 out_mask.push(masked);
827
828 for d in (0..out_shape.len()).rev() {
830 out_multi[d] += 1;
831 if out_multi[d] < out_shape[d] {
832 break;
833 }
834 out_multi[d] = 0;
835 }
836 }
837
838 let data_arr = Array::from_vec(IxDyn::new(&out_shape), out_data)?;
839 let mask_arr = Array::from_vec(IxDyn::new(&out_shape), out_mask)?;
840 MaskedArray::new(data_arr, mask_arr)
841}
842
843pub fn ma_apply_over_axes<T, F>(
851 ma: &MaskedArray<T, IxDyn>,
852 axes: &[usize],
853 mut f: F,
854) -> FerrayResult<MaskedArray<T, IxDyn>>
855where
856 T: Element + Copy,
857 F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
858{
859 let mut result = ma.clone();
860 let mut sorted: Vec<usize> = axes.to_vec();
861 sorted.sort_unstable();
862 for (offset, &ax) in sorted.iter().enumerate() {
863 let adjusted = ax.saturating_sub(offset);
866 result = ma_apply_along_axis(&result, adjusted, &mut f)?;
867 }
868 Ok(result)
869}
870
871pub fn ma_vander<T>(x: &MaskedArray<T, Ix1>, n: Option<usize>) -> FerrayResult<MaskedArray<T, Ix2>>
877where
878 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
879{
880 let m = x.size();
881 if m == 0 {
882 return Err(FerrayError::invalid_value(
883 "ma_vander: input must not be empty",
884 ));
885 }
886 let cols = n.unwrap_or(m);
887 let xs: Vec<T> = x.data().iter().copied().collect();
888 let xmask: Vec<bool> = x.mask().iter().copied().collect();
889 let one = num_traits::one::<T>();
890 let mut data = vec![one; m * cols];
891 let mut mask = vec![false; m * cols];
892 for (i, &xi) in xs.iter().enumerate() {
893 let mut acc = one;
894 let mut powers = Vec::with_capacity(cols);
895 for _ in 0..cols {
896 powers.push(acc);
897 acc = acc * xi;
898 }
899 for (j, p) in powers.iter().enumerate() {
900 data[i * cols + (cols - 1 - j)] = *p;
902 mask[i * cols + (cols - 1 - j)] = xmask[i];
903 }
904 }
905 let data_arr = Array::from_vec(Ix2::new([m, cols]), data)?;
906 let mask_arr = Array::from_vec(Ix2::new([m, cols]), mask)?;
907 MaskedArray::new(data_arr, mask_arr)
908}
909
910#[must_use]
924pub fn default_fill_value_f64() -> f64 {
925 1e20
926}
927
928#[must_use]
930pub fn default_fill_value_f32() -> f32 {
931 1e20_f32
932}
933
934#[must_use]
936pub const fn default_fill_value_bool() -> bool {
937 false
938}
939
940#[must_use]
942pub const fn default_fill_value_i64() -> i64 {
943 999_999
944}
945
946#[must_use]
949pub fn maximum_fill_value<T: Float>() -> T {
950 T::infinity()
951}
952
953#[must_use]
956pub fn minimum_fill_value<T: Float>() -> T {
957 T::neg_infinity()
958}
959
960pub fn common_fill_value<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> T
963where
964 T: Element + Copy + PartialEq,
965 D: Dimension,
966{
967 if a.fill_value() == b.fill_value() {
968 a.fill_value()
969 } else {
970 T::zero()
971 }
972}
973
974macro_rules! ma_cmp {
979 ($name:ident, $op:tt, $bound:path) => {
980 pub fn $name<T, D>(
985 a: &MaskedArray<T, D>,
986 b: &MaskedArray<T, D>,
987 ) -> FerrayResult<MaskedArray<bool, D>>
988 where
989 T: Element + Copy + $bound,
990 D: Dimension,
991 {
992 if a.shape() != b.shape() {
993 return Err(FerrayError::shape_mismatch(format!(
994 "{}: shapes {:?} and {:?} differ",
995 stringify!($name),
996 a.shape(),
997 b.shape(),
998 )));
999 }
1000 let data: Vec<bool> = a
1001 .data()
1002 .iter()
1003 .zip(b.data().iter())
1004 .map(|(x, y)| x $op y)
1005 .collect();
1006 let mask: Vec<bool> = a
1007 .mask()
1008 .iter()
1009 .zip(b.mask().iter())
1010 .map(|(x, y)| *x || *y)
1011 .collect();
1012 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1013 let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
1014 MaskedArray::new(data_arr, mask_arr)
1015 }
1016 };
1017}
1018
1019ma_cmp!(ma_equal, ==, PartialEq);
1020ma_cmp!(ma_not_equal, !=, PartialEq);
1021ma_cmp!(ma_less, <, PartialOrd);
1022ma_cmp!(ma_greater, >, PartialOrd);
1023ma_cmp!(ma_less_equal, <=, PartialOrd);
1024ma_cmp!(ma_greater_equal, >=, PartialOrd);
1025
1026pub fn ma_logical_and<D: Dimension>(
1031 a: &MaskedArray<bool, D>,
1032 b: &MaskedArray<bool, D>,
1033) -> FerrayResult<MaskedArray<bool, D>> {
1034 binary_bool(a, b, |x, y| x && y, "ma_logical_and")
1035}
1036
1037pub fn ma_logical_or<D: Dimension>(
1042 a: &MaskedArray<bool, D>,
1043 b: &MaskedArray<bool, D>,
1044) -> FerrayResult<MaskedArray<bool, D>> {
1045 binary_bool(a, b, |x, y| x || y, "ma_logical_or")
1046}
1047
1048pub fn ma_logical_xor<D: Dimension>(
1053 a: &MaskedArray<bool, D>,
1054 b: &MaskedArray<bool, D>,
1055) -> FerrayResult<MaskedArray<bool, D>> {
1056 binary_bool(a, b, |x, y| x ^ y, "ma_logical_xor")
1057}
1058
1059pub fn ma_logical_not<D: Dimension>(
1064 a: &MaskedArray<bool, D>,
1065) -> FerrayResult<MaskedArray<bool, D>> {
1066 let data: Vec<bool> = a.data().iter().map(|x| !*x).collect();
1067 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1068 let mask_arr: Array<bool, D> =
1069 Array::from_vec(a.mask().dim().clone(), a.mask().iter().copied().collect())?;
1070 MaskedArray::new(data_arr, mask_arr)
1071}
1072
1073fn binary_bool<D: Dimension>(
1074 a: &MaskedArray<bool, D>,
1075 b: &MaskedArray<bool, D>,
1076 op: impl Fn(bool, bool) -> bool,
1077 name: &str,
1078) -> FerrayResult<MaskedArray<bool, D>> {
1079 if a.shape() != b.shape() {
1080 return Err(FerrayError::shape_mismatch(format!(
1081 "{name}: shapes {:?} and {:?} differ",
1082 a.shape(),
1083 b.shape()
1084 )));
1085 }
1086 let data: Vec<bool> = a
1087 .data()
1088 .iter()
1089 .zip(b.data().iter())
1090 .map(|(x, y)| op(*x, *y))
1091 .collect();
1092 let mask: Vec<bool> = a
1093 .mask()
1094 .iter()
1095 .zip(b.mask().iter())
1096 .map(|(x, y)| *x || *y)
1097 .collect();
1098 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1099 let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
1100 MaskedArray::new(data_arr, mask_arr)
1101}
1102
1103#[must_use]
1111pub const fn is_masked_array<T, D>(_ma: &MaskedArray<T, D>) -> bool
1112where
1113 T: Element,
1114 D: Dimension,
1115{
1116 true
1117}
1118
1119#[must_use]
1121pub const fn is_ma<T, D>(ma: &MaskedArray<T, D>) -> bool
1122where
1123 T: Element,
1124 D: Dimension,
1125{
1126 is_masked_array(ma)
1127}
1128
1129pub fn getmaskarray<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<bool, D>>
1137where
1138 T: Element,
1139 D: Dimension,
1140{
1141 Array::from_vec(ma.mask().dim().clone(), ma.mask().iter().copied().collect())
1142}
1143
1144#[must_use]
1150pub fn ids<T, D>(ma: &MaskedArray<T, D>) -> (*const u8, *const u8)
1151where
1152 T: Element,
1153 D: Dimension,
1154{
1155 let data_ptr: *const u8 = ma.data() as *const _ as *const u8;
1156 let mask_ptr: *const u8 = ma.mask() as *const _ as *const u8;
1157 (data_ptr, mask_ptr)
1158}
1159
1160#[cfg(test)]
1165mod tests {
1166 use super::*;
1167 use ferray_core::Ix1;
1168
1169 fn arr1f(v: Vec<f64>) -> Array<f64, Ix1> {
1170 let n = v.len();
1171 Array::from_vec(Ix1::new([n]), v).unwrap()
1172 }
1173
1174 fn mask1(v: Vec<bool>) -> Array<bool, Ix1> {
1175 let n = v.len();
1176 Array::from_vec(Ix1::new([n]), v).unwrap()
1177 }
1178
1179 fn ma_f1(d: Vec<f64>, m: Vec<bool>) -> MaskedArray<f64, Ix1> {
1180 MaskedArray::new(arr1f(d), mask1(m)).unwrap()
1181 }
1182
1183 #[test]
1184 fn prod_skips_masked() {
1185 let ma = ma_f1(vec![2.0, 3.0, 5.0, 7.0], vec![false, true, false, false]);
1186 let p = ma.prod().unwrap();
1187 assert!((p - 70.0).abs() < 1e-10); }
1189
1190 #[test]
1191 fn cumsum_propagates_mask() {
1192 let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
1193 let r = ma.cumsum_flat().unwrap();
1194 let mask: Vec<bool> = r.mask().iter().copied().collect();
1195 let data: Vec<f64> = r.data().iter().copied().collect();
1196 assert_eq!(mask, vec![false, true, false, false]);
1197 assert!((data[0] - 1.0).abs() < 1e-10);
1199 assert!((data[2] - 4.0).abs() < 1e-10);
1200 assert!((data[3] - 8.0).abs() < 1e-10);
1201 }
1202
1203 #[test]
1204 fn argmin_argmax_skip_masked() {
1205 let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
1206 assert_eq!(ma.argmin().unwrap(), 3);
1208 assert_eq!(ma.argmax().unwrap(), 2);
1209 }
1210
1211 #[test]
1212 fn ptp_basic() {
1213 let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
1214 assert!((ma.ptp().unwrap() - 6.0).abs() < 1e-10);
1216 }
1217
1218 #[test]
1219 fn median_odd_and_even() {
1220 let odd = ma_f1(vec![3.0, 1.0, 4.0, 1.0, 5.0], vec![false; 5]);
1221 assert!((odd.median().unwrap() - 3.0).abs() < 1e-10);
1222 let even = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
1223 assert!((even.median().unwrap() - 2.5).abs() < 1e-10);
1224 }
1225
1226 #[test]
1227 fn average_unweighted_matches_mean() {
1228 let ma = ma_f1(vec![2.0, 4.0, 6.0], vec![false; 3]);
1229 assert!((ma.average(None).unwrap() - 4.0).abs() < 1e-10);
1230 }
1231
1232 #[test]
1233 fn average_weighted_skips_masked() {
1234 let ma = ma_f1(vec![1.0, 100.0, 3.0], vec![false, true, false]);
1235 let w = arr1f(vec![1.0, 1.0, 3.0]);
1236 assert!((ma.average(Some(&w)).unwrap() - 2.5).abs() < 1e-10);
1238 }
1239
1240 #[test]
1241 fn anom_centers_at_zero() {
1242 let ma = ma_f1(vec![1.0, 2.0, 3.0], vec![false; 3]);
1243 let a = ma.anom().unwrap();
1244 let data: Vec<f64> = a.data().iter().copied().collect();
1245 assert!((data[0] - (-1.0)).abs() < 1e-10);
1246 assert!((data[1] - 0.0).abs() < 1e-10);
1247 assert!((data[2] - 1.0).abs() < 1e-10);
1248 }
1249
1250 #[test]
1251 fn masked_all_is_fully_masked() {
1252 let ma: MaskedArray<f64, Ix1> = masked_all(Ix1::new([5])).unwrap();
1253 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1254 assert_eq!(mask, vec![true; 5]);
1255 }
1256
1257 #[test]
1258 fn masked_values_within_tol() {
1259 let arr = arr1f(vec![1.0, 1.0001, 2.0]);
1260 let r = masked_values(&arr, 1.0, 1e-3, 0.0).unwrap();
1261 let mask: Vec<bool> = r.mask().iter().copied().collect();
1262 assert_eq!(mask, vec![true, true, false]);
1263 }
1264
1265 #[test]
1266 fn harden_mask_blocks_clearing() {
1267 let mut ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
1268 ma.harden_mask().unwrap();
1269 assert!(ma.is_hard_mask());
1270 ma.set_mask_flat(1, false).unwrap();
1272 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1273 assert_eq!(mask, vec![false, true]);
1275 ma.soften_mask().unwrap();
1276 ma.set_mask_flat(1, false).unwrap();
1277 let mask2: Vec<bool> = ma.mask().iter().copied().collect();
1278 assert_eq!(mask2, vec![false, false]);
1279 }
1280
1281 #[test]
1282 fn mask_or_unions() {
1283 let m1 = mask1(vec![true, false, false]);
1284 let m2 = mask1(vec![false, true, false]);
1285 let r = mask_or(&m1, &m2).unwrap();
1286 let v: Vec<bool> = r.iter().copied().collect();
1287 assert_eq!(v, vec![true, true, false]);
1288 }
1289
1290 #[test]
1291 fn make_mask_none_is_all_false() {
1292 let m: Array<bool, Ix1> = make_mask_none(Ix1::new([3])).unwrap();
1293 let v: Vec<bool> = m.iter().copied().collect();
1294 assert_eq!(v, vec![false; 3]);
1295 }
1296
1297 #[test]
1298 fn clip_unmasked_only() {
1299 let ma = ma_f1(vec![-5.0, 0.0, 5.0, 10.0], vec![false, false, false, true]);
1300 let r = ma.clip(-1.0, 3.0).unwrap();
1301 let data: Vec<f64> = r.data().iter().copied().collect();
1302 assert_eq!(data, vec![-1.0, 0.0, 3.0, 10.0]);
1304 }
1305
1306 #[test]
1307 fn repeat_propagates_mask() {
1308 let ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
1309 let r = ma.repeat(3).unwrap();
1310 let data: Vec<f64> = r.data().iter().copied().collect();
1311 let mask: Vec<bool> = r.mask().iter().copied().collect();
1312 assert_eq!(data, vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
1313 assert_eq!(mask, vec![false, false, false, true, true, true]);
1314 }
1315
1316 #[test]
1317 fn ma_unique_dedups_unmasked() {
1318 let ma = ma_f1(
1319 vec![3.0, 1.0, 2.0, 1.0, 3.0, 9.0],
1320 vec![false, false, false, false, false, true],
1321 );
1322 let v = ma_unique(&ma).unwrap();
1323 let data: Vec<f64> = v.iter().copied().collect();
1324 assert_eq!(data, vec![1.0, 2.0, 3.0]);
1325 }
1326
1327 #[test]
1328 fn ma_isin_basic() {
1329 let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
1330 let r = ma_isin(&ma, &[2.0, 4.0]).unwrap();
1331 let data: Vec<bool> = r.data().iter().copied().collect();
1332 assert_eq!(data, vec![false, true, false, true]);
1333 }
1334
1335 #[test]
1336 fn ma_dot_flat_skips_masked_pairs() {
1337 let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, true, false]);
1338 let b = ma_f1(vec![4.0, 5.0, 6.0], vec![false, false, false]);
1339 assert!((a.ma_dot_flat(&b).unwrap() - 22.0).abs() < 1e-10);
1342 }
1343
1344 #[test]
1345 fn fill_value_protocol_constants() {
1346 assert_eq!(default_fill_value_f64(), 1e20);
1347 assert!(!default_fill_value_bool());
1348 assert!(maximum_fill_value::<f64>().is_infinite());
1349 assert!(
1350 minimum_fill_value::<f64>().is_infinite()
1351 && minimum_fill_value::<f64>().is_sign_negative()
1352 );
1353 }
1354
1355 #[test]
1356 fn common_fill_value_returns_shared_or_zero() {
1357 let a = ma_f1(vec![1.0, 2.0], vec![false, false]).with_fill_value(99.0);
1358 let b = ma_f1(vec![3.0, 4.0], vec![false, false]).with_fill_value(99.0);
1359 assert_eq!(common_fill_value(&a, &b), 99.0);
1360 let c = ma_f1(vec![5.0, 6.0], vec![false, false]).with_fill_value(0.5);
1361 assert_eq!(common_fill_value(&a, &c), 0.0); }
1363
1364 #[test]
1365 fn ma_equal_and_friends_union_mask() {
1366 let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, true]);
1367 let b = ma_f1(vec![1.0, 9.0, 3.0], vec![false, true, false]);
1368 let r = ma_equal(&a, &b).unwrap();
1369 let data: Vec<bool> = r.data().iter().copied().collect();
1370 let mask: Vec<bool> = r.mask().iter().copied().collect();
1371 assert_eq!(data, vec![true, false, true]);
1372 assert_eq!(mask, vec![false, true, true]);
1373 }
1374
1375 #[test]
1376 fn ma_logical_and_basic() {
1377 let a = MaskedArray::new(
1378 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, true, false]).unwrap(),
1379 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
1380 )
1381 .unwrap();
1382 let b = MaskedArray::new(
1383 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, false, true]).unwrap(),
1384 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
1385 )
1386 .unwrap();
1387 let r = ma_logical_and(&a, &b).unwrap();
1388 let data: Vec<bool> = r.data().iter().copied().collect();
1389 assert_eq!(data, vec![true, false, false]);
1390 }
1391
1392 #[test]
1393 fn is_masked_array_always_true() {
1394 let ma = ma_f1(vec![1.0], vec![false]);
1395 assert!(is_masked_array(&ma));
1396 assert!(is_ma(&ma));
1397 }
1398
1399 #[test]
1400 fn getmaskarray_materializes() {
1401 let ma = MaskedArray::<f64, Ix1>::from_data(arr1f(vec![1.0, 2.0])).unwrap();
1402 let m = getmaskarray(&ma).unwrap();
1403 let v: Vec<bool> = m.iter().copied().collect();
1404 assert_eq!(v, vec![false; 2]);
1405 }
1406
1407 #[test]
1408 fn diagonal_main_and_offset() {
1409 use ferray_core::Ix2;
1410 let data = Array::<f64, Ix2>::from_vec(
1411 Ix2::new([3, 3]),
1412 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
1413 )
1414 .unwrap();
1415 let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
1416 let ma = MaskedArray::new(data, mask).unwrap();
1417 let main = ma.diagonal(0).unwrap();
1418 let main_data: Vec<f64> = main.data().iter().copied().collect();
1419 assert_eq!(main_data, vec![1.0, 5.0, 9.0]);
1420 let upper = ma.diagonal(1).unwrap();
1421 let upper_data: Vec<f64> = upper.data().iter().copied().collect();
1422 assert_eq!(upper_data, vec![2.0, 6.0]);
1423 }
1424
1425 #[test]
1426 fn trace_sums_diagonal() {
1427 use ferray_core::Ix2;
1428 let data = Array::<f64, Ix2>::from_vec(
1429 Ix2::new([3, 3]),
1430 vec![1.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 9.0],
1431 )
1432 .unwrap();
1433 let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
1434 let ma = MaskedArray::new(data, mask).unwrap();
1435 assert!((ma.trace(0).unwrap() - 15.0).abs() < 1e-10);
1436 }
1437
1438 #[test]
1439 fn ma_apply_along_axis_sum_lane() {
1440 use ferray_core::IxDyn;
1441 let data =
1442 Array::<f64, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
1443 .unwrap();
1444 let mask = Array::<bool, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![false; 6]).unwrap();
1445 let ma = MaskedArray::new(data, mask).unwrap();
1446 let result = ma_apply_along_axis(&ma, 1, |lane| {
1447 let s: f64 = lane.data().iter().copied().sum();
1448 Ok((s, false))
1449 })
1450 .unwrap();
1451 let v: Vec<f64> = result.data().iter().copied().collect();
1452 assert_eq!(v, vec![6.0, 15.0]);
1453 }
1454}