1use ferray_core::dimension::{Dimension, IxDyn};
76use ferray_core::dtype::Element;
77use ferray_core::error::{FerrayError, FerrayResult};
78use ferray_core::{Array, Ix1, Ix2};
79use num_traits::Float;
80
81use crate::MaskedArray;
82
83pub const NOMASK: bool = false;
90
91impl<T, D> MaskedArray<T, D>
96where
97 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
98 D: Dimension,
99{
100 pub fn prod(&self) -> FerrayResult<T> {
105 let one = num_traits::one::<T>();
106 let p = self
107 .data()
108 .iter()
109 .zip(self.mask().iter())
110 .filter(|(_, m)| !**m)
111 .fold(one, |acc, (v, _)| acc * *v);
112 Ok(p)
113 }
114}
115
116impl<T, D> MaskedArray<T, D>
117where
118 T: Element + Copy + std::ops::Add<Output = T> + num_traits::Zero,
119 D: Dimension,
120{
121 pub fn cumsum_flat(&self) -> FerrayResult<MaskedArray<T, Ix1>> {
128 let zero = num_traits::zero::<T>();
129 let n = self.size();
130 let data: Vec<T> = self
131 .data()
132 .iter()
133 .zip(self.mask().iter())
134 .scan(zero, |acc, (v, m)| {
135 if !*m {
136 *acc = *acc + *v;
137 }
138 Some(*acc)
139 })
140 .collect();
141 let mask: Vec<bool> = self.mask().iter().copied().collect();
142 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
143 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
144 MaskedArray::new(data_arr, mask_arr)
145 }
146}
147
148impl<T, D> MaskedArray<T, D>
149where
150 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
151 D: Dimension,
152{
153 pub fn cumprod_flat(&self) -> FerrayResult<MaskedArray<T, Ix1>> {
159 let one = num_traits::one::<T>();
160 let n = self.size();
161 let data: Vec<T> = self
162 .data()
163 .iter()
164 .zip(self.mask().iter())
165 .scan(one, |acc, (v, m)| {
166 if !*m {
167 *acc = *acc * *v;
168 }
169 Some(*acc)
170 })
171 .collect();
172 let mask: Vec<bool> = self.mask().iter().copied().collect();
173 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
174 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
175 MaskedArray::new(data_arr, mask_arr)
176 }
177}
178
179impl<T, D> MaskedArray<T, D>
180where
181 T: Element + Copy + PartialOrd,
182 D: Dimension,
183{
184 pub fn argmin(&self) -> FerrayResult<usize> {
189 self.data()
190 .iter()
191 .zip(self.mask().iter())
192 .enumerate()
193 .filter(|(_, (_, m))| !**m)
194 .min_by(|(_, (a, _)), (_, (b, _))| {
195 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
196 })
197 .map(|(i, _)| i)
198 .ok_or_else(|| FerrayError::invalid_value("argmin: all elements are masked"))
199 }
200
201 pub fn argmax(&self) -> FerrayResult<usize> {
206 self.data()
207 .iter()
208 .zip(self.mask().iter())
209 .enumerate()
210 .filter(|(_, (_, m))| !**m)
211 .max_by(|(_, (a, _)), (_, (b, _))| {
212 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
213 })
214 .map(|(i, _)| i)
215 .ok_or_else(|| FerrayError::invalid_value("argmax: all elements are masked"))
216 }
217
218 pub fn ptp(&self) -> FerrayResult<T>
223 where
224 T: std::ops::Sub<Output = T>,
225 {
226 let mut iter = self
227 .data()
228 .iter()
229 .zip(self.mask().iter())
230 .filter(|(_, m)| !**m)
231 .map(|(v, _)| *v);
232 let first = iter
233 .next()
234 .ok_or_else(|| FerrayError::invalid_value("ptp: all elements are masked"))?;
235 let mut lo = first;
236 let mut hi = first;
237 for v in iter {
238 if v < lo {
239 lo = v;
240 }
241 if v > hi {
242 hi = v;
243 }
244 }
245 Ok(hi - lo)
246 }
247}
248
249impl<T, D> MaskedArray<T, D>
250where
251 T: Element
252 + Copy
253 + PartialOrd
254 + num_traits::FromPrimitive
255 + std::ops::Add<Output = T>
256 + std::ops::Div<Output = T>,
257 D: Dimension,
258{
259 pub fn median(&self) -> FerrayResult<T> {
265 let mut vals: Vec<T> = self
266 .data()
267 .iter()
268 .zip(self.mask().iter())
269 .filter(|(_, m)| !**m)
270 .map(|(v, _)| *v)
271 .collect();
272 if vals.is_empty() {
273 return Err(FerrayError::invalid_value(
274 "median: all elements are masked",
275 ));
276 }
277 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
278 let n = vals.len();
279 if n % 2 == 1 {
280 Ok(vals[n / 2])
281 } else {
282 let two = T::from_u8(2).unwrap();
283 Ok((vals[n / 2 - 1] + vals[n / 2]) / two)
284 }
285 }
286}
287
288pub fn ma_median_axis<T>(
301 a: &MaskedArray<T, IxDyn>,
302 axis: usize,
303) -> FerrayResult<MaskedArray<T, IxDyn>>
304where
305 T: Element
306 + Copy
307 + PartialOrd
308 + num_traits::Zero
309 + num_traits::FromPrimitive
310 + std::ops::Add<Output = T>
311 + std::ops::Div<Output = T>,
312{
313 let shape = a.shape().to_vec();
314 if axis >= shape.len() {
315 return Err(FerrayError::axis_out_of_bounds(axis, shape.len()));
316 }
317 let lane_len = shape[axis];
318 let out_shape: Vec<usize> = shape
319 .iter()
320 .enumerate()
321 .filter(|&(i, _)| i != axis)
322 .map(|(_, &s)| s)
323 .collect();
324 let out_size: usize = out_shape.iter().product::<usize>().max(1);
325
326 let ndim = shape.len();
327 let mut strides = vec![1usize; ndim];
328 for i in (0..ndim.saturating_sub(1)).rev() {
329 strides[i] = strides[i + 1] * shape[i + 1];
330 }
331
332 let data: Vec<T> = a.data().iter().copied().collect();
333 let mask: Vec<bool> = a.mask().iter().copied().collect();
334
335 let mut out_data = Vec::with_capacity(out_size);
336 let mut out_mask = Vec::with_capacity(out_size);
337
338 let out_axes: Vec<usize> = (0..ndim).filter(|&i| i != axis).collect();
341 let mut counter = vec![0usize; out_axes.len()];
342 let two = T::from_u8(2).ok_or_else(|| {
343 FerrayError::invalid_value("median: constant 2 not representable in element type")
344 })?;
345 for _ in 0..out_size {
346 let mut base = 0usize;
347 for (slot, &ax) in out_axes.iter().enumerate() {
348 base += counter[slot] * strides[ax];
349 }
350 let mut vals: Vec<T> = Vec::with_capacity(lane_len);
351 for k in 0..lane_len {
352 let off = base + k * strides[axis];
353 if !mask[off] {
354 vals.push(data[off]);
355 }
356 }
357 if vals.is_empty() {
358 out_data.push(<T as num_traits::Zero>::zero());
359 out_mask.push(true);
360 } else {
361 vals.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
362 let n = vals.len();
363 let med = if n % 2 == 1 {
364 vals[n / 2]
365 } else {
366 (vals[n / 2 - 1] + vals[n / 2]) / two
367 };
368 out_data.push(med);
369 out_mask.push(false);
370 }
371
372 for slot in (0..out_axes.len()).rev() {
374 counter[slot] += 1;
375 if counter[slot] < out_shape[slot] {
376 break;
377 }
378 counter[slot] = 0;
379 }
380 }
381
382 let dim = IxDyn::new(&out_shape);
383 let data_arr = Array::<T, IxDyn>::from_vec(dim.clone(), out_data)?;
384 let mask_arr = Array::<bool, IxDyn>::from_vec(dim, out_mask)?;
385 MaskedArray::new(data_arr, mask_arr)
386}
387
388impl<T, D> MaskedArray<T, D>
389where
390 T: Element + Copy + Float,
391 D: Dimension,
392{
393 pub fn average(&self, weights: Option<&Array<T, D>>) -> FerrayResult<T> {
403 Ok(self.average_returned(weights)?.0)
404 }
405
406 pub fn average_returned(&self, weights: Option<&Array<T, D>>) -> FerrayResult<(T, T)> {
421 let zero = <T as num_traits::Zero>::zero();
422 let Some(w) = weights else {
423 let avg = self.mean()?;
425 let count = self.mask().iter().filter(|m| !**m).count();
426 let scl = <T as num_traits::NumCast>::from(count).ok_or_else(|| {
427 FerrayError::invalid_value("average: unmasked count not representable")
428 })?;
429 return Ok((avg, scl));
430 };
431 if w.shape() != self.shape() {
432 return Err(FerrayError::shape_mismatch(format!(
433 "average: weights shape {:?} differs from array shape {:?}",
434 w.shape(),
435 self.shape(),
436 )));
437 }
438 let mut wsum = zero;
439 let mut acc = zero;
440 for ((v, m), wi) in self.data().iter().zip(self.mask().iter()).zip(w.iter()) {
441 if !*m {
442 wsum = wsum + *wi;
443 acc = acc + *v * *wi;
444 }
445 }
446 if wsum == zero {
447 return Err(FerrayError::invalid_value(
448 "average: weight sum is zero (or all elements are masked)",
449 ));
450 }
451 Ok((acc / wsum, wsum))
452 }
453
454 pub fn anom(&self) -> FerrayResult<MaskedArray<T, D>> {
462 let m = self.mean()?;
463 let data: Vec<T> = self.data().iter().map(|v| *v - m).collect();
464 let data_arr = Array::from_vec(self.data().dim().clone(), data)?;
465 let mask_arr: Array<bool, D> = Array::from_vec(
466 self.mask().dim().clone(),
467 self.mask().iter().copied().collect(),
468 )?;
469 MaskedArray::new(data_arr, mask_arr)
470 }
471}
472
473pub fn masked_all<T, D>(shape: D) -> FerrayResult<MaskedArray<T, D>>
484where
485 T: Element + Copy,
486 D: Dimension,
487{
488 let data = Array::<T, D>::from_elem(shape.clone(), T::zero())?;
489 let mask = Array::<bool, D>::from_elem(shape, true)?;
490 MaskedArray::new(data, mask)
491}
492
493pub fn masked_all_like<T, U, D>(reference: &Array<U, D>) -> FerrayResult<MaskedArray<T, D>>
500where
501 T: Element + Copy,
502 U: Element,
503 D: Dimension,
504{
505 masked_all(reference.dim().clone())
506}
507
508pub fn masked_values<T, D>(
517 arr: &Array<T, D>,
518 value: T,
519 rtol: T,
520 atol: T,
521) -> FerrayResult<MaskedArray<T, D>>
522where
523 T: Element + Copy + Float,
524 D: Dimension,
525{
526 let threshold = atol + rtol * value.abs();
527 let mask: Vec<bool> = arr
528 .iter()
529 .map(|x| (*x - value).abs() <= threshold)
530 .collect();
531 let mask_arr = Array::from_vec(arr.dim().clone(), mask)?;
532 let data_arr = arr.clone();
533 MaskedArray::new(data_arr, mask_arr)
534}
535
536pub fn mask_or<D: Dimension>(
551 a: &Array<bool, D>,
552 b: &Array<bool, D>,
553) -> FerrayResult<Array<bool, D>> {
554 if a.shape() != b.shape() {
555 return Err(FerrayError::shape_mismatch(format!(
556 "mask_or: shapes {:?} and {:?} differ",
557 a.shape(),
558 b.shape()
559 )));
560 }
561 let data: Vec<bool> = a.iter().zip(b.iter()).map(|(x, y)| *x || *y).collect();
562 Array::from_vec(a.dim().clone(), data)
563}
564
565pub fn make_mask<D: Dimension>(values: &[bool], shape: D) -> FerrayResult<Array<bool, D>> {
573 Array::from_vec(shape, values.to_vec())
574}
575
576pub fn make_mask_none<D: Dimension>(shape: D) -> FerrayResult<Array<bool, D>> {
583 Array::from_elem(shape, false)
584}
585
586pub fn ma_concatenate<T>(
599 a: &MaskedArray<T, IxDyn>,
600 b: &MaskedArray<T, IxDyn>,
601 axis: usize,
602) -> FerrayResult<MaskedArray<T, IxDyn>>
603where
604 T: Element + Copy,
605{
606 let cat_data =
607 ferray_core::manipulation::concatenate(&[a.data().clone(), b.data().clone()], axis)?;
608 let cat_mask =
609 ferray_core::manipulation::concatenate(&[a.mask().clone(), b.mask().clone()], axis)?;
610 MaskedArray::new(cat_data, cat_mask)
611}
612
613impl<T, D> MaskedArray<T, D>
614where
615 T: Element + Copy + PartialOrd,
616 D: Dimension,
617{
618 pub fn clip(&self, a_min: T, a_max: T) -> FerrayResult<MaskedArray<T, D>> {
624 let data: Vec<T> = self
625 .data()
626 .iter()
627 .zip(self.mask().iter())
628 .map(|(v, m)| {
629 if *m {
630 *v
631 } else if *v < a_min {
632 a_min
633 } else if *v > a_max {
634 a_max
635 } else {
636 *v
637 }
638 })
639 .collect();
640 let data_arr = Array::from_vec(self.data().dim().clone(), data)?;
641 let mask_arr: Array<bool, D> = Array::from_vec(
642 self.mask().dim().clone(),
643 self.mask().iter().copied().collect(),
644 )?;
645 MaskedArray::new(data_arr, mask_arr)
646 }
647}
648
649impl<T, D> MaskedArray<T, D>
650where
651 T: Element + Copy,
652 D: Dimension,
653{
654 pub fn repeat(&self, repeats: usize) -> FerrayResult<MaskedArray<T, Ix1>> {
662 let n = self.size() * repeats;
663 let mut data = Vec::with_capacity(n);
664 let mut mask = Vec::with_capacity(n);
665 for (v, m) in self.data().iter().zip(self.mask().iter()) {
666 for _ in 0..repeats {
667 data.push(*v);
668 mask.push(*m);
669 }
670 }
671 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
672 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
673 MaskedArray::new(data_arr, mask_arr)
674 }
675
676 pub fn atleast_1d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
681 let shape = self.shape();
682 let new_shape: Vec<usize> = if shape.is_empty() {
683 vec![1]
684 } else {
685 shape.to_vec()
686 };
687 let data: Vec<T> = self.data().iter().copied().collect();
688 let mask: Vec<bool> = self.mask().iter().copied().collect();
689 let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
690 let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
691 MaskedArray::new(data_arr, mask_arr)
692 }
693
694 pub fn atleast_2d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
699 let shape = self.shape();
700 let new_shape: Vec<usize> = match shape.len() {
701 0 => vec![1, 1],
702 1 => vec![1, shape[0]],
703 _ => shape.to_vec(),
704 };
705 let data: Vec<T> = self.data().iter().copied().collect();
706 let mask: Vec<bool> = self.mask().iter().copied().collect();
707 let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
708 let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
709 MaskedArray::new(data_arr, mask_arr)
710 }
711
712 pub fn atleast_3d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
717 let shape = self.shape();
718 let new_shape: Vec<usize> = match shape.len() {
719 0 => vec![1, 1, 1],
720 1 => vec![1, shape[0], 1],
721 2 => vec![shape[0], shape[1], 1],
722 _ => shape.to_vec(),
723 };
724 let data: Vec<T> = self.data().iter().copied().collect();
725 let mask: Vec<bool> = self.mask().iter().copied().collect();
726 let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
727 let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
728 MaskedArray::new(data_arr, mask_arr)
729 }
730
731 pub fn expand_dims(&self, axis: usize) -> FerrayResult<MaskedArray<T, IxDyn>> {
736 let data_exp = ferray_core::manipulation::expand_dims(self.data(), axis)?;
737 let mask_exp = ferray_core::manipulation::expand_dims(self.mask(), axis)?;
738 MaskedArray::new(data_exp, mask_exp)
739 }
740}
741
742impl<T> MaskedArray<T, Ix2>
743where
744 T: Element + Copy + num_traits::Zero,
745{
746 pub fn diagonal(&self, k: isize) -> FerrayResult<MaskedArray<T, Ix1>> {
753 let shape = self.shape();
754 let (rows, cols) = (shape[0], shape[1]);
755 let (start_r, start_c) = if k >= 0 {
756 (0usize, k as usize)
757 } else {
758 (-k as usize, 0usize)
759 };
760 let mut data = Vec::new();
761 let mut mask = Vec::new();
762 let data_slice = self.data().as_slice();
763 let mask_slice = self.mask().as_slice();
764 let mut r = start_r;
765 let mut c = start_c;
766 while r < rows && c < cols {
767 let flat = r * cols + c;
768 if let (Some(ds), Some(ms)) = (data_slice, mask_slice) {
770 data.push(ds[flat]);
771 mask.push(ms[flat]);
772 } else {
773 data.push(*self.data().iter().nth(flat).expect("flat index in bounds"));
774 mask.push(*self.mask().iter().nth(flat).expect("flat index in bounds"));
775 }
776 r += 1;
777 c += 1;
778 }
779 let n = data.len();
780 let data_arr = Array::from_vec(Ix1::new([n]), data)?;
781 let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
782 MaskedArray::new(data_arr, mask_arr)
783 }
784}
785
786impl<T, D> MaskedArray<T, D>
791where
792 T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
793 D: Dimension,
794{
795 pub fn ma_dot_flat(&self, other: &MaskedArray<T, D>) -> FerrayResult<T> {
801 if self.size() != other.size() {
802 return Err(FerrayError::shape_mismatch(format!(
803 "ma_dot_flat: lhs.size()={} != rhs.size()={}",
804 self.size(),
805 other.size(),
806 )));
807 }
808 let zero = <T as num_traits::Zero>::zero();
809 let s = self
810 .data()
811 .iter()
812 .zip(self.mask().iter())
813 .zip(other.data().iter().zip(other.mask().iter()))
814 .fold(
815 zero,
816 |acc, ((a, ma), (b, mb))| {
817 if *ma || *mb { acc } else { acc + *a * *b }
818 },
819 );
820 Ok(s)
821 }
822}
823
824impl<T> MaskedArray<T, Ix2>
825where
826 T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
827{
828 pub fn trace(&self, k: isize) -> FerrayResult<T> {
831 let diag = self.diagonal(k)?;
832 let zero = <T as num_traits::Zero>::zero();
833 let s = diag
834 .data()
835 .iter()
836 .zip(diag.mask().iter())
837 .filter(|(_, m)| !**m)
838 .fold(zero, |acc, (v, _)| acc + *v);
839 Ok(s)
840 }
841}
842
843pub fn ma_unique<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<T, Ix1>>
855where
856 T: Element + Copy + PartialOrd,
857 D: Dimension,
858{
859 let mut vals: Vec<T> = ma
860 .data()
861 .iter()
862 .zip(ma.mask().iter())
863 .filter(|(_, m)| !**m)
864 .map(|(v, _)| *v)
865 .collect();
866 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
867 vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
868 let n = vals.len();
869 Array::from_vec(Ix1::new([n]), vals)
870}
871
872pub fn ma_isin<T, D>(
895 ma: &MaskedArray<T, D>,
896 test_values: &[T],
897) -> FerrayResult<MaskedArray<bool, D>>
898where
899 T: Element + Copy + PartialEq,
900 D: Dimension,
901{
902 let mask: Vec<bool> = ma.mask().iter().copied().collect();
903 let data: Vec<bool> = ma
904 .data()
905 .iter()
906 .zip(mask.iter())
907 .map(|(v, &m)| {
908 !m && test_values.iter().any(|t| t == v)
910 })
911 .collect();
912 let data_arr = Array::from_vec(ma.data().dim().clone(), data)?;
913 let mask_arr: Array<bool, D> = Array::from_vec(ma.mask().dim().clone(), mask)?;
914 MaskedArray::new(data_arr, mask_arr)
915}
916
917pub fn ma_in1d<T>(
924 ma: &MaskedArray<T, Ix1>,
925 test_values: &[T],
926) -> FerrayResult<MaskedArray<bool, Ix1>>
927where
928 T: Element + Copy + PartialEq,
929{
930 ma_isin(ma, test_values)
931}
932
933pub fn ma_unique_masked<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, Ix1>>
952where
953 T: Element + Copy + PartialOrd,
954 D: Dimension,
955{
956 let mut vals: Vec<T> = ma
957 .data()
958 .iter()
959 .zip(ma.mask().iter())
960 .filter(|(_, m)| !**m)
961 .map(|(v, _)| *v)
962 .collect();
963 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
964 vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
965
966 let mut mask = vec![false; vals.len()];
967 if let Some(pos) = ma.mask().iter().position(|&m| m) {
969 if let Some(placeholder) = ma.data().iter().nth(pos) {
970 vals.push(*placeholder);
971 mask.push(true);
972 }
973 }
974 let n = vals.len();
975 let data_arr = Array::<T, Ix1>::from_vec(Ix1::new([n]), vals)?;
976 let mask_arr = Array::<bool, Ix1>::from_vec(Ix1::new([n]), mask)?;
977 MaskedArray::new(data_arr, mask_arr)
978}
979
980fn build_ma_ix1<T>(pairs: Vec<(T, bool)>) -> FerrayResult<MaskedArray<T, Ix1>>
982where
983 T: Element + Copy,
984{
985 let n = pairs.len();
986 let data: Vec<T> = pairs.iter().map(|(v, _)| *v).collect();
987 let mask: Vec<bool> = pairs.iter().map(|(_, m)| *m).collect();
988 let data_arr = Array::<T, Ix1>::from_vec(Ix1::new([n]), data)?;
989 let mask_arr = Array::<bool, Ix1>::from_vec(Ix1::new([n]), mask)?;
990 MaskedArray::new(data_arr, mask_arr)
991}
992
993fn unique_parts<T, D>(ma: &MaskedArray<T, D>) -> (Vec<T>, bool)
996where
997 T: Element + Copy + PartialOrd,
998 D: Dimension,
999{
1000 let mut vals: Vec<T> = ma
1001 .data()
1002 .iter()
1003 .zip(ma.mask().iter())
1004 .filter(|(_, m)| !**m)
1005 .map(|(v, _)| *v)
1006 .collect();
1007 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1008 vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
1009 let any_masked = ma.mask().iter().any(|&m| m);
1010 (vals, any_masked)
1011}
1012
1013pub fn ma_intersect1d<T, D>(
1025 ar1: &MaskedArray<T, D>,
1026 ar2: &MaskedArray<T, D>,
1027) -> FerrayResult<MaskedArray<T, Ix1>>
1028where
1029 T: Element + Copy + PartialOrd,
1030 D: Dimension,
1031{
1032 let (v1, m1) = unique_parts(ar1);
1033 let (v2, m2) = unique_parts(ar2);
1034 let mut out: Vec<(T, bool)> = Vec::new();
1036 let (mut i, mut j) = (0usize, 0usize);
1037 while i < v1.len() && j < v2.len() {
1038 match v1[i].partial_cmp(&v2[j]) {
1039 Some(std::cmp::Ordering::Equal) => {
1040 out.push((v1[i], false));
1041 i += 1;
1042 j += 1;
1043 }
1044 Some(std::cmp::Ordering::Less) => i += 1,
1045 Some(std::cmp::Ordering::Greater) => j += 1,
1046 None => {
1047 i += 1;
1049 }
1050 }
1051 }
1052 if m1 && m2 {
1054 let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
1055 if let Some(p) = placeholder {
1056 out.push((p, true));
1057 }
1058 }
1059 build_ma_ix1(out)
1060}
1061
1062pub fn ma_union1d<T, D>(
1069 ar1: &MaskedArray<T, D>,
1070 ar2: &MaskedArray<T, D>,
1071) -> FerrayResult<MaskedArray<T, Ix1>>
1072where
1073 T: Element + Copy + PartialOrd,
1074 D: Dimension,
1075{
1076 let (v1, m1) = unique_parts(ar1);
1077 let (v2, m2) = unique_parts(ar2);
1078 let mut vals: Vec<T> = v1;
1079 vals.extend(v2);
1080 vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1081 vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
1082 let mut out: Vec<(T, bool)> = vals.into_iter().map(|v| (v, false)).collect();
1083 if m1 || m2 {
1084 let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
1085 if let Some(p) = placeholder {
1086 out.push((p, true));
1087 }
1088 }
1089 build_ma_ix1(out)
1090}
1091
1092pub fn ma_setdiff1d<T, D>(
1101 ar1: &MaskedArray<T, D>,
1102 ar2: &MaskedArray<T, D>,
1103) -> FerrayResult<MaskedArray<T, Ix1>>
1104where
1105 T: Element + Copy + PartialOrd,
1106 D: Dimension,
1107{
1108 let (v1, m1) = unique_parts(ar1);
1109 let (v2, _m2) = unique_parts(ar2);
1110 let m2_masked = ar2.mask().iter().any(|&m| m);
1111 let mut out: Vec<(T, bool)> = Vec::new();
1112 for v in v1 {
1113 let present = v2
1114 .iter()
1115 .any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal));
1116 if !present {
1117 out.push((v, false));
1118 }
1119 }
1120 if m1 && !m2_masked {
1121 if let Some(p) = first_masked_value(ar1) {
1122 out.push((p, true));
1123 }
1124 }
1125 build_ma_ix1(out)
1126}
1127
1128pub fn ma_setxor1d<T, D>(
1135 ar1: &MaskedArray<T, D>,
1136 ar2: &MaskedArray<T, D>,
1137) -> FerrayResult<MaskedArray<T, Ix1>>
1138where
1139 T: Element + Copy + PartialOrd,
1140 D: Dimension,
1141{
1142 let (v1, m1) = unique_parts(ar1);
1143 let (v2, m2) = unique_parts(ar2);
1144 let mut out: Vec<(T, bool)> = Vec::new();
1145 for v in &v1 {
1147 if !v2
1148 .iter()
1149 .any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal))
1150 {
1151 out.push((*v, false));
1152 }
1153 }
1154 for v in &v2 {
1156 if !v1
1157 .iter()
1158 .any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal))
1159 {
1160 out.push((*v, false));
1161 }
1162 }
1163 out.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
1164 if m1 ^ m2 {
1166 let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
1167 if let Some(p) = placeholder {
1168 out.push((p, true));
1169 }
1170 }
1171 build_ma_ix1(out)
1172}
1173
1174fn first_masked_value<T, D>(ma: &MaskedArray<T, D>) -> Option<T>
1176where
1177 T: Element + Copy,
1178 D: Dimension,
1179{
1180 ma.mask()
1181 .iter()
1182 .position(|&m| m)
1183 .and_then(|pos| ma.data().iter().nth(pos).copied())
1184}
1185
1186pub fn ma_compress_rowcols<T>(
1206 ma: &MaskedArray<T, Ix2>,
1207 axis: Option<usize>,
1208) -> FerrayResult<Array<T, Ix2>>
1209where
1210 T: Element + Copy,
1211{
1212 let shape = ma.data().shape();
1213 let (nrows, ncols) = (shape[0], shape[1]);
1214 let data: Vec<T> = ma.data().iter().copied().collect();
1215 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1216
1217 let drop_rows = matches!(axis, None | Some(0));
1218 let drop_cols = matches!(axis, None | Some(1));
1219
1220 let mut keep_row = vec![true; nrows];
1222 let mut keep_col = vec![true; ncols];
1223 if drop_rows {
1224 for (r, kr) in keep_row.iter_mut().enumerate() {
1225 *kr = !(0..ncols).any(|c| mask[r * ncols + c]);
1226 }
1227 }
1228 if drop_cols {
1229 for (c, kc) in keep_col.iter_mut().enumerate() {
1230 *kc = !(0..nrows).any(|r| mask[r * ncols + c]);
1231 }
1232 }
1233
1234 let kept_rows: Vec<usize> = (0..nrows).filter(|&r| keep_row[r]).collect();
1235 let kept_cols: Vec<usize> = (0..ncols).filter(|&c| keep_col[c]).collect();
1236
1237 let mut out: Vec<T> = Vec::with_capacity(kept_rows.len() * kept_cols.len());
1238 for &r in &kept_rows {
1239 for &c in &kept_cols {
1240 out.push(data[r * ncols + c]);
1241 }
1242 }
1243 Array::<T, Ix2>::from_vec(Ix2::new([kept_rows.len(), kept_cols.len()]), out)
1244}
1245
1246pub fn ma_compress_rows<T>(ma: &MaskedArray<T, Ix2>) -> FerrayResult<Array<T, Ix2>>
1252where
1253 T: Element + Copy,
1254{
1255 ma_compress_rowcols(ma, Some(0))
1256}
1257
1258pub fn ma_compress_cols<T>(ma: &MaskedArray<T, Ix2>) -> FerrayResult<Array<T, Ix2>>
1264where
1265 T: Element + Copy,
1266{
1267 ma_compress_rowcols(ma, Some(1))
1268}
1269
1270pub fn ma_mask_rowcols<T>(
1282 ma: &MaskedArray<T, Ix2>,
1283 axis: Option<usize>,
1284) -> FerrayResult<MaskedArray<T, Ix2>>
1285where
1286 T: Element + Copy,
1287{
1288 let shape = ma.data().shape();
1289 let (nrows, ncols) = (shape[0], shape[1]);
1290 let data: Vec<T> = ma.data().iter().copied().collect();
1291 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1292
1293 let mask_rows = matches!(axis, None | Some(0));
1294 let mask_cols = matches!(axis, None | Some(1));
1295
1296 let row_has_mask: Vec<bool> = (0..nrows)
1297 .map(|r| (0..ncols).any(|c| mask[r * ncols + c]))
1298 .collect();
1299 let col_has_mask: Vec<bool> = (0..ncols)
1300 .map(|c| (0..nrows).any(|r| mask[r * ncols + c]))
1301 .collect();
1302
1303 let mut new_mask = mask.clone();
1304 for r in 0..nrows {
1305 for c in 0..ncols {
1306 if (mask_rows && row_has_mask[r]) || (mask_cols && col_has_mask[c]) {
1307 new_mask[r * ncols + c] = true;
1308 }
1309 }
1310 }
1311
1312 let data_arr = Array::<T, Ix2>::from_vec(Ix2::new([nrows, ncols]), data)?;
1313 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([nrows, ncols]), new_mask)?;
1314 MaskedArray::new(data_arr, mask_arr)
1315}
1316
1317pub fn ma_cov<D>(
1340 x: &MaskedArray<f64, D>,
1341 rowvar: bool,
1342 bias: bool,
1343 ddof: Option<usize>,
1344) -> FerrayResult<MaskedArray<f64, Ix2>>
1345where
1346 D: Dimension,
1347{
1348 let ddof_val = ddof.unwrap_or(if bias { 0 } else { 1 }) as f64;
1349
1350 let ndim = x.ndim();
1352 if ndim > 2 {
1353 return Err(FerrayError::invalid_value(
1354 "ma.cov requires a 1-D or 2-D masked array",
1355 ));
1356 }
1357 let shape = x.data().shape();
1358 let flat_data: Vec<f64> = x.data().iter().copied().collect();
1359 let flat_mask: Vec<bool> = x.mask().iter().copied().collect();
1360
1361 let (nvars, nobs, mat_data, mat_mask): (usize, usize, Vec<f64>, Vec<bool>) = if ndim <= 1 {
1365 let n = flat_data.len();
1366 (1, n, flat_data, flat_mask)
1367 } else {
1368 let (r, c) = (shape[0], shape[1]);
1369 let eff_rowvar = rowvar || r == 1;
1370 if eff_rowvar {
1371 (r, c, flat_data, flat_mask)
1372 } else {
1373 let mut td = vec![0.0f64; r * c];
1375 let mut tm = vec![false; r * c];
1376 for i in 0..c {
1377 for k in 0..r {
1378 td[i * r + k] = flat_data[k * c + i];
1379 tm[i * r + k] = flat_mask[k * c + i];
1380 }
1381 }
1382 (c, r, td, tm)
1383 }
1384 };
1385
1386 let mut centered = vec![0.0f64; nvars * nobs];
1388 let mut notmask = vec![0.0f64; nvars * nobs];
1389 for i in 0..nvars {
1390 let mut sum = 0.0;
1391 let mut cnt = 0.0;
1392 for k in 0..nobs {
1393 let idx = i * nobs + k;
1394 if !mat_mask[idx] {
1395 sum += mat_data[idx];
1396 cnt += 1.0;
1397 notmask[idx] = 1.0;
1398 }
1399 }
1400 let mean = if cnt > 0.0 { sum / cnt } else { 0.0 };
1401 for k in 0..nobs {
1402 let idx = i * nobs + k;
1403 centered[idx] = if mat_mask[idx] {
1405 0.0
1406 } else {
1407 mat_data[idx] - mean
1408 };
1409 }
1410 }
1411
1412 let mut cov_data = vec![0.0f64; nvars * nvars];
1415 let mut cov_mask = vec![false; nvars * nvars];
1416 for i in 0..nvars {
1417 for j in i..nvars {
1418 let mut dot = 0.0;
1419 let mut fact = 0.0;
1420 for k in 0..nobs {
1421 dot += centered[i * nobs + k] * centered[j * nobs + k];
1422 fact += notmask[i * nobs + k] * notmask[j * nobs + k];
1423 }
1424 fact -= ddof_val;
1425 let (val, masked) = if fact <= 0.0 {
1426 (0.0, true)
1427 } else {
1428 (dot / fact, false)
1429 };
1430 cov_data[i * nvars + j] = val;
1431 cov_data[j * nvars + i] = val;
1432 cov_mask[i * nvars + j] = masked;
1433 cov_mask[j * nvars + i] = masked;
1434 }
1435 }
1436
1437 let data_arr = Array::<f64, Ix2>::from_vec(Ix2::new([nvars, nvars]), cov_data)?;
1438 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([nvars, nvars]), cov_mask)?;
1439 MaskedArray::new(data_arr, mask_arr)
1440}
1441
1442pub fn ma_corrcoef<D>(x: &MaskedArray<f64, D>, rowvar: bool) -> FerrayResult<MaskedArray<f64, Ix2>>
1453where
1454 D: Dimension,
1455{
1456 let cov = ma_cov(x, rowvar, false, None)?;
1457 let n = cov.data().shape()[0];
1458 let cov_data: Vec<f64> = cov.data().iter().copied().collect();
1459 let cov_mask: Vec<bool> = cov.mask().iter().copied().collect();
1460
1461 let mut std = vec![0.0f64; n];
1463 let mut std_masked = vec![false; n];
1464 for i in 0..n {
1465 std_masked[i] = cov_mask[i * n + i];
1466 std[i] = cov_data[i * n + i].sqrt();
1467 }
1468
1469 let mut corr_data = vec![0.0f64; n * n];
1470 let mut corr_mask = vec![false; n * n];
1471 for i in 0..n {
1472 for j in 0..n {
1473 let d = std[i] * std[j];
1474 let masked = cov_mask[i * n + j] || std_masked[i] || std_masked[j] || d == 0.0;
1475 if masked {
1476 corr_mask[i * n + j] = true;
1477 corr_data[i * n + j] = 0.0;
1478 } else {
1479 let v = cov_data[i * n + j] / d;
1482 corr_data[i * n + j] = v.clamp(-1.0, 1.0);
1483 }
1484 }
1485 }
1486
1487 let data_arr = Array::<f64, Ix2>::from_vec(Ix2::new([n, n]), corr_data)?;
1488 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([n, n]), corr_mask)?;
1489 MaskedArray::new(data_arr, mask_arr)
1490}
1491
1492pub fn ma_apply_along_axis<T, F>(
1507 ma: &MaskedArray<T, IxDyn>,
1508 axis: usize,
1509 mut f: F,
1510) -> FerrayResult<MaskedArray<T, IxDyn>>
1511where
1512 T: Element + Copy,
1513 F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
1514{
1515 let shape = ma.shape().to_vec();
1516 if axis >= shape.len() {
1517 return Err(FerrayError::axis_out_of_bounds(axis, shape.len()));
1518 }
1519 let lane_len = shape[axis];
1520 let out_shape: Vec<usize> = shape
1521 .iter()
1522 .enumerate()
1523 .filter(|&(i, _)| i != axis)
1524 .map(|(_, &s)| s)
1525 .collect();
1526 let out_size: usize = out_shape.iter().product::<usize>().max(1);
1527
1528 let ndim = shape.len();
1529 let mut strides = vec![1usize; ndim];
1530 for i in (0..ndim.saturating_sub(1)).rev() {
1531 strides[i] = strides[i + 1] * shape[i + 1];
1532 }
1533
1534 let data: Vec<T> = ma.data().iter().copied().collect();
1535 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1536
1537 let mut out_data = Vec::with_capacity(out_size);
1538 let mut out_mask = Vec::with_capacity(out_size);
1539 let mut out_multi = vec![0usize; out_shape.len()];
1540
1541 for _ in 0..out_size {
1542 let mut lane_data = Vec::with_capacity(lane_len);
1544 let mut lane_mask = Vec::with_capacity(lane_len);
1545 let mut full_idx = vec![0usize; ndim];
1546 let mut oi = 0;
1548 for (d, slot) in full_idx.iter_mut().enumerate() {
1549 if d == axis {
1550 continue;
1551 }
1552 *slot = out_multi[oi];
1553 oi += 1;
1554 }
1555 for j in 0..lane_len {
1556 full_idx[axis] = j;
1557 let flat: usize = full_idx
1558 .iter()
1559 .zip(strides.iter())
1560 .map(|(i, s)| i * s)
1561 .sum();
1562 lane_data.push(data[flat]);
1563 lane_mask.push(mask[flat]);
1564 }
1565 let lane_data_arr = Array::from_vec(Ix1::new([lane_len]), lane_data)?;
1566 let lane_mask_arr = Array::from_vec(Ix1::new([lane_len]), lane_mask)?;
1567 let lane_ma = MaskedArray::new(lane_data_arr, lane_mask_arr)?;
1568 let (val, masked) = f(&lane_ma)?;
1569 out_data.push(val);
1570 out_mask.push(masked);
1571
1572 for d in (0..out_shape.len()).rev() {
1574 out_multi[d] += 1;
1575 if out_multi[d] < out_shape[d] {
1576 break;
1577 }
1578 out_multi[d] = 0;
1579 }
1580 }
1581
1582 let data_arr = Array::from_vec(IxDyn::new(&out_shape), out_data)?;
1583 let mask_arr = Array::from_vec(IxDyn::new(&out_shape), out_mask)?;
1584 MaskedArray::new(data_arr, mask_arr)
1585}
1586
1587pub fn ma_apply_over_axes<T, F>(
1595 ma: &MaskedArray<T, IxDyn>,
1596 axes: &[usize],
1597 mut f: F,
1598) -> FerrayResult<MaskedArray<T, IxDyn>>
1599where
1600 T: Element + Copy,
1601 F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
1602{
1603 let mut result = ma.clone();
1604 let mut sorted: Vec<usize> = axes.to_vec();
1605 sorted.sort_unstable();
1606 for (offset, &ax) in sorted.iter().enumerate() {
1607 let adjusted = ax.saturating_sub(offset);
1610 result = ma_apply_along_axis(&result, adjusted, &mut f)?;
1611 }
1612 Ok(result)
1613}
1614
1615pub fn ma_vander<T>(x: &MaskedArray<T, Ix1>, n: Option<usize>) -> FerrayResult<MaskedArray<T, Ix2>>
1634where
1635 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One + num_traits::Zero,
1636{
1637 let m = x.size();
1638 if m == 0 {
1639 return Err(FerrayError::invalid_value(
1640 "ma_vander: input must not be empty",
1641 ));
1642 }
1643 let cols = n.unwrap_or(m);
1644 let xs: Vec<T> = x.data().iter().copied().collect();
1645 let xmask: Vec<bool> = x.mask().iter().copied().collect();
1646 let zero = num_traits::zero::<T>();
1647 let one = num_traits::one::<T>();
1648 let mut data = vec![one; m * cols];
1649 for (i, &xi) in xs.iter().enumerate() {
1650 let mut acc = one;
1653 let mut powers = Vec::with_capacity(cols);
1654 for _ in 0..cols {
1655 powers.push(acc);
1656 acc = acc * xi;
1657 }
1658 for (j, p) in powers.iter().enumerate() {
1659 data[i * cols + (cols - 1 - j)] = *p;
1661 }
1662 if xmask[i] {
1664 for slot in data[i * cols..(i + 1) * cols].iter_mut() {
1665 *slot = zero;
1666 }
1667 }
1668 }
1669 let data_arr = Array::from_vec(Ix2::new([m, cols]), data)?;
1670 MaskedArray::from_data(data_arr)
1672}
1673
1674#[must_use]
1688pub fn default_fill_value_f64() -> f64 {
1689 1e20
1690}
1691
1692#[must_use]
1694pub fn default_fill_value_f32() -> f32 {
1695 1e20_f32
1696}
1697
1698#[must_use]
1700pub const fn default_fill_value_bool() -> bool {
1701 false
1702}
1703
1704#[must_use]
1706pub const fn default_fill_value_i64() -> i64 {
1707 999_999
1708}
1709
1710#[must_use]
1713pub fn maximum_fill_value<T: Float>() -> T {
1714 T::infinity()
1715}
1716
1717#[must_use]
1720pub fn minimum_fill_value<T: Float>() -> T {
1721 T::neg_infinity()
1722}
1723
1724pub fn common_fill_value<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> T
1727where
1728 T: Element + Copy + PartialEq,
1729 D: Dimension,
1730{
1731 if a.fill_value() == b.fill_value() {
1732 a.fill_value()
1733 } else {
1734 T::zero()
1735 }
1736}
1737
1738macro_rules! ma_cmp {
1743 ($name:ident, $op:tt, $bound:path) => {
1744 pub fn $name<T, D>(
1749 a: &MaskedArray<T, D>,
1750 b: &MaskedArray<T, D>,
1751 ) -> FerrayResult<MaskedArray<bool, D>>
1752 where
1753 T: Element + Copy + $bound,
1754 D: Dimension,
1755 {
1756 if a.shape() != b.shape() {
1757 return Err(FerrayError::shape_mismatch(format!(
1758 "{}: shapes {:?} and {:?} differ",
1759 stringify!($name),
1760 a.shape(),
1761 b.shape(),
1762 )));
1763 }
1764 let data: Vec<bool> = a
1765 .data()
1766 .iter()
1767 .zip(b.data().iter())
1768 .map(|(x, y)| x $op y)
1769 .collect();
1770 let mask: Vec<bool> = a
1771 .mask()
1772 .iter()
1773 .zip(b.mask().iter())
1774 .map(|(x, y)| *x || *y)
1775 .collect();
1776 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1777 let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
1778 MaskedArray::new(data_arr, mask_arr)
1779 }
1780 };
1781}
1782
1783ma_cmp!(ma_equal, ==, PartialEq);
1784ma_cmp!(ma_not_equal, !=, PartialEq);
1785ma_cmp!(ma_less, <, PartialOrd);
1786ma_cmp!(ma_greater, >, PartialOrd);
1787ma_cmp!(ma_less_equal, <=, PartialOrd);
1788ma_cmp!(ma_greater_equal, >=, PartialOrd);
1789
1790pub fn ma_logical_and<D: Dimension>(
1795 a: &MaskedArray<bool, D>,
1796 b: &MaskedArray<bool, D>,
1797) -> FerrayResult<MaskedArray<bool, D>> {
1798 binary_bool(a, b, |x, y| x && y, "ma_logical_and")
1799}
1800
1801pub fn ma_logical_or<D: Dimension>(
1806 a: &MaskedArray<bool, D>,
1807 b: &MaskedArray<bool, D>,
1808) -> FerrayResult<MaskedArray<bool, D>> {
1809 binary_bool(a, b, |x, y| x || y, "ma_logical_or")
1810}
1811
1812pub fn ma_logical_xor<D: Dimension>(
1817 a: &MaskedArray<bool, D>,
1818 b: &MaskedArray<bool, D>,
1819) -> FerrayResult<MaskedArray<bool, D>> {
1820 binary_bool(a, b, |x, y| x ^ y, "ma_logical_xor")
1821}
1822
1823pub fn ma_logical_not<D: Dimension>(
1828 a: &MaskedArray<bool, D>,
1829) -> FerrayResult<MaskedArray<bool, D>> {
1830 let data: Vec<bool> = a.data().iter().map(|x| !*x).collect();
1831 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1832 let mask_arr: Array<bool, D> =
1833 Array::from_vec(a.mask().dim().clone(), a.mask().iter().copied().collect())?;
1834 MaskedArray::new(data_arr, mask_arr)
1835}
1836
1837fn binary_bool<D: Dimension>(
1838 a: &MaskedArray<bool, D>,
1839 b: &MaskedArray<bool, D>,
1840 op: impl Fn(bool, bool) -> bool,
1841 name: &str,
1842) -> FerrayResult<MaskedArray<bool, D>> {
1843 if a.shape() != b.shape() {
1844 return Err(FerrayError::shape_mismatch(format!(
1845 "{name}: shapes {:?} and {:?} differ",
1846 a.shape(),
1847 b.shape()
1848 )));
1849 }
1850 let data: Vec<bool> = a
1851 .data()
1852 .iter()
1853 .zip(b.data().iter())
1854 .map(|(x, y)| op(*x, *y))
1855 .collect();
1856 let mask: Vec<bool> = a
1857 .mask()
1858 .iter()
1859 .zip(b.mask().iter())
1860 .map(|(x, y)| *x || *y)
1861 .collect();
1862 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1863 let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
1864 MaskedArray::new(data_arr, mask_arr)
1865}
1866
1867#[must_use]
1875pub const fn is_masked_array<T, D>(_ma: &MaskedArray<T, D>) -> bool
1876where
1877 T: Element,
1878 D: Dimension,
1879{
1880 true
1881}
1882
1883#[must_use]
1885pub const fn is_ma<T, D>(ma: &MaskedArray<T, D>) -> bool
1886where
1887 T: Element,
1888 D: Dimension,
1889{
1890 is_masked_array(ma)
1891}
1892
1893pub fn getmaskarray<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<bool, D>>
1901where
1902 T: Element,
1903 D: Dimension,
1904{
1905 Array::from_vec(ma.mask().dim().clone(), ma.mask().iter().copied().collect())
1906}
1907
1908#[must_use]
1914pub fn ids<T, D>(ma: &MaskedArray<T, D>) -> (*const u8, *const u8)
1915where
1916 T: Element,
1917 D: Dimension,
1918{
1919 let data_ptr: *const u8 = ma.data() as *const _ as *const u8;
1920 let mask_ptr: *const u8 = ma.mask() as *const _ as *const u8;
1921 (data_ptr, mask_ptr)
1922}
1923
1924fn ezclump_true(mask: &[bool]) -> Vec<(usize, usize)> {
1938 let n = mask.len();
1939 let mut runs: Vec<(usize, usize)> = Vec::new();
1940 let mut i = 0usize;
1941 while i < n {
1942 if mask[i] {
1943 let start = i;
1944 while i < n && mask[i] {
1945 i += 1;
1946 }
1947 runs.push((start, i));
1948 } else {
1949 i += 1;
1950 }
1951 }
1952 runs
1953}
1954
1955fn flat_mask<T, D>(a: &MaskedArray<T, D>) -> Vec<bool>
1957where
1958 T: Element,
1959 D: Dimension,
1960{
1961 a.mask().iter().copied().collect()
1962}
1963
1964#[must_use]
1969pub fn clump_masked<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
1970where
1971 T: Element,
1972 D: Dimension,
1973{
1974 ezclump_true(&flat_mask(a))
1975}
1976
1977#[must_use]
1982pub fn clump_unmasked<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
1983where
1984 T: Element,
1985 D: Dimension,
1986{
1987 let inv: Vec<bool> = flat_mask(a).iter().map(|&m| !m).collect();
1988 ezclump_true(&inv)
1989}
1990
1991#[must_use]
1998pub fn flatnotmasked_edges<T, D>(a: &MaskedArray<T, D>) -> Option<[usize; 2]>
1999where
2000 T: Element,
2001 D: Dimension,
2002{
2003 let m = flat_mask(a);
2004 let size = m.len();
2005 if size == 0 {
2006 return None;
2007 }
2008 if !m.iter().any(|&b| b) {
2009 return Some([0, size - 1]);
2010 }
2011 let first = m.iter().position(|&b| !b)?;
2012 let last = m.iter().rposition(|&b| !b)?;
2013 Some([first, last])
2014}
2015
2016#[must_use]
2021pub fn flatnotmasked_contiguous<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
2022where
2023 T: Element,
2024 D: Dimension,
2025{
2026 let inv: Vec<bool> = flat_mask(a).iter().map(|&m| !m).collect();
2027 ezclump_true(&inv)
2028}
2029
2030#[must_use]
2037pub fn notmasked_edges<T, D>(a: &MaskedArray<T, D>) -> Option<[usize; 2]>
2038where
2039 T: Element,
2040 D: Dimension,
2041{
2042 flatnotmasked_edges(a)
2043}
2044
2045#[allow(
2061 clippy::type_complexity,
2062 reason = "mirrors numpy's two (rows, cols) tuples"
2063)]
2064pub fn notmasked_edges_axis2<T>(
2065 a: &MaskedArray<T, Ix2>,
2066 axis: usize,
2067) -> FerrayResult<((Vec<i64>, Vec<i64>), (Vec<i64>, Vec<i64>))>
2068where
2069 T: Element,
2070{
2071 if axis >= 2 {
2072 return Err(FerrayError::axis_out_of_bounds(axis, 2));
2073 }
2074 let shape = a.shape();
2075 let (rows, cols) = (shape[0], shape[1]);
2076 let mask: Vec<bool> = a.mask().iter().copied().collect();
2077
2078 let (mut first_rows, mut first_cols) = (Vec::new(), Vec::new());
2081 let (mut last_rows, mut last_cols) = (Vec::new(), Vec::new());
2082 let other_len = if axis == 0 { cols } else { rows };
2083 let axis_len = if axis == 0 { rows } else { cols };
2084 for o in 0..other_len {
2085 let mut first: Option<usize> = None;
2086 let mut last: Option<usize> = None;
2087 for k in 0..axis_len {
2088 let (r, c) = if axis == 0 { (k, o) } else { (o, k) };
2089 if !mask[r * cols + c] {
2090 if first.is_none() {
2091 first = Some(k);
2092 }
2093 last = Some(k);
2094 }
2095 }
2096 if let (Some(f), Some(l)) = (first, last) {
2097 let (fr_r, fr_c) = if axis == 0 { (f, o) } else { (o, f) };
2099 let (lr_r, lr_c) = if axis == 0 { (l, o) } else { (o, l) };
2100 first_rows.push(fr_r as i64);
2101 first_cols.push(fr_c as i64);
2102 last_rows.push(lr_r as i64);
2103 last_cols.push(lr_c as i64);
2104 }
2105 }
2106 Ok(((first_rows, first_cols), (last_rows, last_cols)))
2107}
2108
2109pub fn notmasked_contiguous_axis<T>(
2120 a: &MaskedArray<T, Ix2>,
2121 axis: usize,
2122) -> FerrayResult<Vec<Vec<(usize, usize)>>>
2123where
2124 T: Element,
2125{
2126 if axis >= 2 {
2127 return Err(FerrayError::axis_out_of_bounds(axis, 2));
2128 }
2129 let shape = a.shape();
2130 let (rows, cols) = (shape[0], shape[1]);
2131 let mask: Vec<bool> = a.mask().iter().copied().collect();
2132 let other = (axis + 1) % 2;
2135 let other_len = if other == 0 { rows } else { cols };
2136 let axis_len = if axis == 0 { rows } else { cols };
2137 let mut result: Vec<Vec<(usize, usize)>> = Vec::with_capacity(other_len);
2138 for o in 0..other_len {
2139 let mut lane: Vec<bool> = Vec::with_capacity(axis_len);
2140 for k in 0..axis_len {
2141 let (r, c) = if axis == 0 { (k, o) } else { (o, k) };
2142 lane.push(mask[r * cols + c]);
2143 }
2144 let inv: Vec<bool> = lane.iter().map(|&m| !m).collect();
2145 result.push(ezclump_true(&inv));
2146 }
2147 Ok(result)
2148}
2149
2150impl<T> MaskedArray<T, Ix2>
2155where
2156 T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
2157{
2158 pub fn ma_dot_2d(&self, other: &MaskedArray<T, Ix2>) -> FerrayResult<MaskedArray<T, Ix2>> {
2169 let a_shape = self.shape();
2170 let b_shape = other.shape();
2171 let (m, k1) = (a_shape[0], a_shape[1]);
2172 let (k2, n) = (b_shape[0], b_shape[1]);
2173 if k1 != k2 {
2174 return Err(FerrayError::shape_mismatch(format!(
2175 "ma_dot_2d: lhs.cols={k1} != rhs.rows={k2}"
2176 )));
2177 }
2178 let a_data: Vec<T> = self.data().iter().copied().collect();
2179 let a_mask: Vec<bool> = self.mask().iter().copied().collect();
2180 let b_data: Vec<T> = other.data().iter().copied().collect();
2181 let b_mask: Vec<bool> = other.mask().iter().copied().collect();
2182 let zero = <T as num_traits::Zero>::zero();
2183
2184 let mut out_data = vec![zero; m * n];
2185 let mut out_mask = vec![false; m * n];
2186 for i in 0..m {
2187 for j in 0..n {
2188 let mut acc = zero;
2189 let mut any_valid = false;
2192 for k in 0..k1 {
2193 let am = a_mask[i * k1 + k];
2194 let bm = b_mask[k * n + j];
2195 let av = if am { zero } else { a_data[i * k1 + k] };
2199 let bv = if bm { zero } else { b_data[k * n + j] };
2200 acc = acc + av * bv;
2201 if !am && !bm {
2202 any_valid = true;
2203 }
2204 }
2205 out_data[i * n + j] = acc;
2206 out_mask[i * n + j] = !any_valid;
2207 }
2208 }
2209 let data_arr = Array::<T, Ix2>::from_vec(Ix2::new([m, n]), out_data)?;
2210 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([m, n]), out_mask)?;
2211 MaskedArray::new(data_arr, mask_arr)
2212 }
2213}
2214
2215impl<T, D> MaskedArray<T, D>
2220where
2221 T: Element + PartialOrd + Copy,
2222 D: Dimension,
2223{
2224 pub fn argsort_axis(&self, axis: usize) -> FerrayResult<Array<u64, IxDyn>> {
2237 let ndim = self.ndim();
2238 if axis >= ndim {
2239 return Err(FerrayError::axis_out_of_bounds(axis, ndim));
2240 }
2241 let shape = self.shape().to_vec();
2242 let axis_len = shape[axis];
2243 let total: usize = shape.iter().product();
2244 let src_data: Vec<T> = self.data().iter().copied().collect();
2245 let src_mask: Vec<bool> = self.mask().iter().copied().collect();
2246
2247 let mut strides = vec![1usize; ndim];
2248 for i in (0..ndim.saturating_sub(1)).rev() {
2249 strides[i] = strides[i + 1] * shape[i + 1];
2250 }
2251
2252 let mut out = vec![0u64; total];
2253
2254 let outer_shape: Vec<usize> = shape
2255 .iter()
2256 .enumerate()
2257 .filter_map(|(i, &s)| if i == axis { None } else { Some(s) })
2258 .collect();
2259 let outer_size: usize = if outer_shape.is_empty() {
2260 1
2261 } else {
2262 outer_shape.iter().product()
2263 };
2264
2265 let mut outer_multi = vec![0usize; outer_shape.len()];
2266 for _ in 0..outer_size {
2267 let flat_of = |k: usize| -> usize {
2270 let mut flat = 0usize;
2271 let mut o = 0usize;
2272 for (i, &stride) in strides.iter().enumerate() {
2273 if i == axis {
2274 flat += stride * k;
2275 } else {
2276 flat += stride * outer_multi[o];
2277 o += 1;
2278 }
2279 }
2280 flat
2281 };
2282
2283 let mut order: Vec<usize> = (0..axis_len).collect();
2287 order.sort_by(|&x, &y| {
2288 let fx = flat_of(x);
2289 let fy = flat_of(y);
2290 match (src_mask[fx], src_mask[fy]) {
2291 (false, false) => src_data[fx]
2292 .partial_cmp(&src_data[fy])
2293 .unwrap_or(std::cmp::Ordering::Equal),
2294 (false, true) => std::cmp::Ordering::Less,
2295 (true, false) => std::cmp::Ordering::Greater,
2296 (true, true) => std::cmp::Ordering::Equal,
2297 }
2298 });
2299
2300 for (k, &pos) in order.iter().enumerate() {
2301 out[flat_of(k)] = pos as u64;
2302 }
2303
2304 for i in (0..outer_shape.len()).rev() {
2305 outer_multi[i] += 1;
2306 if outer_multi[i] < outer_shape[i] {
2307 break;
2308 }
2309 outer_multi[i] = 0;
2310 }
2311 }
2312
2313 Array::<u64, IxDyn>::from_vec(IxDyn::new(&shape), out)
2314 }
2315}
2316
2317#[cfg(test)]
2322mod tests {
2323 use super::*;
2324 use ferray_core::Ix1;
2325
2326 fn arr1f(v: Vec<f64>) -> Array<f64, Ix1> {
2327 let n = v.len();
2328 Array::from_vec(Ix1::new([n]), v).unwrap()
2329 }
2330
2331 fn mask1(v: Vec<bool>) -> Array<bool, Ix1> {
2332 let n = v.len();
2333 Array::from_vec(Ix1::new([n]), v).unwrap()
2334 }
2335
2336 fn ma_f1(d: Vec<f64>, m: Vec<bool>) -> MaskedArray<f64, Ix1> {
2337 MaskedArray::new(arr1f(d), mask1(m)).unwrap()
2338 }
2339
2340 #[test]
2341 fn prod_skips_masked() {
2342 let ma = ma_f1(vec![2.0, 3.0, 5.0, 7.0], vec![false, true, false, false]);
2343 let p = ma.prod().unwrap();
2344 assert!((p - 70.0).abs() < 1e-10); }
2346
2347 #[test]
2348 fn cumsum_propagates_mask() {
2349 let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
2350 let r = ma.cumsum_flat().unwrap();
2351 let mask: Vec<bool> = r.mask().iter().copied().collect();
2352 let data: Vec<f64> = r.data().iter().copied().collect();
2353 assert_eq!(mask, vec![false, true, false, false]);
2354 assert!((data[0] - 1.0).abs() < 1e-10);
2356 assert!((data[2] - 4.0).abs() < 1e-10);
2357 assert!((data[3] - 8.0).abs() < 1e-10);
2358 }
2359
2360 #[test]
2361 fn argmin_argmax_skip_masked() {
2362 let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
2363 assert_eq!(ma.argmin().unwrap(), 3);
2365 assert_eq!(ma.argmax().unwrap(), 2);
2366 }
2367
2368 #[test]
2369 fn ptp_basic() {
2370 let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
2371 assert!((ma.ptp().unwrap() - 6.0).abs() < 1e-10);
2373 }
2374
2375 #[test]
2376 fn median_odd_and_even() {
2377 let odd = ma_f1(vec![3.0, 1.0, 4.0, 1.0, 5.0], vec![false; 5]);
2378 assert!((odd.median().unwrap() - 3.0).abs() < 1e-10);
2379 let even = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
2380 assert!((even.median().unwrap() - 2.5).abs() < 1e-10);
2381 }
2382
2383 #[test]
2384 fn average_unweighted_matches_mean() {
2385 let ma = ma_f1(vec![2.0, 4.0, 6.0], vec![false; 3]);
2386 assert!((ma.average(None).unwrap() - 4.0).abs() < 1e-10);
2387 }
2388
2389 #[test]
2390 fn average_weighted_skips_masked() {
2391 let ma = ma_f1(vec![1.0, 100.0, 3.0], vec![false, true, false]);
2392 let w = arr1f(vec![1.0, 1.0, 3.0]);
2393 assert!((ma.average(Some(&w)).unwrap() - 2.5).abs() < 1e-10);
2395 }
2396
2397 #[test]
2398 fn anom_centers_at_zero() {
2399 let ma = ma_f1(vec![1.0, 2.0, 3.0], vec![false; 3]);
2400 let a = ma.anom().unwrap();
2401 let data: Vec<f64> = a.data().iter().copied().collect();
2402 assert!((data[0] - (-1.0)).abs() < 1e-10);
2403 assert!((data[1] - 0.0).abs() < 1e-10);
2404 assert!((data[2] - 1.0).abs() < 1e-10);
2405 }
2406
2407 #[test]
2408 fn masked_all_is_fully_masked() {
2409 let ma: MaskedArray<f64, Ix1> = masked_all(Ix1::new([5])).unwrap();
2410 let mask: Vec<bool> = ma.mask().iter().copied().collect();
2411 assert_eq!(mask, vec![true; 5]);
2412 }
2413
2414 #[test]
2415 fn masked_values_within_tol() {
2416 let arr = arr1f(vec![1.0, 1.0001, 2.0]);
2417 let r = masked_values(&arr, 1.0, 1e-3, 0.0).unwrap();
2418 let mask: Vec<bool> = r.mask().iter().copied().collect();
2419 assert_eq!(mask, vec![true, true, false]);
2420 }
2421
2422 #[test]
2423 fn harden_mask_blocks_clearing() {
2424 let mut ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
2425 ma.harden_mask().unwrap();
2426 assert!(ma.is_hard_mask());
2427 ma.set_mask_flat(1, false).unwrap();
2429 let mask: Vec<bool> = ma.mask().iter().copied().collect();
2430 assert_eq!(mask, vec![false, true]);
2432 ma.soften_mask().unwrap();
2433 ma.set_mask_flat(1, false).unwrap();
2434 let mask2: Vec<bool> = ma.mask().iter().copied().collect();
2435 assert_eq!(mask2, vec![false, false]);
2436 }
2437
2438 #[test]
2439 fn mask_or_unions() {
2440 let m1 = mask1(vec![true, false, false]);
2441 let m2 = mask1(vec![false, true, false]);
2442 let r = mask_or(&m1, &m2).unwrap();
2443 let v: Vec<bool> = r.iter().copied().collect();
2444 assert_eq!(v, vec![true, true, false]);
2445 }
2446
2447 #[test]
2448 fn make_mask_none_is_all_false() {
2449 let m: Array<bool, Ix1> = make_mask_none(Ix1::new([3])).unwrap();
2450 let v: Vec<bool> = m.iter().copied().collect();
2451 assert_eq!(v, vec![false; 3]);
2452 }
2453
2454 #[test]
2455 fn clip_unmasked_only() {
2456 let ma = ma_f1(vec![-5.0, 0.0, 5.0, 10.0], vec![false, false, false, true]);
2457 let r = ma.clip(-1.0, 3.0).unwrap();
2458 let data: Vec<f64> = r.data().iter().copied().collect();
2459 assert_eq!(data, vec![-1.0, 0.0, 3.0, 10.0]);
2461 }
2462
2463 #[test]
2464 fn repeat_propagates_mask() {
2465 let ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
2466 let r = ma.repeat(3).unwrap();
2467 let data: Vec<f64> = r.data().iter().copied().collect();
2468 let mask: Vec<bool> = r.mask().iter().copied().collect();
2469 assert_eq!(data, vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
2470 assert_eq!(mask, vec![false, false, false, true, true, true]);
2471 }
2472
2473 #[test]
2474 fn ma_unique_dedups_unmasked() {
2475 let ma = ma_f1(
2476 vec![3.0, 1.0, 2.0, 1.0, 3.0, 9.0],
2477 vec![false, false, false, false, false, true],
2478 );
2479 let v = ma_unique(&ma).unwrap();
2480 let data: Vec<f64> = v.iter().copied().collect();
2481 assert_eq!(data, vec![1.0, 2.0, 3.0]);
2482 }
2483
2484 #[test]
2485 fn ma_isin_basic() {
2486 let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
2487 let r = ma_isin(&ma, &[2.0, 4.0]).unwrap();
2488 let data: Vec<bool> = r.data().iter().copied().collect();
2489 assert_eq!(data, vec![false, true, false, true]);
2490 }
2491
2492 #[test]
2493 fn ma_dot_flat_skips_masked_pairs() {
2494 let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, true, false]);
2495 let b = ma_f1(vec![4.0, 5.0, 6.0], vec![false, false, false]);
2496 assert!((a.ma_dot_flat(&b).unwrap() - 22.0).abs() < 1e-10);
2499 }
2500
2501 #[test]
2502 fn fill_value_protocol_constants() {
2503 assert_eq!(default_fill_value_f64(), 1e20);
2504 assert!(!default_fill_value_bool());
2505 assert!(maximum_fill_value::<f64>().is_infinite());
2506 assert!(
2507 minimum_fill_value::<f64>().is_infinite()
2508 && minimum_fill_value::<f64>().is_sign_negative()
2509 );
2510 }
2511
2512 #[test]
2513 fn common_fill_value_returns_shared_or_zero() {
2514 let a = ma_f1(vec![1.0, 2.0], vec![false, false]).with_fill_value(99.0);
2515 let b = ma_f1(vec![3.0, 4.0], vec![false, false]).with_fill_value(99.0);
2516 assert_eq!(common_fill_value(&a, &b), 99.0);
2517 let c = ma_f1(vec![5.0, 6.0], vec![false, false]).with_fill_value(0.5);
2518 assert_eq!(common_fill_value(&a, &c), 0.0); }
2520
2521 #[test]
2522 fn ma_equal_and_friends_union_mask() {
2523 let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, true]);
2524 let b = ma_f1(vec![1.0, 9.0, 3.0], vec![false, true, false]);
2525 let r = ma_equal(&a, &b).unwrap();
2526 let data: Vec<bool> = r.data().iter().copied().collect();
2527 let mask: Vec<bool> = r.mask().iter().copied().collect();
2528 assert_eq!(data, vec![true, false, true]);
2529 assert_eq!(mask, vec![false, true, true]);
2530 }
2531
2532 #[test]
2533 fn ma_logical_and_basic() {
2534 let a = MaskedArray::new(
2535 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, true, false]).unwrap(),
2536 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
2537 )
2538 .unwrap();
2539 let b = MaskedArray::new(
2540 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, false, true]).unwrap(),
2541 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
2542 )
2543 .unwrap();
2544 let r = ma_logical_and(&a, &b).unwrap();
2545 let data: Vec<bool> = r.data().iter().copied().collect();
2546 assert_eq!(data, vec![true, false, false]);
2547 }
2548
2549 #[test]
2550 fn is_masked_array_always_true() {
2551 let ma = ma_f1(vec![1.0], vec![false]);
2552 assert!(is_masked_array(&ma));
2553 assert!(is_ma(&ma));
2554 }
2555
2556 #[test]
2557 fn getmaskarray_materializes() {
2558 let ma = MaskedArray::<f64, Ix1>::from_data(arr1f(vec![1.0, 2.0])).unwrap();
2559 let m = getmaskarray(&ma).unwrap();
2560 let v: Vec<bool> = m.iter().copied().collect();
2561 assert_eq!(v, vec![false; 2]);
2562 }
2563
2564 #[test]
2565 fn diagonal_main_and_offset() {
2566 use ferray_core::Ix2;
2567 let data = Array::<f64, Ix2>::from_vec(
2568 Ix2::new([3, 3]),
2569 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
2570 )
2571 .unwrap();
2572 let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
2573 let ma = MaskedArray::new(data, mask).unwrap();
2574 let main = ma.diagonal(0).unwrap();
2575 let main_data: Vec<f64> = main.data().iter().copied().collect();
2576 assert_eq!(main_data, vec![1.0, 5.0, 9.0]);
2577 let upper = ma.diagonal(1).unwrap();
2578 let upper_data: Vec<f64> = upper.data().iter().copied().collect();
2579 assert_eq!(upper_data, vec![2.0, 6.0]);
2580 }
2581
2582 #[test]
2583 fn trace_sums_diagonal() {
2584 use ferray_core::Ix2;
2585 let data = Array::<f64, Ix2>::from_vec(
2586 Ix2::new([3, 3]),
2587 vec![1.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 9.0],
2588 )
2589 .unwrap();
2590 let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
2591 let ma = MaskedArray::new(data, mask).unwrap();
2592 assert!((ma.trace(0).unwrap() - 15.0).abs() < 1e-10);
2593 }
2594
2595 #[test]
2596 fn ma_apply_along_axis_sum_lane() {
2597 use ferray_core::IxDyn;
2598 let data =
2599 Array::<f64, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
2600 .unwrap();
2601 let mask = Array::<bool, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![false; 6]).unwrap();
2602 let ma = MaskedArray::new(data, mask).unwrap();
2603 let result = ma_apply_along_axis(&ma, 1, |lane| {
2604 let s: f64 = lane.data().iter().copied().sum();
2605 Ok((s, false))
2606 })
2607 .unwrap();
2608 let v: Vec<f64> = result.data().iter().copied().collect();
2609 assert_eq!(v, vec![6.0, 15.0]);
2610 }
2611
2612 fn ab() -> (MaskedArray<f64, Ix1>, MaskedArray<f64, Ix1>) {
2623 (
2624 ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]),
2625 ma_f1(vec![3.0, 4.0, 5.0], vec![false, false, true]),
2626 )
2627 }
2628
2629 fn data_mask(m: &MaskedArray<f64, Ix1>) -> (Vec<f64>, Vec<bool>) {
2630 (
2631 m.data().iter().copied().collect(),
2632 m.mask().iter().copied().collect(),
2633 )
2634 }
2635
2636 #[test]
2637 fn ma_unique_masked_trails_one_masked_slot() -> FerrayResult<()> {
2638 let (a, _) = ab();
2639 let (data, mask) = data_mask(&ma_unique_masked(&a)?);
2640 assert_eq!(&data[..3], &[1.0, 3.0, 4.0]);
2642 assert_eq!(mask, vec![false, false, false, true]);
2643 Ok(())
2644 }
2645
2646 #[test]
2647 fn ma_intersect1d_common_with_both_masked() -> FerrayResult<()> {
2648 let (a, b) = ab();
2649 let (data, mask) = data_mask(&ma_intersect1d(&a, &b)?);
2650 assert_eq!(&data[..2], &[3.0, 4.0]);
2651 assert_eq!(mask, vec![false, false, true]);
2652 Ok(())
2653 }
2654
2655 #[test]
2656 fn ma_union1d_all_uniques_plus_masked() -> FerrayResult<()> {
2657 let (a, b) = ab();
2658 let (data, mask) = data_mask(&ma_union1d(&a, &b)?);
2659 assert_eq!(&data[..3], &[1.0, 3.0, 4.0]);
2660 assert_eq!(mask, vec![false, false, false, true]);
2661 Ok(())
2662 }
2663
2664 #[test]
2665 fn ma_setdiff1d_drops_masked_when_rhs_masked() -> FerrayResult<()> {
2666 let (a, b) = ab();
2667 let (data, mask) = data_mask(&ma_setdiff1d(&a, &b)?);
2668 assert_eq!(data, vec![1.0]);
2670 assert_eq!(mask, vec![false]);
2671 Ok(())
2672 }
2673
2674 #[test]
2675 fn ma_setxor1d_symmetric_difference() -> FerrayResult<()> {
2676 let (a, b) = ab();
2677 let (data, mask) = data_mask(&ma_setxor1d(&a, &b)?);
2678 assert_eq!(data, vec![1.0]);
2680 assert_eq!(mask, vec![false]);
2681 Ok(())
2682 }
2683
2684 fn m23() -> FerrayResult<MaskedArray<f64, Ix2>> {
2693 let data =
2694 Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])?;
2695 let mask = Array::<bool, Ix2>::from_vec(
2696 Ix2::new([2, 3]),
2697 vec![false, false, true, false, false, false],
2698 )?;
2699 MaskedArray::new(data, mask)
2700 }
2701
2702 #[test]
2703 fn ma_compress_rowcols_both() -> FerrayResult<()> {
2704 let r = ma_compress_rowcols(&m23()?, None)?;
2705 assert_eq!(r.shape(), &[1, 2]);
2706 let v: Vec<f64> = r.iter().copied().collect();
2707 assert_eq!(v, vec![4.0, 5.0]);
2708 Ok(())
2709 }
2710
2711 #[test]
2712 fn ma_compress_rows_and_cols() -> FerrayResult<()> {
2713 let rows = ma_compress_rows(&m23()?)?;
2714 assert_eq!(rows.shape(), &[1, 3]);
2715 assert_eq!(
2716 rows.iter().copied().collect::<Vec<f64>>(),
2717 vec![4.0, 5.0, 6.0]
2718 );
2719 let cols = ma_compress_cols(&m23()?)?;
2720 assert_eq!(cols.shape(), &[2, 2]);
2721 assert_eq!(
2722 cols.iter().copied().collect::<Vec<f64>>(),
2723 vec![1.0, 2.0, 4.0, 5.0]
2724 );
2725 Ok(())
2726 }
2727
2728 #[test]
2729 fn ma_mask_rowcols_masks_whole_row_and_col() -> FerrayResult<()> {
2730 let r = ma_mask_rowcols(&m23()?, None)?;
2731 let mask: Vec<bool> = r.mask().iter().copied().collect();
2732 assert_eq!(mask, vec![true, true, true, false, false, true]);
2734 let data: Vec<f64> = r.data().iter().copied().collect();
2735 assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
2736 Ok(())
2737 }
2738
2739 #[test]
2746 fn ma_cov_unmasked_pairs() -> FerrayResult<()> {
2747 let c = ma_cov(&m23()?, true, false, None)?;
2748 let data: Vec<f64> = c.data().iter().copied().collect();
2749 let mask: Vec<bool> = c.mask().iter().copied().collect();
2750 let expect = [0.5, 0.5, 0.5, 1.0];
2751 for (g, e) in data.iter().zip(expect.iter()) {
2752 assert!((g - e).abs() < 1e-12, "cov {g} != {e}");
2753 }
2754 assert_eq!(mask, vec![false; 4]);
2755 Ok(())
2756 }
2757
2758 #[test]
2759 fn ma_corrcoef_normalized() -> FerrayResult<()> {
2760 let c = ma_corrcoef(&m23()?, true)?;
2761 let data: Vec<f64> = c.data().iter().copied().collect();
2762 let expect = [1.0, 0.7071067811865475, 0.7071067811865475, 1.0];
2763 for (g, e) in data.iter().zip(expect.iter()) {
2764 assert!((g - e).abs() < 1e-12, "corr {g} != {e}");
2765 }
2766 Ok(())
2767 }
2768
2769 #[test]
2780 fn clump_run_length_matches_numpy() {
2781 let m = ma_f1(
2782 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
2783 vec![false, false, true, true, false, false],
2784 );
2785 assert_eq!(clump_masked(&m), vec![(2, 4)]);
2786 assert_eq!(clump_unmasked(&m), vec![(0, 2), (4, 6)]);
2787 assert_eq!(flatnotmasked_contiguous(&m), vec![(0, 2), (4, 6)]);
2788 assert_eq!(flatnotmasked_edges(&m), Some([0, 5]));
2789 assert_eq!(notmasked_edges(&m), Some([0, 5]));
2790 }
2791
2792 #[test]
2793 fn clump_nomask_and_allmask_edges() {
2794 let none = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, false]);
2797 assert_eq!(clump_masked(&none), vec![]);
2798 assert_eq!(clump_unmasked(&none), vec![(0, 3)]);
2799 assert_eq!(flatnotmasked_edges(&none), Some([0, 2]));
2800 let all = ma_f1(vec![1.0, 2.0, 3.0], vec![true, true, true]);
2803 assert_eq!(clump_unmasked(&all), vec![]);
2804 assert_eq!(clump_masked(&all), vec![(0, 3)]);
2805 assert_eq!(flatnotmasked_contiguous(&all), vec![]);
2806 assert_eq!(flatnotmasked_edges(&all), None);
2807 }
2808
2809 #[test]
2810 fn clump_leading_and_trailing_masked() {
2811 let mut mask = vec![false; 10];
2815 for &i in &[0usize, 1, 2, 6, 8, 9] {
2816 mask[i] = true;
2817 }
2818 let data: Vec<f64> = (0..10).map(|x| x as f64).collect();
2819 let m = ma_f1(data, mask);
2820 assert_eq!(clump_masked(&m), vec![(0, 3), (6, 7), (8, 10)]);
2821 assert_eq!(clump_unmasked(&m), vec![(3, 6), (7, 8)]);
2822 }
2823
2824 #[test]
2825 fn notmasked_contiguous_axis_2d_matches_numpy() -> FerrayResult<()> {
2826 use ferray_core::Ix2;
2827 let mask = vec![
2836 false, true, false, false, true, true, true, false, false, true, true, false,
2837 ];
2838 let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
2839 let d = Array::<f64, Ix2>::from_vec(Ix2::new([3, 4]), data)?;
2840 let mk = Array::<bool, Ix2>::from_vec(Ix2::new([3, 4]), mask)?;
2841 let m = MaskedArray::new(d, mk)?;
2842 let by0 = notmasked_contiguous_axis(&m, 0)?;
2843 assert_eq!(
2844 by0,
2845 vec![vec![(0, 1), (2, 3)], vec![], vec![(0, 1)], vec![(0, 3)]]
2846 );
2847 let by1 = notmasked_contiguous_axis(&m, 1)?;
2848 assert_eq!(
2849 by1,
2850 vec![vec![(0, 1), (2, 4)], vec![(3, 4)], vec![(0, 1), (3, 4)]]
2851 );
2852 Ok(())
2853 }
2854
2855 #[test]
2862 fn ma_dot_2d_matches_numpy() -> FerrayResult<()> {
2863 use ferray_core::Ix2;
2864 let d = Array::<f64, Ix2>::from_vec(Ix2::new([2, 2]), vec![1.0, 2.0, 3.0, 4.0])?;
2865 let mk = Array::<bool, Ix2>::from_vec(Ix2::new([2, 2]), vec![false, true, false, false])?;
2866 let a = MaskedArray::new(d, mk)?;
2867 let out = a.ma_dot_2d(&a)?;
2868 let data: Vec<f64> = out.data().iter().copied().collect();
2869 let mask: Vec<bool> = out.mask().iter().copied().collect();
2870 assert_eq!(data, vec![1.0, 0.0, 15.0, 16.0]);
2873 assert_eq!(mask, vec![false, true, false, false]);
2874 Ok(())
2875 }
2876
2877 #[test]
2884 fn argsort_axis_matches_numpy() -> FerrayResult<()> {
2885 use ferray_core::Ix2;
2886 let d = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![3.0, 1.0, 2.0, 6.0, 5.0, 4.0])?;
2887 let mk = Array::<bool, Ix2>::from_vec(
2888 Ix2::new([2, 3]),
2889 vec![false, false, true, false, false, false],
2890 )?;
2891 let b = MaskedArray::new(d, mk)?;
2892 let by1 = b.argsort_axis(1)?;
2893 let v1: Vec<u64> = by1.iter().copied().collect();
2894 assert_eq!(v1, vec![1, 0, 2, 2, 1, 0]);
2895 let by0 = b.argsort_axis(0)?;
2896 let v0: Vec<u64> = by0.iter().copied().collect();
2897 assert_eq!(v0, vec![0, 0, 1, 1, 1, 0]);
2898 Ok(())
2899 }
2900}