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 && let Some(placeholder) = ma.data().iter().nth(pos)
970 {
971 vals.push(*placeholder);
972 mask.push(true);
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
1121 && !m2_masked
1122 && let Some(p) = first_masked_value(ar1)
1123 {
1124 out.push((p, true));
1125 }
1126 build_ma_ix1(out)
1127}
1128
1129pub fn ma_setxor1d<T, D>(
1136 ar1: &MaskedArray<T, D>,
1137 ar2: &MaskedArray<T, D>,
1138) -> FerrayResult<MaskedArray<T, Ix1>>
1139where
1140 T: Element + Copy + PartialOrd,
1141 D: Dimension,
1142{
1143 let (v1, m1) = unique_parts(ar1);
1144 let (v2, m2) = unique_parts(ar2);
1145 let mut out: Vec<(T, bool)> = Vec::new();
1146 for v in &v1 {
1148 if !v2
1149 .iter()
1150 .any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal))
1151 {
1152 out.push((*v, false));
1153 }
1154 }
1155 for v in &v2 {
1157 if !v1
1158 .iter()
1159 .any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal))
1160 {
1161 out.push((*v, false));
1162 }
1163 }
1164 out.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
1165 if m1 ^ m2 {
1167 let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
1168 if let Some(p) = placeholder {
1169 out.push((p, true));
1170 }
1171 }
1172 build_ma_ix1(out)
1173}
1174
1175fn first_masked_value<T, D>(ma: &MaskedArray<T, D>) -> Option<T>
1177where
1178 T: Element + Copy,
1179 D: Dimension,
1180{
1181 ma.mask()
1182 .iter()
1183 .position(|&m| m)
1184 .and_then(|pos| ma.data().iter().nth(pos).copied())
1185}
1186
1187pub fn ma_compress_rowcols<T>(
1207 ma: &MaskedArray<T, Ix2>,
1208 axis: Option<usize>,
1209) -> FerrayResult<Array<T, Ix2>>
1210where
1211 T: Element + Copy,
1212{
1213 let shape = ma.data().shape();
1214 let (nrows, ncols) = (shape[0], shape[1]);
1215 let data: Vec<T> = ma.data().iter().copied().collect();
1216 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1217
1218 let drop_rows = matches!(axis, None | Some(0));
1219 let drop_cols = matches!(axis, None | Some(1));
1220
1221 let mut keep_row = vec![true; nrows];
1223 let mut keep_col = vec![true; ncols];
1224 if drop_rows {
1225 for (r, kr) in keep_row.iter_mut().enumerate() {
1226 *kr = !(0..ncols).any(|c| mask[r * ncols + c]);
1227 }
1228 }
1229 if drop_cols {
1230 for (c, kc) in keep_col.iter_mut().enumerate() {
1231 *kc = !(0..nrows).any(|r| mask[r * ncols + c]);
1232 }
1233 }
1234
1235 let kept_rows: Vec<usize> = (0..nrows).filter(|&r| keep_row[r]).collect();
1236 let kept_cols: Vec<usize> = (0..ncols).filter(|&c| keep_col[c]).collect();
1237
1238 let mut out: Vec<T> = Vec::with_capacity(kept_rows.len() * kept_cols.len());
1239 for &r in &kept_rows {
1240 for &c in &kept_cols {
1241 out.push(data[r * ncols + c]);
1242 }
1243 }
1244 Array::<T, Ix2>::from_vec(Ix2::new([kept_rows.len(), kept_cols.len()]), out)
1245}
1246
1247pub fn ma_compress_rows<T>(ma: &MaskedArray<T, Ix2>) -> FerrayResult<Array<T, Ix2>>
1253where
1254 T: Element + Copy,
1255{
1256 ma_compress_rowcols(ma, Some(0))
1257}
1258
1259pub fn ma_compress_cols<T>(ma: &MaskedArray<T, Ix2>) -> FerrayResult<Array<T, Ix2>>
1265where
1266 T: Element + Copy,
1267{
1268 ma_compress_rowcols(ma, Some(1))
1269}
1270
1271pub fn ma_mask_rowcols<T>(
1283 ma: &MaskedArray<T, Ix2>,
1284 axis: Option<usize>,
1285) -> FerrayResult<MaskedArray<T, Ix2>>
1286where
1287 T: Element + Copy,
1288{
1289 let shape = ma.data().shape();
1290 let (nrows, ncols) = (shape[0], shape[1]);
1291 let data: Vec<T> = ma.data().iter().copied().collect();
1292 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1293
1294 let mask_rows = matches!(axis, None | Some(0));
1295 let mask_cols = matches!(axis, None | Some(1));
1296
1297 let row_has_mask: Vec<bool> = (0..nrows)
1298 .map(|r| (0..ncols).any(|c| mask[r * ncols + c]))
1299 .collect();
1300 let col_has_mask: Vec<bool> = (0..ncols)
1301 .map(|c| (0..nrows).any(|r| mask[r * ncols + c]))
1302 .collect();
1303
1304 let mut new_mask = mask.clone();
1305 for r in 0..nrows {
1306 for c in 0..ncols {
1307 if (mask_rows && row_has_mask[r]) || (mask_cols && col_has_mask[c]) {
1308 new_mask[r * ncols + c] = true;
1309 }
1310 }
1311 }
1312
1313 let data_arr = Array::<T, Ix2>::from_vec(Ix2::new([nrows, ncols]), data)?;
1314 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([nrows, ncols]), new_mask)?;
1315 MaskedArray::new(data_arr, mask_arr)
1316}
1317
1318pub fn ma_cov<D>(
1341 x: &MaskedArray<f64, D>,
1342 rowvar: bool,
1343 bias: bool,
1344 ddof: Option<usize>,
1345) -> FerrayResult<MaskedArray<f64, Ix2>>
1346where
1347 D: Dimension,
1348{
1349 let ddof_val = ddof.unwrap_or(if bias { 0 } else { 1 }) as f64;
1350
1351 let ndim = x.ndim();
1353 if ndim > 2 {
1354 return Err(FerrayError::invalid_value(
1355 "ma.cov requires a 1-D or 2-D masked array",
1356 ));
1357 }
1358 let shape = x.data().shape();
1359 let flat_data: Vec<f64> = x.data().iter().copied().collect();
1360 let flat_mask: Vec<bool> = x.mask().iter().copied().collect();
1361
1362 let (nvars, nobs, mat_data, mat_mask): (usize, usize, Vec<f64>, Vec<bool>) = if ndim <= 1 {
1366 let n = flat_data.len();
1367 (1, n, flat_data, flat_mask)
1368 } else {
1369 let (r, c) = (shape[0], shape[1]);
1370 let eff_rowvar = rowvar || r == 1;
1371 if eff_rowvar {
1372 (r, c, flat_data, flat_mask)
1373 } else {
1374 let mut td = vec![0.0f64; r * c];
1376 let mut tm = vec![false; r * c];
1377 for i in 0..c {
1378 for k in 0..r {
1379 td[i * r + k] = flat_data[k * c + i];
1380 tm[i * r + k] = flat_mask[k * c + i];
1381 }
1382 }
1383 (c, r, td, tm)
1384 }
1385 };
1386
1387 let mut centered = vec![0.0f64; nvars * nobs];
1389 let mut notmask = vec![0.0f64; nvars * nobs];
1390 for i in 0..nvars {
1391 let mut sum = 0.0;
1392 let mut cnt = 0.0;
1393 for k in 0..nobs {
1394 let idx = i * nobs + k;
1395 if !mat_mask[idx] {
1396 sum += mat_data[idx];
1397 cnt += 1.0;
1398 notmask[idx] = 1.0;
1399 }
1400 }
1401 let mean = if cnt > 0.0 { sum / cnt } else { 0.0 };
1402 for k in 0..nobs {
1403 let idx = i * nobs + k;
1404 centered[idx] = if mat_mask[idx] {
1406 0.0
1407 } else {
1408 mat_data[idx] - mean
1409 };
1410 }
1411 }
1412
1413 let mut cov_data = vec![0.0f64; nvars * nvars];
1416 let mut cov_mask = vec![false; nvars * nvars];
1417 for i in 0..nvars {
1418 for j in i..nvars {
1419 let mut dot = 0.0;
1420 let mut fact = 0.0;
1421 for k in 0..nobs {
1422 dot += centered[i * nobs + k] * centered[j * nobs + k];
1423 fact += notmask[i * nobs + k] * notmask[j * nobs + k];
1424 }
1425 fact -= ddof_val;
1426 let (val, masked) = if fact <= 0.0 {
1427 (0.0, true)
1428 } else {
1429 (dot / fact, false)
1430 };
1431 cov_data[i * nvars + j] = val;
1432 cov_data[j * nvars + i] = val;
1433 cov_mask[i * nvars + j] = masked;
1434 cov_mask[j * nvars + i] = masked;
1435 }
1436 }
1437
1438 let data_arr = Array::<f64, Ix2>::from_vec(Ix2::new([nvars, nvars]), cov_data)?;
1439 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([nvars, nvars]), cov_mask)?;
1440 MaskedArray::new(data_arr, mask_arr)
1441}
1442
1443pub fn ma_corrcoef<D>(x: &MaskedArray<f64, D>, rowvar: bool) -> FerrayResult<MaskedArray<f64, Ix2>>
1454where
1455 D: Dimension,
1456{
1457 let cov = ma_cov(x, rowvar, false, None)?;
1458 let n = cov.data().shape()[0];
1459 let cov_data: Vec<f64> = cov.data().iter().copied().collect();
1460 let cov_mask: Vec<bool> = cov.mask().iter().copied().collect();
1461
1462 let mut std = vec![0.0f64; n];
1464 let mut std_masked = vec![false; n];
1465 for i in 0..n {
1466 std_masked[i] = cov_mask[i * n + i];
1467 std[i] = cov_data[i * n + i].sqrt();
1468 }
1469
1470 let mut corr_data = vec![0.0f64; n * n];
1471 let mut corr_mask = vec![false; n * n];
1472 for i in 0..n {
1473 for j in 0..n {
1474 let d = std[i] * std[j];
1475 let masked = cov_mask[i * n + j] || std_masked[i] || std_masked[j] || d == 0.0;
1476 if masked {
1477 corr_mask[i * n + j] = true;
1478 corr_data[i * n + j] = 0.0;
1479 } else {
1480 let v = cov_data[i * n + j] / d;
1483 corr_data[i * n + j] = v.clamp(-1.0, 1.0);
1484 }
1485 }
1486 }
1487
1488 let data_arr = Array::<f64, Ix2>::from_vec(Ix2::new([n, n]), corr_data)?;
1489 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([n, n]), corr_mask)?;
1490 MaskedArray::new(data_arr, mask_arr)
1491}
1492
1493pub fn ma_apply_along_axis<T, F>(
1508 ma: &MaskedArray<T, IxDyn>,
1509 axis: usize,
1510 mut f: F,
1511) -> FerrayResult<MaskedArray<T, IxDyn>>
1512where
1513 T: Element + Copy,
1514 F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
1515{
1516 let shape = ma.shape().to_vec();
1517 if axis >= shape.len() {
1518 return Err(FerrayError::axis_out_of_bounds(axis, shape.len()));
1519 }
1520 let lane_len = shape[axis];
1521 let out_shape: Vec<usize> = shape
1522 .iter()
1523 .enumerate()
1524 .filter(|&(i, _)| i != axis)
1525 .map(|(_, &s)| s)
1526 .collect();
1527 let out_size: usize = out_shape.iter().product::<usize>().max(1);
1528
1529 let ndim = shape.len();
1530 let mut strides = vec![1usize; ndim];
1531 for i in (0..ndim.saturating_sub(1)).rev() {
1532 strides[i] = strides[i + 1] * shape[i + 1];
1533 }
1534
1535 let data: Vec<T> = ma.data().iter().copied().collect();
1536 let mask: Vec<bool> = ma.mask().iter().copied().collect();
1537
1538 let mut out_data = Vec::with_capacity(out_size);
1539 let mut out_mask = Vec::with_capacity(out_size);
1540 let mut out_multi = vec![0usize; out_shape.len()];
1541
1542 for _ in 0..out_size {
1543 let mut lane_data = Vec::with_capacity(lane_len);
1545 let mut lane_mask = Vec::with_capacity(lane_len);
1546 let mut full_idx = vec![0usize; ndim];
1547 let mut oi = 0;
1549 for (d, slot) in full_idx.iter_mut().enumerate() {
1550 if d == axis {
1551 continue;
1552 }
1553 *slot = out_multi[oi];
1554 oi += 1;
1555 }
1556 for j in 0..lane_len {
1557 full_idx[axis] = j;
1558 let flat: usize = full_idx
1559 .iter()
1560 .zip(strides.iter())
1561 .map(|(i, s)| i * s)
1562 .sum();
1563 lane_data.push(data[flat]);
1564 lane_mask.push(mask[flat]);
1565 }
1566 let lane_data_arr = Array::from_vec(Ix1::new([lane_len]), lane_data)?;
1567 let lane_mask_arr = Array::from_vec(Ix1::new([lane_len]), lane_mask)?;
1568 let lane_ma = MaskedArray::new(lane_data_arr, lane_mask_arr)?;
1569 let (val, masked) = f(&lane_ma)?;
1570 out_data.push(val);
1571 out_mask.push(masked);
1572
1573 for d in (0..out_shape.len()).rev() {
1575 out_multi[d] += 1;
1576 if out_multi[d] < out_shape[d] {
1577 break;
1578 }
1579 out_multi[d] = 0;
1580 }
1581 }
1582
1583 let data_arr = Array::from_vec(IxDyn::new(&out_shape), out_data)?;
1584 let mask_arr = Array::from_vec(IxDyn::new(&out_shape), out_mask)?;
1585 MaskedArray::new(data_arr, mask_arr)
1586}
1587
1588pub fn ma_apply_over_axes<T, F>(
1596 ma: &MaskedArray<T, IxDyn>,
1597 axes: &[usize],
1598 mut f: F,
1599) -> FerrayResult<MaskedArray<T, IxDyn>>
1600where
1601 T: Element + Copy,
1602 F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
1603{
1604 let mut result = ma.clone();
1605 let mut sorted: Vec<usize> = axes.to_vec();
1606 sorted.sort_unstable();
1607 for (offset, &ax) in sorted.iter().enumerate() {
1608 let adjusted = ax.saturating_sub(offset);
1611 result = ma_apply_along_axis(&result, adjusted, &mut f)?;
1612 }
1613 Ok(result)
1614}
1615
1616pub fn ma_vander<T>(x: &MaskedArray<T, Ix1>, n: Option<usize>) -> FerrayResult<MaskedArray<T, Ix2>>
1635where
1636 T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One + num_traits::Zero,
1637{
1638 let m = x.size();
1639 if m == 0 {
1640 return Err(FerrayError::invalid_value(
1641 "ma_vander: input must not be empty",
1642 ));
1643 }
1644 let cols = n.unwrap_or(m);
1645 let xs: Vec<T> = x.data().iter().copied().collect();
1646 let xmask: Vec<bool> = x.mask().iter().copied().collect();
1647 let zero = num_traits::zero::<T>();
1648 let one = num_traits::one::<T>();
1649 let mut data = vec![one; m * cols];
1650 for (i, &xi) in xs.iter().enumerate() {
1651 let mut acc = one;
1654 let mut powers = Vec::with_capacity(cols);
1655 for _ in 0..cols {
1656 powers.push(acc);
1657 acc = acc * xi;
1658 }
1659 for (j, p) in powers.iter().enumerate() {
1660 data[i * cols + (cols - 1 - j)] = *p;
1662 }
1663 if xmask[i] {
1665 for slot in data[i * cols..(i + 1) * cols].iter_mut() {
1666 *slot = zero;
1667 }
1668 }
1669 }
1670 let data_arr = Array::from_vec(Ix2::new([m, cols]), data)?;
1671 MaskedArray::from_data(data_arr)
1673}
1674
1675#[must_use]
1689pub fn default_fill_value_f64() -> f64 {
1690 1e20
1691}
1692
1693#[must_use]
1695pub fn default_fill_value_f32() -> f32 {
1696 1e20_f32
1697}
1698
1699#[must_use]
1701pub const fn default_fill_value_bool() -> bool {
1702 false
1703}
1704
1705#[must_use]
1707pub const fn default_fill_value_i64() -> i64 {
1708 999_999
1709}
1710
1711#[must_use]
1714pub fn maximum_fill_value<T: Float>() -> T {
1715 T::infinity()
1716}
1717
1718#[must_use]
1721pub fn minimum_fill_value<T: Float>() -> T {
1722 T::neg_infinity()
1723}
1724
1725pub fn common_fill_value<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> T
1728where
1729 T: Element + Copy + PartialEq,
1730 D: Dimension,
1731{
1732 if a.fill_value() == b.fill_value() {
1733 a.fill_value()
1734 } else {
1735 T::zero()
1736 }
1737}
1738
1739macro_rules! ma_cmp {
1744 ($name:ident, $op:tt, $bound:path) => {
1745 pub fn $name<T, D>(
1750 a: &MaskedArray<T, D>,
1751 b: &MaskedArray<T, D>,
1752 ) -> FerrayResult<MaskedArray<bool, D>>
1753 where
1754 T: Element + Copy + $bound,
1755 D: Dimension,
1756 {
1757 if a.shape() != b.shape() {
1758 return Err(FerrayError::shape_mismatch(format!(
1759 "{}: shapes {:?} and {:?} differ",
1760 stringify!($name),
1761 a.shape(),
1762 b.shape(),
1763 )));
1764 }
1765 let data: Vec<bool> = a
1766 .data()
1767 .iter()
1768 .zip(b.data().iter())
1769 .map(|(x, y)| x $op y)
1770 .collect();
1771 let mask: Vec<bool> = a
1772 .mask()
1773 .iter()
1774 .zip(b.mask().iter())
1775 .map(|(x, y)| *x || *y)
1776 .collect();
1777 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1778 let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
1779 MaskedArray::new(data_arr, mask_arr)
1780 }
1781 };
1782}
1783
1784ma_cmp!(ma_equal, ==, PartialEq);
1785ma_cmp!(ma_not_equal, !=, PartialEq);
1786ma_cmp!(ma_less, <, PartialOrd);
1787ma_cmp!(ma_greater, >, PartialOrd);
1788ma_cmp!(ma_less_equal, <=, PartialOrd);
1789ma_cmp!(ma_greater_equal, >=, PartialOrd);
1790
1791pub fn ma_logical_and<D: Dimension>(
1796 a: &MaskedArray<bool, D>,
1797 b: &MaskedArray<bool, D>,
1798) -> FerrayResult<MaskedArray<bool, D>> {
1799 binary_bool(a, b, |x, y| x && y, "ma_logical_and")
1800}
1801
1802pub fn ma_logical_or<D: Dimension>(
1807 a: &MaskedArray<bool, D>,
1808 b: &MaskedArray<bool, D>,
1809) -> FerrayResult<MaskedArray<bool, D>> {
1810 binary_bool(a, b, |x, y| x || y, "ma_logical_or")
1811}
1812
1813pub fn ma_logical_xor<D: Dimension>(
1818 a: &MaskedArray<bool, D>,
1819 b: &MaskedArray<bool, D>,
1820) -> FerrayResult<MaskedArray<bool, D>> {
1821 binary_bool(a, b, |x, y| x ^ y, "ma_logical_xor")
1822}
1823
1824pub fn ma_logical_not<D: Dimension>(
1829 a: &MaskedArray<bool, D>,
1830) -> FerrayResult<MaskedArray<bool, D>> {
1831 let data: Vec<bool> = a.data().iter().map(|x| !*x).collect();
1832 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1833 let mask_arr: Array<bool, D> =
1834 Array::from_vec(a.mask().dim().clone(), a.mask().iter().copied().collect())?;
1835 MaskedArray::new(data_arr, mask_arr)
1836}
1837
1838fn binary_bool<D: Dimension>(
1839 a: &MaskedArray<bool, D>,
1840 b: &MaskedArray<bool, D>,
1841 op: impl Fn(bool, bool) -> bool,
1842 name: &str,
1843) -> FerrayResult<MaskedArray<bool, D>> {
1844 if a.shape() != b.shape() {
1845 return Err(FerrayError::shape_mismatch(format!(
1846 "{name}: shapes {:?} and {:?} differ",
1847 a.shape(),
1848 b.shape()
1849 )));
1850 }
1851 let data: Vec<bool> = a
1852 .data()
1853 .iter()
1854 .zip(b.data().iter())
1855 .map(|(x, y)| op(*x, *y))
1856 .collect();
1857 let mask: Vec<bool> = a
1858 .mask()
1859 .iter()
1860 .zip(b.mask().iter())
1861 .map(|(x, y)| *x || *y)
1862 .collect();
1863 let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
1864 let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
1865 MaskedArray::new(data_arr, mask_arr)
1866}
1867
1868#[must_use]
1876pub const fn is_masked_array<T, D>(_ma: &MaskedArray<T, D>) -> bool
1877where
1878 T: Element,
1879 D: Dimension,
1880{
1881 true
1882}
1883
1884#[must_use]
1886pub const fn is_ma<T, D>(ma: &MaskedArray<T, D>) -> bool
1887where
1888 T: Element,
1889 D: Dimension,
1890{
1891 is_masked_array(ma)
1892}
1893
1894pub fn getmaskarray<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<bool, D>>
1902where
1903 T: Element,
1904 D: Dimension,
1905{
1906 Array::from_vec(ma.mask().dim().clone(), ma.mask().iter().copied().collect())
1907}
1908
1909#[must_use]
1915pub fn ids<T, D>(ma: &MaskedArray<T, D>) -> (*const u8, *const u8)
1916where
1917 T: Element,
1918 D: Dimension,
1919{
1920 let data_ptr: *const u8 = ma.data() as *const _ as *const u8;
1921 let mask_ptr: *const u8 = ma.mask() as *const _ as *const u8;
1922 (data_ptr, mask_ptr)
1923}
1924
1925fn ezclump_true(mask: &[bool]) -> Vec<(usize, usize)> {
1939 let n = mask.len();
1940 let mut runs: Vec<(usize, usize)> = Vec::new();
1941 let mut i = 0usize;
1942 while i < n {
1943 if mask[i] {
1944 let start = i;
1945 while i < n && mask[i] {
1946 i += 1;
1947 }
1948 runs.push((start, i));
1949 } else {
1950 i += 1;
1951 }
1952 }
1953 runs
1954}
1955
1956fn flat_mask<T, D>(a: &MaskedArray<T, D>) -> Vec<bool>
1958where
1959 T: Element,
1960 D: Dimension,
1961{
1962 a.mask().iter().copied().collect()
1963}
1964
1965#[must_use]
1970pub fn clump_masked<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
1971where
1972 T: Element,
1973 D: Dimension,
1974{
1975 ezclump_true(&flat_mask(a))
1976}
1977
1978#[must_use]
1983pub fn clump_unmasked<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
1984where
1985 T: Element,
1986 D: Dimension,
1987{
1988 let inv: Vec<bool> = flat_mask(a).iter().map(|&m| !m).collect();
1989 ezclump_true(&inv)
1990}
1991
1992#[must_use]
1999pub fn flatnotmasked_edges<T, D>(a: &MaskedArray<T, D>) -> Option<[usize; 2]>
2000where
2001 T: Element,
2002 D: Dimension,
2003{
2004 let m = flat_mask(a);
2005 let size = m.len();
2006 if size == 0 {
2007 return None;
2008 }
2009 if !m.iter().any(|&b| b) {
2010 return Some([0, size - 1]);
2011 }
2012 let first = m.iter().position(|&b| !b)?;
2013 let last = m.iter().rposition(|&b| !b)?;
2014 Some([first, last])
2015}
2016
2017#[must_use]
2022pub fn flatnotmasked_contiguous<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
2023where
2024 T: Element,
2025 D: Dimension,
2026{
2027 let inv: Vec<bool> = flat_mask(a).iter().map(|&m| !m).collect();
2028 ezclump_true(&inv)
2029}
2030
2031#[must_use]
2038pub fn notmasked_edges<T, D>(a: &MaskedArray<T, D>) -> Option<[usize; 2]>
2039where
2040 T: Element,
2041 D: Dimension,
2042{
2043 flatnotmasked_edges(a)
2044}
2045
2046#[allow(
2062 clippy::type_complexity,
2063 reason = "mirrors numpy's two (rows, cols) tuples"
2064)]
2065pub fn notmasked_edges_axis2<T>(
2066 a: &MaskedArray<T, Ix2>,
2067 axis: usize,
2068) -> FerrayResult<((Vec<i64>, Vec<i64>), (Vec<i64>, Vec<i64>))>
2069where
2070 T: Element,
2071{
2072 if axis >= 2 {
2073 return Err(FerrayError::axis_out_of_bounds(axis, 2));
2074 }
2075 let shape = a.shape();
2076 let (rows, cols) = (shape[0], shape[1]);
2077 let mask: Vec<bool> = a.mask().iter().copied().collect();
2078
2079 let (mut first_rows, mut first_cols) = (Vec::new(), Vec::new());
2082 let (mut last_rows, mut last_cols) = (Vec::new(), Vec::new());
2083 let other_len = if axis == 0 { cols } else { rows };
2084 let axis_len = if axis == 0 { rows } else { cols };
2085 for o in 0..other_len {
2086 let mut first: Option<usize> = None;
2087 let mut last: Option<usize> = None;
2088 for k in 0..axis_len {
2089 let (r, c) = if axis == 0 { (k, o) } else { (o, k) };
2090 if !mask[r * cols + c] {
2091 if first.is_none() {
2092 first = Some(k);
2093 }
2094 last = Some(k);
2095 }
2096 }
2097 if let (Some(f), Some(l)) = (first, last) {
2098 let (fr_r, fr_c) = if axis == 0 { (f, o) } else { (o, f) };
2100 let (lr_r, lr_c) = if axis == 0 { (l, o) } else { (o, l) };
2101 first_rows.push(fr_r as i64);
2102 first_cols.push(fr_c as i64);
2103 last_rows.push(lr_r as i64);
2104 last_cols.push(lr_c as i64);
2105 }
2106 }
2107 Ok(((first_rows, first_cols), (last_rows, last_cols)))
2108}
2109
2110pub fn notmasked_contiguous_axis<T>(
2121 a: &MaskedArray<T, Ix2>,
2122 axis: usize,
2123) -> FerrayResult<Vec<Vec<(usize, usize)>>>
2124where
2125 T: Element,
2126{
2127 if axis >= 2 {
2128 return Err(FerrayError::axis_out_of_bounds(axis, 2));
2129 }
2130 let shape = a.shape();
2131 let (rows, cols) = (shape[0], shape[1]);
2132 let mask: Vec<bool> = a.mask().iter().copied().collect();
2133 let other = (axis + 1) % 2;
2136 let other_len = if other == 0 { rows } else { cols };
2137 let axis_len = if axis == 0 { rows } else { cols };
2138 let mut result: Vec<Vec<(usize, usize)>> = Vec::with_capacity(other_len);
2139 for o in 0..other_len {
2140 let mut lane: Vec<bool> = Vec::with_capacity(axis_len);
2141 for k in 0..axis_len {
2142 let (r, c) = if axis == 0 { (k, o) } else { (o, k) };
2143 lane.push(mask[r * cols + c]);
2144 }
2145 let inv: Vec<bool> = lane.iter().map(|&m| !m).collect();
2146 result.push(ezclump_true(&inv));
2147 }
2148 Ok(result)
2149}
2150
2151impl<T> MaskedArray<T, Ix2>
2156where
2157 T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
2158{
2159 pub fn ma_dot_2d(&self, other: &MaskedArray<T, Ix2>) -> FerrayResult<MaskedArray<T, Ix2>> {
2170 let a_shape = self.shape();
2171 let b_shape = other.shape();
2172 let (m, k1) = (a_shape[0], a_shape[1]);
2173 let (k2, n) = (b_shape[0], b_shape[1]);
2174 if k1 != k2 {
2175 return Err(FerrayError::shape_mismatch(format!(
2176 "ma_dot_2d: lhs.cols={k1} != rhs.rows={k2}"
2177 )));
2178 }
2179 let a_data: Vec<T> = self.data().iter().copied().collect();
2180 let a_mask: Vec<bool> = self.mask().iter().copied().collect();
2181 let b_data: Vec<T> = other.data().iter().copied().collect();
2182 let b_mask: Vec<bool> = other.mask().iter().copied().collect();
2183 let zero = <T as num_traits::Zero>::zero();
2184
2185 let mut out_data = vec![zero; m * n];
2186 let mut out_mask = vec![false; m * n];
2187 for i in 0..m {
2188 for j in 0..n {
2189 let mut acc = zero;
2190 let mut any_valid = false;
2193 for k in 0..k1 {
2194 let am = a_mask[i * k1 + k];
2195 let bm = b_mask[k * n + j];
2196 let av = if am { zero } else { a_data[i * k1 + k] };
2200 let bv = if bm { zero } else { b_data[k * n + j] };
2201 acc = acc + av * bv;
2202 if !am && !bm {
2203 any_valid = true;
2204 }
2205 }
2206 out_data[i * n + j] = acc;
2207 out_mask[i * n + j] = !any_valid;
2208 }
2209 }
2210 let data_arr = Array::<T, Ix2>::from_vec(Ix2::new([m, n]), out_data)?;
2211 let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([m, n]), out_mask)?;
2212 MaskedArray::new(data_arr, mask_arr)
2213 }
2214}
2215
2216impl<T, D> MaskedArray<T, D>
2221where
2222 T: Element + PartialOrd + Copy,
2223 D: Dimension,
2224{
2225 pub fn argsort_axis(&self, axis: usize) -> FerrayResult<Array<u64, IxDyn>> {
2238 let ndim = self.ndim();
2239 if axis >= ndim {
2240 return Err(FerrayError::axis_out_of_bounds(axis, ndim));
2241 }
2242 let shape = self.shape().to_vec();
2243 let axis_len = shape[axis];
2244 let total: usize = shape.iter().product();
2245 let src_data: Vec<T> = self.data().iter().copied().collect();
2246 let src_mask: Vec<bool> = self.mask().iter().copied().collect();
2247
2248 let mut strides = vec![1usize; ndim];
2249 for i in (0..ndim.saturating_sub(1)).rev() {
2250 strides[i] = strides[i + 1] * shape[i + 1];
2251 }
2252
2253 let mut out = vec![0u64; total];
2254
2255 let outer_shape: Vec<usize> = shape
2256 .iter()
2257 .enumerate()
2258 .filter_map(|(i, &s)| if i == axis { None } else { Some(s) })
2259 .collect();
2260 let outer_size: usize = if outer_shape.is_empty() {
2261 1
2262 } else {
2263 outer_shape.iter().product()
2264 };
2265
2266 let mut outer_multi = vec![0usize; outer_shape.len()];
2267 for _ in 0..outer_size {
2268 let flat_of = |k: usize| -> usize {
2271 let mut flat = 0usize;
2272 let mut o = 0usize;
2273 for (i, &stride) in strides.iter().enumerate() {
2274 if i == axis {
2275 flat += stride * k;
2276 } else {
2277 flat += stride * outer_multi[o];
2278 o += 1;
2279 }
2280 }
2281 flat
2282 };
2283
2284 let mut order: Vec<usize> = (0..axis_len).collect();
2288 order.sort_by(|&x, &y| {
2289 let fx = flat_of(x);
2290 let fy = flat_of(y);
2291 match (src_mask[fx], src_mask[fy]) {
2292 (false, false) => src_data[fx]
2293 .partial_cmp(&src_data[fy])
2294 .unwrap_or(std::cmp::Ordering::Equal),
2295 (false, true) => std::cmp::Ordering::Less,
2296 (true, false) => std::cmp::Ordering::Greater,
2297 (true, true) => std::cmp::Ordering::Equal,
2298 }
2299 });
2300
2301 for (k, &pos) in order.iter().enumerate() {
2302 out[flat_of(k)] = pos as u64;
2303 }
2304
2305 for i in (0..outer_shape.len()).rev() {
2306 outer_multi[i] += 1;
2307 if outer_multi[i] < outer_shape[i] {
2308 break;
2309 }
2310 outer_multi[i] = 0;
2311 }
2312 }
2313
2314 Array::<u64, IxDyn>::from_vec(IxDyn::new(&shape), out)
2315 }
2316}
2317
2318#[cfg(test)]
2323mod tests {
2324 use super::*;
2325 use ferray_core::Ix1;
2326
2327 fn arr1f(v: Vec<f64>) -> Array<f64, Ix1> {
2328 let n = v.len();
2329 Array::from_vec(Ix1::new([n]), v).unwrap()
2330 }
2331
2332 fn mask1(v: Vec<bool>) -> Array<bool, Ix1> {
2333 let n = v.len();
2334 Array::from_vec(Ix1::new([n]), v).unwrap()
2335 }
2336
2337 fn ma_f1(d: Vec<f64>, m: Vec<bool>) -> MaskedArray<f64, Ix1> {
2338 MaskedArray::new(arr1f(d), mask1(m)).unwrap()
2339 }
2340
2341 #[test]
2342 fn prod_skips_masked() {
2343 let ma = ma_f1(vec![2.0, 3.0, 5.0, 7.0], vec![false, true, false, false]);
2344 let p = ma.prod().unwrap();
2345 assert!((p - 70.0).abs() < 1e-10); }
2347
2348 #[test]
2349 fn cumsum_propagates_mask() {
2350 let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
2351 let r = ma.cumsum_flat().unwrap();
2352 let mask: Vec<bool> = r.mask().iter().copied().collect();
2353 let data: Vec<f64> = r.data().iter().copied().collect();
2354 assert_eq!(mask, vec![false, true, false, false]);
2355 assert!((data[0] - 1.0).abs() < 1e-10);
2357 assert!((data[2] - 4.0).abs() < 1e-10);
2358 assert!((data[3] - 8.0).abs() < 1e-10);
2359 }
2360
2361 #[test]
2362 fn argmin_argmax_skip_masked() {
2363 let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
2364 assert_eq!(ma.argmin().unwrap(), 3);
2366 assert_eq!(ma.argmax().unwrap(), 2);
2367 }
2368
2369 #[test]
2370 fn ptp_basic() {
2371 let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
2372 assert!((ma.ptp().unwrap() - 6.0).abs() < 1e-10);
2374 }
2375
2376 #[test]
2377 fn median_odd_and_even() {
2378 let odd = ma_f1(vec![3.0, 1.0, 4.0, 1.0, 5.0], vec![false; 5]);
2379 assert!((odd.median().unwrap() - 3.0).abs() < 1e-10);
2380 let even = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
2381 assert!((even.median().unwrap() - 2.5).abs() < 1e-10);
2382 }
2383
2384 #[test]
2385 fn average_unweighted_matches_mean() {
2386 let ma = ma_f1(vec![2.0, 4.0, 6.0], vec![false; 3]);
2387 assert!((ma.average(None).unwrap() - 4.0).abs() < 1e-10);
2388 }
2389
2390 #[test]
2391 fn average_weighted_skips_masked() {
2392 let ma = ma_f1(vec![1.0, 100.0, 3.0], vec![false, true, false]);
2393 let w = arr1f(vec![1.0, 1.0, 3.0]);
2394 assert!((ma.average(Some(&w)).unwrap() - 2.5).abs() < 1e-10);
2396 }
2397
2398 #[test]
2399 fn anom_centers_at_zero() {
2400 let ma = ma_f1(vec![1.0, 2.0, 3.0], vec![false; 3]);
2401 let a = ma.anom().unwrap();
2402 let data: Vec<f64> = a.data().iter().copied().collect();
2403 assert!((data[0] - (-1.0)).abs() < 1e-10);
2404 assert!((data[1] - 0.0).abs() < 1e-10);
2405 assert!((data[2] - 1.0).abs() < 1e-10);
2406 }
2407
2408 #[test]
2409 fn masked_all_is_fully_masked() {
2410 let ma: MaskedArray<f64, Ix1> = masked_all(Ix1::new([5])).unwrap();
2411 let mask: Vec<bool> = ma.mask().iter().copied().collect();
2412 assert_eq!(mask, vec![true; 5]);
2413 }
2414
2415 #[test]
2416 fn masked_values_within_tol() {
2417 let arr = arr1f(vec![1.0, 1.0001, 2.0]);
2418 let r = masked_values(&arr, 1.0, 1e-3, 0.0).unwrap();
2419 let mask: Vec<bool> = r.mask().iter().copied().collect();
2420 assert_eq!(mask, vec![true, true, false]);
2421 }
2422
2423 #[test]
2424 fn harden_mask_blocks_clearing() {
2425 let mut ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
2426 ma.harden_mask().unwrap();
2427 assert!(ma.is_hard_mask());
2428 ma.set_mask_flat(1, false).unwrap();
2430 let mask: Vec<bool> = ma.mask().iter().copied().collect();
2431 assert_eq!(mask, vec![false, true]);
2433 ma.soften_mask().unwrap();
2434 ma.set_mask_flat(1, false).unwrap();
2435 let mask2: Vec<bool> = ma.mask().iter().copied().collect();
2436 assert_eq!(mask2, vec![false, false]);
2437 }
2438
2439 #[test]
2440 fn mask_or_unions() {
2441 let m1 = mask1(vec![true, false, false]);
2442 let m2 = mask1(vec![false, true, false]);
2443 let r = mask_or(&m1, &m2).unwrap();
2444 let v: Vec<bool> = r.iter().copied().collect();
2445 assert_eq!(v, vec![true, true, false]);
2446 }
2447
2448 #[test]
2449 fn make_mask_none_is_all_false() {
2450 let m: Array<bool, Ix1> = make_mask_none(Ix1::new([3])).unwrap();
2451 let v: Vec<bool> = m.iter().copied().collect();
2452 assert_eq!(v, vec![false; 3]);
2453 }
2454
2455 #[test]
2456 fn clip_unmasked_only() {
2457 let ma = ma_f1(vec![-5.0, 0.0, 5.0, 10.0], vec![false, false, false, true]);
2458 let r = ma.clip(-1.0, 3.0).unwrap();
2459 let data: Vec<f64> = r.data().iter().copied().collect();
2460 assert_eq!(data, vec![-1.0, 0.0, 3.0, 10.0]);
2462 }
2463
2464 #[test]
2465 fn repeat_propagates_mask() {
2466 let ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
2467 let r = ma.repeat(3).unwrap();
2468 let data: Vec<f64> = r.data().iter().copied().collect();
2469 let mask: Vec<bool> = r.mask().iter().copied().collect();
2470 assert_eq!(data, vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
2471 assert_eq!(mask, vec![false, false, false, true, true, true]);
2472 }
2473
2474 #[test]
2475 fn ma_unique_dedups_unmasked() {
2476 let ma = ma_f1(
2477 vec![3.0, 1.0, 2.0, 1.0, 3.0, 9.0],
2478 vec![false, false, false, false, false, true],
2479 );
2480 let v = ma_unique(&ma).unwrap();
2481 let data: Vec<f64> = v.iter().copied().collect();
2482 assert_eq!(data, vec![1.0, 2.0, 3.0]);
2483 }
2484
2485 #[test]
2486 fn ma_isin_basic() {
2487 let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
2488 let r = ma_isin(&ma, &[2.0, 4.0]).unwrap();
2489 let data: Vec<bool> = r.data().iter().copied().collect();
2490 assert_eq!(data, vec![false, true, false, true]);
2491 }
2492
2493 #[test]
2494 fn ma_dot_flat_skips_masked_pairs() {
2495 let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, true, false]);
2496 let b = ma_f1(vec![4.0, 5.0, 6.0], vec![false, false, false]);
2497 assert!((a.ma_dot_flat(&b).unwrap() - 22.0).abs() < 1e-10);
2500 }
2501
2502 #[test]
2503 fn fill_value_protocol_constants() {
2504 assert_eq!(default_fill_value_f64(), 1e20);
2505 assert!(!default_fill_value_bool());
2506 assert!(maximum_fill_value::<f64>().is_infinite());
2507 assert!(
2508 minimum_fill_value::<f64>().is_infinite()
2509 && minimum_fill_value::<f64>().is_sign_negative()
2510 );
2511 }
2512
2513 #[test]
2514 fn common_fill_value_returns_shared_or_zero() {
2515 let a = ma_f1(vec![1.0, 2.0], vec![false, false]).with_fill_value(99.0);
2516 let b = ma_f1(vec![3.0, 4.0], vec![false, false]).with_fill_value(99.0);
2517 assert_eq!(common_fill_value(&a, &b), 99.0);
2518 let c = ma_f1(vec![5.0, 6.0], vec![false, false]).with_fill_value(0.5);
2519 assert_eq!(common_fill_value(&a, &c), 0.0); }
2521
2522 #[test]
2523 fn ma_equal_and_friends_union_mask() {
2524 let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, true]);
2525 let b = ma_f1(vec![1.0, 9.0, 3.0], vec![false, true, false]);
2526 let r = ma_equal(&a, &b).unwrap();
2527 let data: Vec<bool> = r.data().iter().copied().collect();
2528 let mask: Vec<bool> = r.mask().iter().copied().collect();
2529 assert_eq!(data, vec![true, false, true]);
2530 assert_eq!(mask, vec![false, true, true]);
2531 }
2532
2533 #[test]
2534 fn ma_logical_and_basic() {
2535 let a = MaskedArray::new(
2536 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, true, false]).unwrap(),
2537 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
2538 )
2539 .unwrap();
2540 let b = MaskedArray::new(
2541 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, false, true]).unwrap(),
2542 Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
2543 )
2544 .unwrap();
2545 let r = ma_logical_and(&a, &b).unwrap();
2546 let data: Vec<bool> = r.data().iter().copied().collect();
2547 assert_eq!(data, vec![true, false, false]);
2548 }
2549
2550 #[test]
2551 fn is_masked_array_always_true() {
2552 let ma = ma_f1(vec![1.0], vec![false]);
2553 assert!(is_masked_array(&ma));
2554 assert!(is_ma(&ma));
2555 }
2556
2557 #[test]
2558 fn getmaskarray_materializes() {
2559 let ma = MaskedArray::<f64, Ix1>::from_data(arr1f(vec![1.0, 2.0])).unwrap();
2560 let m = getmaskarray(&ma).unwrap();
2561 let v: Vec<bool> = m.iter().copied().collect();
2562 assert_eq!(v, vec![false; 2]);
2563 }
2564
2565 #[test]
2566 fn diagonal_main_and_offset() {
2567 use ferray_core::Ix2;
2568 let data = Array::<f64, Ix2>::from_vec(
2569 Ix2::new([3, 3]),
2570 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
2571 )
2572 .unwrap();
2573 let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
2574 let ma = MaskedArray::new(data, mask).unwrap();
2575 let main = ma.diagonal(0).unwrap();
2576 let main_data: Vec<f64> = main.data().iter().copied().collect();
2577 assert_eq!(main_data, vec![1.0, 5.0, 9.0]);
2578 let upper = ma.diagonal(1).unwrap();
2579 let upper_data: Vec<f64> = upper.data().iter().copied().collect();
2580 assert_eq!(upper_data, vec![2.0, 6.0]);
2581 }
2582
2583 #[test]
2584 fn trace_sums_diagonal() {
2585 use ferray_core::Ix2;
2586 let data = Array::<f64, Ix2>::from_vec(
2587 Ix2::new([3, 3]),
2588 vec![1.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 9.0],
2589 )
2590 .unwrap();
2591 let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
2592 let ma = MaskedArray::new(data, mask).unwrap();
2593 assert!((ma.trace(0).unwrap() - 15.0).abs() < 1e-10);
2594 }
2595
2596 #[test]
2597 fn ma_apply_along_axis_sum_lane() {
2598 use ferray_core::IxDyn;
2599 let data =
2600 Array::<f64, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
2601 .unwrap();
2602 let mask = Array::<bool, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![false; 6]).unwrap();
2603 let ma = MaskedArray::new(data, mask).unwrap();
2604 let result = ma_apply_along_axis(&ma, 1, |lane| {
2605 let s: f64 = lane.data().iter().copied().sum();
2606 Ok((s, false))
2607 })
2608 .unwrap();
2609 let v: Vec<f64> = result.data().iter().copied().collect();
2610 assert_eq!(v, vec![6.0, 15.0]);
2611 }
2612
2613 fn ab() -> (MaskedArray<f64, Ix1>, MaskedArray<f64, Ix1>) {
2624 (
2625 ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]),
2626 ma_f1(vec![3.0, 4.0, 5.0], vec![false, false, true]),
2627 )
2628 }
2629
2630 fn data_mask(m: &MaskedArray<f64, Ix1>) -> (Vec<f64>, Vec<bool>) {
2631 (
2632 m.data().iter().copied().collect(),
2633 m.mask().iter().copied().collect(),
2634 )
2635 }
2636
2637 #[test]
2638 fn ma_unique_masked_trails_one_masked_slot() -> FerrayResult<()> {
2639 let (a, _) = ab();
2640 let (data, mask) = data_mask(&ma_unique_masked(&a)?);
2641 assert_eq!(&data[..3], &[1.0, 3.0, 4.0]);
2643 assert_eq!(mask, vec![false, false, false, true]);
2644 Ok(())
2645 }
2646
2647 #[test]
2648 fn ma_intersect1d_common_with_both_masked() -> FerrayResult<()> {
2649 let (a, b) = ab();
2650 let (data, mask) = data_mask(&ma_intersect1d(&a, &b)?);
2651 assert_eq!(&data[..2], &[3.0, 4.0]);
2652 assert_eq!(mask, vec![false, false, true]);
2653 Ok(())
2654 }
2655
2656 #[test]
2657 fn ma_union1d_all_uniques_plus_masked() -> FerrayResult<()> {
2658 let (a, b) = ab();
2659 let (data, mask) = data_mask(&ma_union1d(&a, &b)?);
2660 assert_eq!(&data[..3], &[1.0, 3.0, 4.0]);
2661 assert_eq!(mask, vec![false, false, false, true]);
2662 Ok(())
2663 }
2664
2665 #[test]
2666 fn ma_setdiff1d_drops_masked_when_rhs_masked() -> FerrayResult<()> {
2667 let (a, b) = ab();
2668 let (data, mask) = data_mask(&ma_setdiff1d(&a, &b)?);
2669 assert_eq!(data, vec![1.0]);
2671 assert_eq!(mask, vec![false]);
2672 Ok(())
2673 }
2674
2675 #[test]
2676 fn ma_setxor1d_symmetric_difference() -> FerrayResult<()> {
2677 let (a, b) = ab();
2678 let (data, mask) = data_mask(&ma_setxor1d(&a, &b)?);
2679 assert_eq!(data, vec![1.0]);
2681 assert_eq!(mask, vec![false]);
2682 Ok(())
2683 }
2684
2685 fn m23() -> FerrayResult<MaskedArray<f64, Ix2>> {
2694 let data =
2695 Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])?;
2696 let mask = Array::<bool, Ix2>::from_vec(
2697 Ix2::new([2, 3]),
2698 vec![false, false, true, false, false, false],
2699 )?;
2700 MaskedArray::new(data, mask)
2701 }
2702
2703 #[test]
2704 fn ma_compress_rowcols_both() -> FerrayResult<()> {
2705 let r = ma_compress_rowcols(&m23()?, None)?;
2706 assert_eq!(r.shape(), &[1, 2]);
2707 let v: Vec<f64> = r.iter().copied().collect();
2708 assert_eq!(v, vec![4.0, 5.0]);
2709 Ok(())
2710 }
2711
2712 #[test]
2713 fn ma_compress_rows_and_cols() -> FerrayResult<()> {
2714 let rows = ma_compress_rows(&m23()?)?;
2715 assert_eq!(rows.shape(), &[1, 3]);
2716 assert_eq!(
2717 rows.iter().copied().collect::<Vec<f64>>(),
2718 vec![4.0, 5.0, 6.0]
2719 );
2720 let cols = ma_compress_cols(&m23()?)?;
2721 assert_eq!(cols.shape(), &[2, 2]);
2722 assert_eq!(
2723 cols.iter().copied().collect::<Vec<f64>>(),
2724 vec![1.0, 2.0, 4.0, 5.0]
2725 );
2726 Ok(())
2727 }
2728
2729 #[test]
2730 fn ma_mask_rowcols_masks_whole_row_and_col() -> FerrayResult<()> {
2731 let r = ma_mask_rowcols(&m23()?, None)?;
2732 let mask: Vec<bool> = r.mask().iter().copied().collect();
2733 assert_eq!(mask, vec![true, true, true, false, false, true]);
2735 let data: Vec<f64> = r.data().iter().copied().collect();
2736 assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
2737 Ok(())
2738 }
2739
2740 #[test]
2747 fn ma_cov_unmasked_pairs() -> FerrayResult<()> {
2748 let c = ma_cov(&m23()?, true, false, None)?;
2749 let data: Vec<f64> = c.data().iter().copied().collect();
2750 let mask: Vec<bool> = c.mask().iter().copied().collect();
2751 let expect = [0.5, 0.5, 0.5, 1.0];
2752 for (g, e) in data.iter().zip(expect.iter()) {
2753 assert!((g - e).abs() < 1e-12, "cov {g} != {e}");
2754 }
2755 assert_eq!(mask, vec![false; 4]);
2756 Ok(())
2757 }
2758
2759 #[test]
2760 fn ma_corrcoef_normalized() -> FerrayResult<()> {
2761 let c = ma_corrcoef(&m23()?, true)?;
2762 let data: Vec<f64> = c.data().iter().copied().collect();
2763 let expect = [1.0, 0.7071067811865475, 0.7071067811865475, 1.0];
2764 for (g, e) in data.iter().zip(expect.iter()) {
2765 assert!((g - e).abs() < 1e-12, "corr {g} != {e}");
2766 }
2767 Ok(())
2768 }
2769
2770 #[test]
2781 fn clump_run_length_matches_numpy() {
2782 let m = ma_f1(
2783 vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
2784 vec![false, false, true, true, false, false],
2785 );
2786 assert_eq!(clump_masked(&m), vec![(2, 4)]);
2787 assert_eq!(clump_unmasked(&m), vec![(0, 2), (4, 6)]);
2788 assert_eq!(flatnotmasked_contiguous(&m), vec![(0, 2), (4, 6)]);
2789 assert_eq!(flatnotmasked_edges(&m), Some([0, 5]));
2790 assert_eq!(notmasked_edges(&m), Some([0, 5]));
2791 }
2792
2793 #[test]
2794 fn clump_nomask_and_allmask_edges() {
2795 let none = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, false]);
2798 assert_eq!(clump_masked(&none), vec![]);
2799 assert_eq!(clump_unmasked(&none), vec![(0, 3)]);
2800 assert_eq!(flatnotmasked_edges(&none), Some([0, 2]));
2801 let all = ma_f1(vec![1.0, 2.0, 3.0], vec![true, true, true]);
2804 assert_eq!(clump_unmasked(&all), vec![]);
2805 assert_eq!(clump_masked(&all), vec![(0, 3)]);
2806 assert_eq!(flatnotmasked_contiguous(&all), vec![]);
2807 assert_eq!(flatnotmasked_edges(&all), None);
2808 }
2809
2810 #[test]
2811 fn clump_leading_and_trailing_masked() {
2812 let mut mask = vec![false; 10];
2816 for &i in &[0usize, 1, 2, 6, 8, 9] {
2817 mask[i] = true;
2818 }
2819 let data: Vec<f64> = (0..10).map(|x| x as f64).collect();
2820 let m = ma_f1(data, mask);
2821 assert_eq!(clump_masked(&m), vec![(0, 3), (6, 7), (8, 10)]);
2822 assert_eq!(clump_unmasked(&m), vec![(3, 6), (7, 8)]);
2823 }
2824
2825 #[test]
2826 fn notmasked_contiguous_axis_2d_matches_numpy() -> FerrayResult<()> {
2827 use ferray_core::Ix2;
2828 let mask = vec![
2837 false, true, false, false, true, true, true, false, false, true, true, false,
2838 ];
2839 let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
2840 let d = Array::<f64, Ix2>::from_vec(Ix2::new([3, 4]), data)?;
2841 let mk = Array::<bool, Ix2>::from_vec(Ix2::new([3, 4]), mask)?;
2842 let m = MaskedArray::new(d, mk)?;
2843 let by0 = notmasked_contiguous_axis(&m, 0)?;
2844 assert_eq!(
2845 by0,
2846 vec![vec![(0, 1), (2, 3)], vec![], vec![(0, 1)], vec![(0, 3)]]
2847 );
2848 let by1 = notmasked_contiguous_axis(&m, 1)?;
2849 assert_eq!(
2850 by1,
2851 vec![vec![(0, 1), (2, 4)], vec![(3, 4)], vec![(0, 1), (3, 4)]]
2852 );
2853 Ok(())
2854 }
2855
2856 #[test]
2863 fn ma_dot_2d_matches_numpy() -> FerrayResult<()> {
2864 use ferray_core::Ix2;
2865 let d = Array::<f64, Ix2>::from_vec(Ix2::new([2, 2]), vec![1.0, 2.0, 3.0, 4.0])?;
2866 let mk = Array::<bool, Ix2>::from_vec(Ix2::new([2, 2]), vec![false, true, false, false])?;
2867 let a = MaskedArray::new(d, mk)?;
2868 let out = a.ma_dot_2d(&a)?;
2869 let data: Vec<f64> = out.data().iter().copied().collect();
2870 let mask: Vec<bool> = out.mask().iter().copied().collect();
2871 assert_eq!(data, vec![1.0, 0.0, 15.0, 16.0]);
2874 assert_eq!(mask, vec![false, true, false, false]);
2875 Ok(())
2876 }
2877
2878 #[test]
2885 fn argsort_axis_matches_numpy() -> FerrayResult<()> {
2886 use ferray_core::Ix2;
2887 let d = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![3.0, 1.0, 2.0, 6.0, 5.0, 4.0])?;
2888 let mk = Array::<bool, Ix2>::from_vec(
2889 Ix2::new([2, 3]),
2890 vec![false, false, true, false, false, false],
2891 )?;
2892 let b = MaskedArray::new(d, mk)?;
2893 let by1 = b.argsort_axis(1)?;
2894 let v1: Vec<u64> = by1.iter().copied().collect();
2895 assert_eq!(v1, vec![1, 0, 2, 2, 1, 0]);
2896 let by0 = b.argsort_axis(0)?;
2897 let v0: Vec<u64> = by0.iter().copied().collect();
2898 assert_eq!(v0, vec![0, 0, 1, 1, 1, 0]);
2899 Ok(())
2900 }
2901}