use ferray_core::dimension::{Dimension, IxDyn};
use ferray_core::dtype::Element;
use ferray_core::error::{FerrayError, FerrayResult};
use ferray_core::{Array, Ix1, Ix2};
use num_traits::Float;
use crate::MaskedArray;
pub const NOMASK: bool = false;
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
D: Dimension,
{
pub fn prod(&self) -> FerrayResult<T> {
let one = num_traits::one::<T>();
let p = self
.data()
.iter()
.zip(self.mask().iter())
.filter(|(_, m)| !**m)
.fold(one, |acc, (v, _)| acc * *v);
Ok(p)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + std::ops::Add<Output = T> + num_traits::Zero,
D: Dimension,
{
pub fn cumsum_flat(&self) -> FerrayResult<MaskedArray<T, Ix1>> {
let zero = num_traits::zero::<T>();
let n = self.size();
let data: Vec<T> = self
.data()
.iter()
.zip(self.mask().iter())
.scan(zero, |acc, (v, m)| {
if !*m {
*acc = *acc + *v;
}
Some(*acc)
})
.collect();
let mask: Vec<bool> = self.mask().iter().copied().collect();
let data_arr = Array::from_vec(Ix1::new([n]), data)?;
let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One,
D: Dimension,
{
pub fn cumprod_flat(&self) -> FerrayResult<MaskedArray<T, Ix1>> {
let one = num_traits::one::<T>();
let n = self.size();
let data: Vec<T> = self
.data()
.iter()
.zip(self.mask().iter())
.scan(one, |acc, (v, m)| {
if !*m {
*acc = *acc * *v;
}
Some(*acc)
})
.collect();
let mask: Vec<bool> = self.mask().iter().copied().collect();
let data_arr = Array::from_vec(Ix1::new([n]), data)?;
let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
pub fn argmin(&self) -> FerrayResult<usize> {
self.data()
.iter()
.zip(self.mask().iter())
.enumerate()
.filter(|(_, (_, m))| !**m)
.min_by(|(_, (a, _)), (_, (b, _))| {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
.ok_or_else(|| FerrayError::invalid_value("argmin: all elements are masked"))
}
pub fn argmax(&self) -> FerrayResult<usize> {
self.data()
.iter()
.zip(self.mask().iter())
.enumerate()
.filter(|(_, (_, m))| !**m)
.max_by(|(_, (a, _)), (_, (b, _))| {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
.ok_or_else(|| FerrayError::invalid_value("argmax: all elements are masked"))
}
pub fn ptp(&self) -> FerrayResult<T>
where
T: std::ops::Sub<Output = T>,
{
let mut iter = self
.data()
.iter()
.zip(self.mask().iter())
.filter(|(_, m)| !**m)
.map(|(v, _)| *v);
let first = iter
.next()
.ok_or_else(|| FerrayError::invalid_value("ptp: all elements are masked"))?;
let mut lo = first;
let mut hi = first;
for v in iter {
if v < lo {
lo = v;
}
if v > hi {
hi = v;
}
}
Ok(hi - lo)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element
+ Copy
+ PartialOrd
+ num_traits::FromPrimitive
+ std::ops::Add<Output = T>
+ std::ops::Div<Output = T>,
D: Dimension,
{
pub fn median(&self) -> FerrayResult<T> {
let mut vals: Vec<T> = self
.data()
.iter()
.zip(self.mask().iter())
.filter(|(_, m)| !**m)
.map(|(v, _)| *v)
.collect();
if vals.is_empty() {
return Err(FerrayError::invalid_value(
"median: all elements are masked",
));
}
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = vals.len();
if n % 2 == 1 {
Ok(vals[n / 2])
} else {
let two = T::from_u8(2).unwrap();
Ok((vals[n / 2 - 1] + vals[n / 2]) / two)
}
}
}
pub fn ma_median_axis<T>(
a: &MaskedArray<T, IxDyn>,
axis: usize,
) -> FerrayResult<MaskedArray<T, IxDyn>>
where
T: Element
+ Copy
+ PartialOrd
+ num_traits::Zero
+ num_traits::FromPrimitive
+ std::ops::Add<Output = T>
+ std::ops::Div<Output = T>,
{
let shape = a.shape().to_vec();
if axis >= shape.len() {
return Err(FerrayError::axis_out_of_bounds(axis, shape.len()));
}
let lane_len = shape[axis];
let out_shape: Vec<usize> = shape
.iter()
.enumerate()
.filter(|&(i, _)| i != axis)
.map(|(_, &s)| s)
.collect();
let out_size: usize = out_shape.iter().product::<usize>().max(1);
let ndim = shape.len();
let mut strides = vec![1usize; ndim];
for i in (0..ndim.saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let data: Vec<T> = a.data().iter().copied().collect();
let mask: Vec<bool> = a.mask().iter().copied().collect();
let mut out_data = Vec::with_capacity(out_size);
let mut out_mask = Vec::with_capacity(out_size);
let out_axes: Vec<usize> = (0..ndim).filter(|&i| i != axis).collect();
let mut counter = vec![0usize; out_axes.len()];
let two = T::from_u8(2).ok_or_else(|| {
FerrayError::invalid_value("median: constant 2 not representable in element type")
})?;
for _ in 0..out_size {
let mut base = 0usize;
for (slot, &ax) in out_axes.iter().enumerate() {
base += counter[slot] * strides[ax];
}
let mut vals: Vec<T> = Vec::with_capacity(lane_len);
for k in 0..lane_len {
let off = base + k * strides[axis];
if !mask[off] {
vals.push(data[off]);
}
}
if vals.is_empty() {
out_data.push(<T as num_traits::Zero>::zero());
out_mask.push(true);
} else {
vals.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
let n = vals.len();
let med = if n % 2 == 1 {
vals[n / 2]
} else {
(vals[n / 2 - 1] + vals[n / 2]) / two
};
out_data.push(med);
out_mask.push(false);
}
for slot in (0..out_axes.len()).rev() {
counter[slot] += 1;
if counter[slot] < out_shape[slot] {
break;
}
counter[slot] = 0;
}
}
let dim = IxDyn::new(&out_shape);
let data_arr = Array::<T, IxDyn>::from_vec(dim.clone(), out_data)?;
let mask_arr = Array::<bool, IxDyn>::from_vec(dim, out_mask)?;
MaskedArray::new(data_arr, mask_arr)
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + Float,
D: Dimension,
{
pub fn average(&self, weights: Option<&Array<T, D>>) -> FerrayResult<T> {
Ok(self.average_returned(weights)?.0)
}
pub fn average_returned(&self, weights: Option<&Array<T, D>>) -> FerrayResult<(T, T)> {
let zero = <T as num_traits::Zero>::zero();
let Some(w) = weights else {
let avg = self.mean()?;
let count = self.mask().iter().filter(|m| !**m).count();
let scl = <T as num_traits::NumCast>::from(count).ok_or_else(|| {
FerrayError::invalid_value("average: unmasked count not representable")
})?;
return Ok((avg, scl));
};
if w.shape() != self.shape() {
return Err(FerrayError::shape_mismatch(format!(
"average: weights shape {:?} differs from array shape {:?}",
w.shape(),
self.shape(),
)));
}
let mut wsum = zero;
let mut acc = zero;
for ((v, m), wi) in self.data().iter().zip(self.mask().iter()).zip(w.iter()) {
if !*m {
wsum = wsum + *wi;
acc = acc + *v * *wi;
}
}
if wsum == zero {
return Err(FerrayError::invalid_value(
"average: weight sum is zero (or all elements are masked)",
));
}
Ok((acc / wsum, wsum))
}
pub fn anom(&self) -> FerrayResult<MaskedArray<T, D>> {
let m = self.mean()?;
let data: Vec<T> = self.data().iter().map(|v| *v - m).collect();
let data_arr = Array::from_vec(self.data().dim().clone(), data)?;
let mask_arr: Array<bool, D> = Array::from_vec(
self.mask().dim().clone(),
self.mask().iter().copied().collect(),
)?;
MaskedArray::new(data_arr, mask_arr)
}
}
pub fn masked_all<T, D>(shape: D) -> FerrayResult<MaskedArray<T, D>>
where
T: Element + Copy,
D: Dimension,
{
let data = Array::<T, D>::from_elem(shape.clone(), T::zero())?;
let mask = Array::<bool, D>::from_elem(shape, true)?;
MaskedArray::new(data, mask)
}
pub fn masked_all_like<T, U, D>(reference: &Array<U, D>) -> FerrayResult<MaskedArray<T, D>>
where
T: Element + Copy,
U: Element,
D: Dimension,
{
masked_all(reference.dim().clone())
}
pub fn masked_values<T, D>(
arr: &Array<T, D>,
value: T,
rtol: T,
atol: T,
) -> FerrayResult<MaskedArray<T, D>>
where
T: Element + Copy + Float,
D: Dimension,
{
let threshold = atol + rtol * value.abs();
let mask: Vec<bool> = arr
.iter()
.map(|x| (*x - value).abs() <= threshold)
.collect();
let mask_arr = Array::from_vec(arr.dim().clone(), mask)?;
let data_arr = arr.clone();
MaskedArray::new(data_arr, mask_arr)
}
pub fn mask_or<D: Dimension>(
a: &Array<bool, D>,
b: &Array<bool, D>,
) -> FerrayResult<Array<bool, D>> {
if a.shape() != b.shape() {
return Err(FerrayError::shape_mismatch(format!(
"mask_or: shapes {:?} and {:?} differ",
a.shape(),
b.shape()
)));
}
let data: Vec<bool> = a.iter().zip(b.iter()).map(|(x, y)| *x || *y).collect();
Array::from_vec(a.dim().clone(), data)
}
pub fn make_mask<D: Dimension>(values: &[bool], shape: D) -> FerrayResult<Array<bool, D>> {
Array::from_vec(shape, values.to_vec())
}
pub fn make_mask_none<D: Dimension>(shape: D) -> FerrayResult<Array<bool, D>> {
Array::from_elem(shape, false)
}
pub fn ma_concatenate<T>(
a: &MaskedArray<T, IxDyn>,
b: &MaskedArray<T, IxDyn>,
axis: usize,
) -> FerrayResult<MaskedArray<T, IxDyn>>
where
T: Element + Copy,
{
let cat_data =
ferray_core::manipulation::concatenate(&[a.data().clone(), b.data().clone()], axis)?;
let cat_mask =
ferray_core::manipulation::concatenate(&[a.mask().clone(), b.mask().clone()], axis)?;
MaskedArray::new(cat_data, cat_mask)
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
pub fn clip(&self, a_min: T, a_max: T) -> FerrayResult<MaskedArray<T, D>> {
let data: Vec<T> = self
.data()
.iter()
.zip(self.mask().iter())
.map(|(v, m)| {
if *m {
*v
} else if *v < a_min {
a_min
} else if *v > a_max {
a_max
} else {
*v
}
})
.collect();
let data_arr = Array::from_vec(self.data().dim().clone(), data)?;
let mask_arr: Array<bool, D> = Array::from_vec(
self.mask().dim().clone(),
self.mask().iter().copied().collect(),
)?;
MaskedArray::new(data_arr, mask_arr)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy,
D: Dimension,
{
pub fn repeat(&self, repeats: usize) -> FerrayResult<MaskedArray<T, Ix1>> {
let n = self.size() * repeats;
let mut data = Vec::with_capacity(n);
let mut mask = Vec::with_capacity(n);
for (v, m) in self.data().iter().zip(self.mask().iter()) {
for _ in 0..repeats {
data.push(*v);
mask.push(*m);
}
}
let data_arr = Array::from_vec(Ix1::new([n]), data)?;
let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn atleast_1d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
let shape = self.shape();
let new_shape: Vec<usize> = if shape.is_empty() {
vec![1]
} else {
shape.to_vec()
};
let data: Vec<T> = self.data().iter().copied().collect();
let mask: Vec<bool> = self.mask().iter().copied().collect();
let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn atleast_2d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
let shape = self.shape();
let new_shape: Vec<usize> = match shape.len() {
0 => vec![1, 1],
1 => vec![1, shape[0]],
_ => shape.to_vec(),
};
let data: Vec<T> = self.data().iter().copied().collect();
let mask: Vec<bool> = self.mask().iter().copied().collect();
let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn atleast_3d(&self) -> FerrayResult<MaskedArray<T, IxDyn>> {
let shape = self.shape();
let new_shape: Vec<usize> = match shape.len() {
0 => vec![1, 1, 1],
1 => vec![1, shape[0], 1],
2 => vec![shape[0], shape[1], 1],
_ => shape.to_vec(),
};
let data: Vec<T> = self.data().iter().copied().collect();
let mask: Vec<bool> = self.mask().iter().copied().collect();
let data_arr = Array::from_vec(IxDyn::new(&new_shape), data)?;
let mask_arr = Array::from_vec(IxDyn::new(&new_shape), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn expand_dims(&self, axis: usize) -> FerrayResult<MaskedArray<T, IxDyn>> {
let data_exp = ferray_core::manipulation::expand_dims(self.data(), axis)?;
let mask_exp = ferray_core::manipulation::expand_dims(self.mask(), axis)?;
MaskedArray::new(data_exp, mask_exp)
}
}
impl<T> MaskedArray<T, Ix2>
where
T: Element + Copy + num_traits::Zero,
{
pub fn diagonal(&self, k: isize) -> FerrayResult<MaskedArray<T, Ix1>> {
let shape = self.shape();
let (rows, cols) = (shape[0], shape[1]);
let (start_r, start_c) = if k >= 0 {
(0usize, k as usize)
} else {
(-k as usize, 0usize)
};
let mut data = Vec::new();
let mut mask = Vec::new();
let data_slice = self.data().as_slice();
let mask_slice = self.mask().as_slice();
let mut r = start_r;
let mut c = start_c;
while r < rows && c < cols {
let flat = r * cols + c;
if let (Some(ds), Some(ms)) = (data_slice, mask_slice) {
data.push(ds[flat]);
mask.push(ms[flat]);
} else {
data.push(*self.data().iter().nth(flat).expect("flat index in bounds"));
mask.push(*self.mask().iter().nth(flat).expect("flat index in bounds"));
}
r += 1;
c += 1;
}
let n = data.len();
let data_arr = Array::from_vec(Ix1::new([n]), data)?;
let mask_arr = Array::from_vec(Ix1::new([n]), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
D: Dimension,
{
pub fn ma_dot_flat(&self, other: &MaskedArray<T, D>) -> FerrayResult<T> {
if self.size() != other.size() {
return Err(FerrayError::shape_mismatch(format!(
"ma_dot_flat: lhs.size()={} != rhs.size()={}",
self.size(),
other.size(),
)));
}
let zero = <T as num_traits::Zero>::zero();
let s = self
.data()
.iter()
.zip(self.mask().iter())
.zip(other.data().iter().zip(other.mask().iter()))
.fold(
zero,
|acc, ((a, ma), (b, mb))| {
if *ma || *mb { acc } else { acc + *a * *b }
},
);
Ok(s)
}
}
impl<T> MaskedArray<T, Ix2>
where
T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
{
pub fn trace(&self, k: isize) -> FerrayResult<T> {
let diag = self.diagonal(k)?;
let zero = <T as num_traits::Zero>::zero();
let s = diag
.data()
.iter()
.zip(diag.mask().iter())
.filter(|(_, m)| !**m)
.fold(zero, |acc, (v, _)| acc + *v);
Ok(s)
}
}
pub fn ma_unique<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<T, Ix1>>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let mut vals: Vec<T> = ma
.data()
.iter()
.zip(ma.mask().iter())
.filter(|(_, m)| !**m)
.map(|(v, _)| *v)
.collect();
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
let n = vals.len();
Array::from_vec(Ix1::new([n]), vals)
}
pub fn ma_isin<T, D>(
ma: &MaskedArray<T, D>,
test_values: &[T],
) -> FerrayResult<MaskedArray<bool, D>>
where
T: Element + Copy + PartialEq,
D: Dimension,
{
let mask: Vec<bool> = ma.mask().iter().copied().collect();
let data: Vec<bool> = ma
.data()
.iter()
.zip(mask.iter())
.map(|(v, &m)| {
!m && test_values.iter().any(|t| t == v)
})
.collect();
let data_arr = Array::from_vec(ma.data().dim().clone(), data)?;
let mask_arr: Array<bool, D> = Array::from_vec(ma.mask().dim().clone(), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn ma_in1d<T>(
ma: &MaskedArray<T, Ix1>,
test_values: &[T],
) -> FerrayResult<MaskedArray<bool, Ix1>>
where
T: Element + Copy + PartialEq,
{
ma_isin(ma, test_values)
}
pub fn ma_unique_masked<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, Ix1>>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let mut vals: Vec<T> = ma
.data()
.iter()
.zip(ma.mask().iter())
.filter(|(_, m)| !**m)
.map(|(v, _)| *v)
.collect();
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
let mut mask = vec![false; vals.len()];
if let Some(pos) = ma.mask().iter().position(|&m| m)
&& let Some(placeholder) = ma.data().iter().nth(pos)
{
vals.push(*placeholder);
mask.push(true);
}
let n = vals.len();
let data_arr = Array::<T, Ix1>::from_vec(Ix1::new([n]), vals)?;
let mask_arr = Array::<bool, Ix1>::from_vec(Ix1::new([n]), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
fn build_ma_ix1<T>(pairs: Vec<(T, bool)>) -> FerrayResult<MaskedArray<T, Ix1>>
where
T: Element + Copy,
{
let n = pairs.len();
let data: Vec<T> = pairs.iter().map(|(v, _)| *v).collect();
let mask: Vec<bool> = pairs.iter().map(|(_, m)| *m).collect();
let data_arr = Array::<T, Ix1>::from_vec(Ix1::new([n]), data)?;
let mask_arr = Array::<bool, Ix1>::from_vec(Ix1::new([n]), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
fn unique_parts<T, D>(ma: &MaskedArray<T, D>) -> (Vec<T>, bool)
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let mut vals: Vec<T> = ma
.data()
.iter()
.zip(ma.mask().iter())
.filter(|(_, m)| !**m)
.map(|(v, _)| *v)
.collect();
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
let any_masked = ma.mask().iter().any(|&m| m);
(vals, any_masked)
}
pub fn ma_intersect1d<T, D>(
ar1: &MaskedArray<T, D>,
ar2: &MaskedArray<T, D>,
) -> FerrayResult<MaskedArray<T, Ix1>>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let (v1, m1) = unique_parts(ar1);
let (v2, m2) = unique_parts(ar2);
let mut out: Vec<(T, bool)> = Vec::new();
let (mut i, mut j) = (0usize, 0usize);
while i < v1.len() && j < v2.len() {
match v1[i].partial_cmp(&v2[j]) {
Some(std::cmp::Ordering::Equal) => {
out.push((v1[i], false));
i += 1;
j += 1;
}
Some(std::cmp::Ordering::Less) => i += 1,
Some(std::cmp::Ordering::Greater) => j += 1,
None => {
i += 1;
}
}
}
if m1 && m2 {
let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
if let Some(p) = placeholder {
out.push((p, true));
}
}
build_ma_ix1(out)
}
pub fn ma_union1d<T, D>(
ar1: &MaskedArray<T, D>,
ar2: &MaskedArray<T, D>,
) -> FerrayResult<MaskedArray<T, Ix1>>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let (v1, m1) = unique_parts(ar1);
let (v2, m2) = unique_parts(ar2);
let mut vals: Vec<T> = v1;
vals.extend(v2);
vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
vals.dedup_by(|a, b| (*a).partial_cmp(&*b) == Some(std::cmp::Ordering::Equal));
let mut out: Vec<(T, bool)> = vals.into_iter().map(|v| (v, false)).collect();
if m1 || m2 {
let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
if let Some(p) = placeholder {
out.push((p, true));
}
}
build_ma_ix1(out)
}
pub fn ma_setdiff1d<T, D>(
ar1: &MaskedArray<T, D>,
ar2: &MaskedArray<T, D>,
) -> FerrayResult<MaskedArray<T, Ix1>>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let (v1, m1) = unique_parts(ar1);
let (v2, _m2) = unique_parts(ar2);
let m2_masked = ar2.mask().iter().any(|&m| m);
let mut out: Vec<(T, bool)> = Vec::new();
for v in v1 {
let present = v2
.iter()
.any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal));
if !present {
out.push((v, false));
}
}
if m1
&& !m2_masked
&& let Some(p) = first_masked_value(ar1)
{
out.push((p, true));
}
build_ma_ix1(out)
}
pub fn ma_setxor1d<T, D>(
ar1: &MaskedArray<T, D>,
ar2: &MaskedArray<T, D>,
) -> FerrayResult<MaskedArray<T, Ix1>>
where
T: Element + Copy + PartialOrd,
D: Dimension,
{
let (v1, m1) = unique_parts(ar1);
let (v2, m2) = unique_parts(ar2);
let mut out: Vec<(T, bool)> = Vec::new();
for v in &v1 {
if !v2
.iter()
.any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal))
{
out.push((*v, false));
}
}
for v in &v2 {
if !v1
.iter()
.any(|w| v.partial_cmp(w) == Some(std::cmp::Ordering::Equal))
{
out.push((*v, false));
}
}
out.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
if m1 ^ m2 {
let placeholder = first_masked_value(ar1).or_else(|| first_masked_value(ar2));
if let Some(p) = placeholder {
out.push((p, true));
}
}
build_ma_ix1(out)
}
fn first_masked_value<T, D>(ma: &MaskedArray<T, D>) -> Option<T>
where
T: Element + Copy,
D: Dimension,
{
ma.mask()
.iter()
.position(|&m| m)
.and_then(|pos| ma.data().iter().nth(pos).copied())
}
pub fn ma_compress_rowcols<T>(
ma: &MaskedArray<T, Ix2>,
axis: Option<usize>,
) -> FerrayResult<Array<T, Ix2>>
where
T: Element + Copy,
{
let shape = ma.data().shape();
let (nrows, ncols) = (shape[0], shape[1]);
let data: Vec<T> = ma.data().iter().copied().collect();
let mask: Vec<bool> = ma.mask().iter().copied().collect();
let drop_rows = matches!(axis, None | Some(0));
let drop_cols = matches!(axis, None | Some(1));
let mut keep_row = vec![true; nrows];
let mut keep_col = vec![true; ncols];
if drop_rows {
for (r, kr) in keep_row.iter_mut().enumerate() {
*kr = !(0..ncols).any(|c| mask[r * ncols + c]);
}
}
if drop_cols {
for (c, kc) in keep_col.iter_mut().enumerate() {
*kc = !(0..nrows).any(|r| mask[r * ncols + c]);
}
}
let kept_rows: Vec<usize> = (0..nrows).filter(|&r| keep_row[r]).collect();
let kept_cols: Vec<usize> = (0..ncols).filter(|&c| keep_col[c]).collect();
let mut out: Vec<T> = Vec::with_capacity(kept_rows.len() * kept_cols.len());
for &r in &kept_rows {
for &c in &kept_cols {
out.push(data[r * ncols + c]);
}
}
Array::<T, Ix2>::from_vec(Ix2::new([kept_rows.len(), kept_cols.len()]), out)
}
pub fn ma_compress_rows<T>(ma: &MaskedArray<T, Ix2>) -> FerrayResult<Array<T, Ix2>>
where
T: Element + Copy,
{
ma_compress_rowcols(ma, Some(0))
}
pub fn ma_compress_cols<T>(ma: &MaskedArray<T, Ix2>) -> FerrayResult<Array<T, Ix2>>
where
T: Element + Copy,
{
ma_compress_rowcols(ma, Some(1))
}
pub fn ma_mask_rowcols<T>(
ma: &MaskedArray<T, Ix2>,
axis: Option<usize>,
) -> FerrayResult<MaskedArray<T, Ix2>>
where
T: Element + Copy,
{
let shape = ma.data().shape();
let (nrows, ncols) = (shape[0], shape[1]);
let data: Vec<T> = ma.data().iter().copied().collect();
let mask: Vec<bool> = ma.mask().iter().copied().collect();
let mask_rows = matches!(axis, None | Some(0));
let mask_cols = matches!(axis, None | Some(1));
let row_has_mask: Vec<bool> = (0..nrows)
.map(|r| (0..ncols).any(|c| mask[r * ncols + c]))
.collect();
let col_has_mask: Vec<bool> = (0..ncols)
.map(|c| (0..nrows).any(|r| mask[r * ncols + c]))
.collect();
let mut new_mask = mask.clone();
for r in 0..nrows {
for c in 0..ncols {
if (mask_rows && row_has_mask[r]) || (mask_cols && col_has_mask[c]) {
new_mask[r * ncols + c] = true;
}
}
}
let data_arr = Array::<T, Ix2>::from_vec(Ix2::new([nrows, ncols]), data)?;
let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([nrows, ncols]), new_mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn ma_cov<D>(
x: &MaskedArray<f64, D>,
rowvar: bool,
bias: bool,
ddof: Option<usize>,
) -> FerrayResult<MaskedArray<f64, Ix2>>
where
D: Dimension,
{
let ddof_val = ddof.unwrap_or(if bias { 0 } else { 1 }) as f64;
let ndim = x.ndim();
if ndim > 2 {
return Err(FerrayError::invalid_value(
"ma.cov requires a 1-D or 2-D masked array",
));
}
let shape = x.data().shape();
let flat_data: Vec<f64> = x.data().iter().copied().collect();
let flat_mask: Vec<bool> = x.mask().iter().copied().collect();
let (nvars, nobs, mat_data, mat_mask): (usize, usize, Vec<f64>, Vec<bool>) = if ndim <= 1 {
let n = flat_data.len();
(1, n, flat_data, flat_mask)
} else {
let (r, c) = (shape[0], shape[1]);
let eff_rowvar = rowvar || r == 1;
if eff_rowvar {
(r, c, flat_data, flat_mask)
} else {
let mut td = vec![0.0f64; r * c];
let mut tm = vec![false; r * c];
for i in 0..c {
for k in 0..r {
td[i * r + k] = flat_data[k * c + i];
tm[i * r + k] = flat_mask[k * c + i];
}
}
(c, r, td, tm)
}
};
let mut centered = vec![0.0f64; nvars * nobs];
let mut notmask = vec![0.0f64; nvars * nobs];
for i in 0..nvars {
let mut sum = 0.0;
let mut cnt = 0.0;
for k in 0..nobs {
let idx = i * nobs + k;
if !mat_mask[idx] {
sum += mat_data[idx];
cnt += 1.0;
notmask[idx] = 1.0;
}
}
let mean = if cnt > 0.0 { sum / cnt } else { 0.0 };
for k in 0..nobs {
let idx = i * nobs + k;
centered[idx] = if mat_mask[idx] {
0.0
} else {
mat_data[idx] - mean
};
}
}
let mut cov_data = vec![0.0f64; nvars * nvars];
let mut cov_mask = vec![false; nvars * nvars];
for i in 0..nvars {
for j in i..nvars {
let mut dot = 0.0;
let mut fact = 0.0;
for k in 0..nobs {
dot += centered[i * nobs + k] * centered[j * nobs + k];
fact += notmask[i * nobs + k] * notmask[j * nobs + k];
}
fact -= ddof_val;
let (val, masked) = if fact <= 0.0 {
(0.0, true)
} else {
(dot / fact, false)
};
cov_data[i * nvars + j] = val;
cov_data[j * nvars + i] = val;
cov_mask[i * nvars + j] = masked;
cov_mask[j * nvars + i] = masked;
}
}
let data_arr = Array::<f64, Ix2>::from_vec(Ix2::new([nvars, nvars]), cov_data)?;
let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([nvars, nvars]), cov_mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn ma_corrcoef<D>(x: &MaskedArray<f64, D>, rowvar: bool) -> FerrayResult<MaskedArray<f64, Ix2>>
where
D: Dimension,
{
let cov = ma_cov(x, rowvar, false, None)?;
let n = cov.data().shape()[0];
let cov_data: Vec<f64> = cov.data().iter().copied().collect();
let cov_mask: Vec<bool> = cov.mask().iter().copied().collect();
let mut std = vec![0.0f64; n];
let mut std_masked = vec![false; n];
for i in 0..n {
std_masked[i] = cov_mask[i * n + i];
std[i] = cov_data[i * n + i].sqrt();
}
let mut corr_data = vec![0.0f64; n * n];
let mut corr_mask = vec![false; n * n];
for i in 0..n {
for j in 0..n {
let d = std[i] * std[j];
let masked = cov_mask[i * n + j] || std_masked[i] || std_masked[j] || d == 0.0;
if masked {
corr_mask[i * n + j] = true;
corr_data[i * n + j] = 0.0;
} else {
let v = cov_data[i * n + j] / d;
corr_data[i * n + j] = v.clamp(-1.0, 1.0);
}
}
}
let data_arr = Array::<f64, Ix2>::from_vec(Ix2::new([n, n]), corr_data)?;
let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([n, n]), corr_mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn ma_apply_along_axis<T, F>(
ma: &MaskedArray<T, IxDyn>,
axis: usize,
mut f: F,
) -> FerrayResult<MaskedArray<T, IxDyn>>
where
T: Element + Copy,
F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
{
let shape = ma.shape().to_vec();
if axis >= shape.len() {
return Err(FerrayError::axis_out_of_bounds(axis, shape.len()));
}
let lane_len = shape[axis];
let out_shape: Vec<usize> = shape
.iter()
.enumerate()
.filter(|&(i, _)| i != axis)
.map(|(_, &s)| s)
.collect();
let out_size: usize = out_shape.iter().product::<usize>().max(1);
let ndim = shape.len();
let mut strides = vec![1usize; ndim];
for i in (0..ndim.saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let data: Vec<T> = ma.data().iter().copied().collect();
let mask: Vec<bool> = ma.mask().iter().copied().collect();
let mut out_data = Vec::with_capacity(out_size);
let mut out_mask = Vec::with_capacity(out_size);
let mut out_multi = vec![0usize; out_shape.len()];
for _ in 0..out_size {
let mut lane_data = Vec::with_capacity(lane_len);
let mut lane_mask = Vec::with_capacity(lane_len);
let mut full_idx = vec![0usize; ndim];
let mut oi = 0;
for (d, slot) in full_idx.iter_mut().enumerate() {
if d == axis {
continue;
}
*slot = out_multi[oi];
oi += 1;
}
for j in 0..lane_len {
full_idx[axis] = j;
let flat: usize = full_idx
.iter()
.zip(strides.iter())
.map(|(i, s)| i * s)
.sum();
lane_data.push(data[flat]);
lane_mask.push(mask[flat]);
}
let lane_data_arr = Array::from_vec(Ix1::new([lane_len]), lane_data)?;
let lane_mask_arr = Array::from_vec(Ix1::new([lane_len]), lane_mask)?;
let lane_ma = MaskedArray::new(lane_data_arr, lane_mask_arr)?;
let (val, masked) = f(&lane_ma)?;
out_data.push(val);
out_mask.push(masked);
for d in (0..out_shape.len()).rev() {
out_multi[d] += 1;
if out_multi[d] < out_shape[d] {
break;
}
out_multi[d] = 0;
}
}
let data_arr = Array::from_vec(IxDyn::new(&out_shape), out_data)?;
let mask_arr = Array::from_vec(IxDyn::new(&out_shape), out_mask)?;
MaskedArray::new(data_arr, mask_arr)
}
pub fn ma_apply_over_axes<T, F>(
ma: &MaskedArray<T, IxDyn>,
axes: &[usize],
mut f: F,
) -> FerrayResult<MaskedArray<T, IxDyn>>
where
T: Element + Copy,
F: FnMut(&MaskedArray<T, Ix1>) -> FerrayResult<(T, bool)>,
{
let mut result = ma.clone();
let mut sorted: Vec<usize> = axes.to_vec();
sorted.sort_unstable();
for (offset, &ax) in sorted.iter().enumerate() {
let adjusted = ax.saturating_sub(offset);
result = ma_apply_along_axis(&result, adjusted, &mut f)?;
}
Ok(result)
}
pub fn ma_vander<T>(x: &MaskedArray<T, Ix1>, n: Option<usize>) -> FerrayResult<MaskedArray<T, Ix2>>
where
T: Element + Copy + std::ops::Mul<Output = T> + num_traits::One + num_traits::Zero,
{
let m = x.size();
if m == 0 {
return Err(FerrayError::invalid_value(
"ma_vander: input must not be empty",
));
}
let cols = n.unwrap_or(m);
let xs: Vec<T> = x.data().iter().copied().collect();
let xmask: Vec<bool> = x.mask().iter().copied().collect();
let zero = num_traits::zero::<T>();
let one = num_traits::one::<T>();
let mut data = vec![one; m * cols];
for (i, &xi) in xs.iter().enumerate() {
let mut acc = one;
let mut powers = Vec::with_capacity(cols);
for _ in 0..cols {
powers.push(acc);
acc = acc * xi;
}
for (j, p) in powers.iter().enumerate() {
data[i * cols + (cols - 1 - j)] = *p;
}
if xmask[i] {
for slot in data[i * cols..(i + 1) * cols].iter_mut() {
*slot = zero;
}
}
}
let data_arr = Array::from_vec(Ix2::new([m, cols]), data)?;
MaskedArray::from_data(data_arr)
}
#[must_use]
pub fn default_fill_value_f64() -> f64 {
1e20
}
#[must_use]
pub fn default_fill_value_f32() -> f32 {
1e20_f32
}
#[must_use]
pub const fn default_fill_value_bool() -> bool {
false
}
#[must_use]
pub const fn default_fill_value_i64() -> i64 {
999_999
}
#[must_use]
pub fn maximum_fill_value<T: Float>() -> T {
T::infinity()
}
#[must_use]
pub fn minimum_fill_value<T: Float>() -> T {
T::neg_infinity()
}
pub fn common_fill_value<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> T
where
T: Element + Copy + PartialEq,
D: Dimension,
{
if a.fill_value() == b.fill_value() {
a.fill_value()
} else {
T::zero()
}
}
macro_rules! ma_cmp {
($name:ident, $op:tt, $bound:path) => {
pub fn $name<T, D>(
a: &MaskedArray<T, D>,
b: &MaskedArray<T, D>,
) -> FerrayResult<MaskedArray<bool, D>>
where
T: Element + Copy + $bound,
D: Dimension,
{
if a.shape() != b.shape() {
return Err(FerrayError::shape_mismatch(format!(
"{}: shapes {:?} and {:?} differ",
stringify!($name),
a.shape(),
b.shape(),
)));
}
let data: Vec<bool> = a
.data()
.iter()
.zip(b.data().iter())
.map(|(x, y)| x $op y)
.collect();
let mask: Vec<bool> = a
.mask()
.iter()
.zip(b.mask().iter())
.map(|(x, y)| *x || *y)
.collect();
let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
};
}
ma_cmp!(ma_equal, ==, PartialEq);
ma_cmp!(ma_not_equal, !=, PartialEq);
ma_cmp!(ma_less, <, PartialOrd);
ma_cmp!(ma_greater, >, PartialOrd);
ma_cmp!(ma_less_equal, <=, PartialOrd);
ma_cmp!(ma_greater_equal, >=, PartialOrd);
pub fn ma_logical_and<D: Dimension>(
a: &MaskedArray<bool, D>,
b: &MaskedArray<bool, D>,
) -> FerrayResult<MaskedArray<bool, D>> {
binary_bool(a, b, |x, y| x && y, "ma_logical_and")
}
pub fn ma_logical_or<D: Dimension>(
a: &MaskedArray<bool, D>,
b: &MaskedArray<bool, D>,
) -> FerrayResult<MaskedArray<bool, D>> {
binary_bool(a, b, |x, y| x || y, "ma_logical_or")
}
pub fn ma_logical_xor<D: Dimension>(
a: &MaskedArray<bool, D>,
b: &MaskedArray<bool, D>,
) -> FerrayResult<MaskedArray<bool, D>> {
binary_bool(a, b, |x, y| x ^ y, "ma_logical_xor")
}
pub fn ma_logical_not<D: Dimension>(
a: &MaskedArray<bool, D>,
) -> FerrayResult<MaskedArray<bool, D>> {
let data: Vec<bool> = a.data().iter().map(|x| !*x).collect();
let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
let mask_arr: Array<bool, D> =
Array::from_vec(a.mask().dim().clone(), a.mask().iter().copied().collect())?;
MaskedArray::new(data_arr, mask_arr)
}
fn binary_bool<D: Dimension>(
a: &MaskedArray<bool, D>,
b: &MaskedArray<bool, D>,
op: impl Fn(bool, bool) -> bool,
name: &str,
) -> FerrayResult<MaskedArray<bool, D>> {
if a.shape() != b.shape() {
return Err(FerrayError::shape_mismatch(format!(
"{name}: shapes {:?} and {:?} differ",
a.shape(),
b.shape()
)));
}
let data: Vec<bool> = a
.data()
.iter()
.zip(b.data().iter())
.map(|(x, y)| op(*x, *y))
.collect();
let mask: Vec<bool> = a
.mask()
.iter()
.zip(b.mask().iter())
.map(|(x, y)| *x || *y)
.collect();
let data_arr = Array::from_vec(a.data().dim().clone(), data)?;
let mask_arr: Array<bool, D> = Array::from_vec(a.mask().dim().clone(), mask)?;
MaskedArray::new(data_arr, mask_arr)
}
#[must_use]
pub const fn is_masked_array<T, D>(_ma: &MaskedArray<T, D>) -> bool
where
T: Element,
D: Dimension,
{
true
}
#[must_use]
pub const fn is_ma<T, D>(ma: &MaskedArray<T, D>) -> bool
where
T: Element,
D: Dimension,
{
is_masked_array(ma)
}
pub fn getmaskarray<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<Array<bool, D>>
where
T: Element,
D: Dimension,
{
Array::from_vec(ma.mask().dim().clone(), ma.mask().iter().copied().collect())
}
#[must_use]
pub fn ids<T, D>(ma: &MaskedArray<T, D>) -> (*const u8, *const u8)
where
T: Element,
D: Dimension,
{
let data_ptr: *const u8 = ma.data() as *const _ as *const u8;
let mask_ptr: *const u8 = ma.mask() as *const _ as *const u8;
(data_ptr, mask_ptr)
}
fn ezclump_true(mask: &[bool]) -> Vec<(usize, usize)> {
let n = mask.len();
let mut runs: Vec<(usize, usize)> = Vec::new();
let mut i = 0usize;
while i < n {
if mask[i] {
let start = i;
while i < n && mask[i] {
i += 1;
}
runs.push((start, i));
} else {
i += 1;
}
}
runs
}
fn flat_mask<T, D>(a: &MaskedArray<T, D>) -> Vec<bool>
where
T: Element,
D: Dimension,
{
a.mask().iter().copied().collect()
}
#[must_use]
pub fn clump_masked<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
where
T: Element,
D: Dimension,
{
ezclump_true(&flat_mask(a))
}
#[must_use]
pub fn clump_unmasked<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
where
T: Element,
D: Dimension,
{
let inv: Vec<bool> = flat_mask(a).iter().map(|&m| !m).collect();
ezclump_true(&inv)
}
#[must_use]
pub fn flatnotmasked_edges<T, D>(a: &MaskedArray<T, D>) -> Option<[usize; 2]>
where
T: Element,
D: Dimension,
{
let m = flat_mask(a);
let size = m.len();
if size == 0 {
return None;
}
if !m.iter().any(|&b| b) {
return Some([0, size - 1]);
}
let first = m.iter().position(|&b| !b)?;
let last = m.iter().rposition(|&b| !b)?;
Some([first, last])
}
#[must_use]
pub fn flatnotmasked_contiguous<T, D>(a: &MaskedArray<T, D>) -> Vec<(usize, usize)>
where
T: Element,
D: Dimension,
{
let inv: Vec<bool> = flat_mask(a).iter().map(|&m| !m).collect();
ezclump_true(&inv)
}
#[must_use]
pub fn notmasked_edges<T, D>(a: &MaskedArray<T, D>) -> Option<[usize; 2]>
where
T: Element,
D: Dimension,
{
flatnotmasked_edges(a)
}
#[allow(
clippy::type_complexity,
reason = "mirrors numpy's two (rows, cols) tuples"
)]
pub fn notmasked_edges_axis2<T>(
a: &MaskedArray<T, Ix2>,
axis: usize,
) -> FerrayResult<((Vec<i64>, Vec<i64>), (Vec<i64>, Vec<i64>))>
where
T: Element,
{
if axis >= 2 {
return Err(FerrayError::axis_out_of_bounds(axis, 2));
}
let shape = a.shape();
let (rows, cols) = (shape[0], shape[1]);
let mask: Vec<bool> = a.mask().iter().copied().collect();
let (mut first_rows, mut first_cols) = (Vec::new(), Vec::new());
let (mut last_rows, mut last_cols) = (Vec::new(), Vec::new());
let other_len = if axis == 0 { cols } else { rows };
let axis_len = if axis == 0 { rows } else { cols };
for o in 0..other_len {
let mut first: Option<usize> = None;
let mut last: Option<usize> = None;
for k in 0..axis_len {
let (r, c) = if axis == 0 { (k, o) } else { (o, k) };
if !mask[r * cols + c] {
if first.is_none() {
first = Some(k);
}
last = Some(k);
}
}
if let (Some(f), Some(l)) = (first, last) {
let (fr_r, fr_c) = if axis == 0 { (f, o) } else { (o, f) };
let (lr_r, lr_c) = if axis == 0 { (l, o) } else { (o, l) };
first_rows.push(fr_r as i64);
first_cols.push(fr_c as i64);
last_rows.push(lr_r as i64);
last_cols.push(lr_c as i64);
}
}
Ok(((first_rows, first_cols), (last_rows, last_cols)))
}
pub fn notmasked_contiguous_axis<T>(
a: &MaskedArray<T, Ix2>,
axis: usize,
) -> FerrayResult<Vec<Vec<(usize, usize)>>>
where
T: Element,
{
if axis >= 2 {
return Err(FerrayError::axis_out_of_bounds(axis, 2));
}
let shape = a.shape();
let (rows, cols) = (shape[0], shape[1]);
let mask: Vec<bool> = a.mask().iter().copied().collect();
let other = (axis + 1) % 2;
let other_len = if other == 0 { rows } else { cols };
let axis_len = if axis == 0 { rows } else { cols };
let mut result: Vec<Vec<(usize, usize)>> = Vec::with_capacity(other_len);
for o in 0..other_len {
let mut lane: Vec<bool> = Vec::with_capacity(axis_len);
for k in 0..axis_len {
let (r, c) = if axis == 0 { (k, o) } else { (o, k) };
lane.push(mask[r * cols + c]);
}
let inv: Vec<bool> = lane.iter().map(|&m| !m).collect();
result.push(ezclump_true(&inv));
}
Ok(result)
}
impl<T> MaskedArray<T, Ix2>
where
T: Element + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T> + num_traits::Zero,
{
pub fn ma_dot_2d(&self, other: &MaskedArray<T, Ix2>) -> FerrayResult<MaskedArray<T, Ix2>> {
let a_shape = self.shape();
let b_shape = other.shape();
let (m, k1) = (a_shape[0], a_shape[1]);
let (k2, n) = (b_shape[0], b_shape[1]);
if k1 != k2 {
return Err(FerrayError::shape_mismatch(format!(
"ma_dot_2d: lhs.cols={k1} != rhs.rows={k2}"
)));
}
let a_data: Vec<T> = self.data().iter().copied().collect();
let a_mask: Vec<bool> = self.mask().iter().copied().collect();
let b_data: Vec<T> = other.data().iter().copied().collect();
let b_mask: Vec<bool> = other.mask().iter().copied().collect();
let zero = <T as num_traits::Zero>::zero();
let mut out_data = vec![zero; m * n];
let mut out_mask = vec![false; m * n];
for i in 0..m {
for j in 0..n {
let mut acc = zero;
let mut any_valid = false;
for k in 0..k1 {
let am = a_mask[i * k1 + k];
let bm = b_mask[k * n + j];
let av = if am { zero } else { a_data[i * k1 + k] };
let bv = if bm { zero } else { b_data[k * n + j] };
acc = acc + av * bv;
if !am && !bm {
any_valid = true;
}
}
out_data[i * n + j] = acc;
out_mask[i * n + j] = !any_valid;
}
}
let data_arr = Array::<T, Ix2>::from_vec(Ix2::new([m, n]), out_data)?;
let mask_arr = Array::<bool, Ix2>::from_vec(Ix2::new([m, n]), out_mask)?;
MaskedArray::new(data_arr, mask_arr)
}
}
impl<T, D> MaskedArray<T, D>
where
T: Element + PartialOrd + Copy,
D: Dimension,
{
pub fn argsort_axis(&self, axis: usize) -> FerrayResult<Array<u64, IxDyn>> {
let ndim = self.ndim();
if axis >= ndim {
return Err(FerrayError::axis_out_of_bounds(axis, ndim));
}
let shape = self.shape().to_vec();
let axis_len = shape[axis];
let total: usize = shape.iter().product();
let src_data: Vec<T> = self.data().iter().copied().collect();
let src_mask: Vec<bool> = self.mask().iter().copied().collect();
let mut strides = vec![1usize; ndim];
for i in (0..ndim.saturating_sub(1)).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
let mut out = vec![0u64; total];
let outer_shape: Vec<usize> = shape
.iter()
.enumerate()
.filter_map(|(i, &s)| if i == axis { None } else { Some(s) })
.collect();
let outer_size: usize = if outer_shape.is_empty() {
1
} else {
outer_shape.iter().product()
};
let mut outer_multi = vec![0usize; outer_shape.len()];
for _ in 0..outer_size {
let flat_of = |k: usize| -> usize {
let mut flat = 0usize;
let mut o = 0usize;
for (i, &stride) in strides.iter().enumerate() {
if i == axis {
flat += stride * k;
} else {
flat += stride * outer_multi[o];
o += 1;
}
}
flat
};
let mut order: Vec<usize> = (0..axis_len).collect();
order.sort_by(|&x, &y| {
let fx = flat_of(x);
let fy = flat_of(y);
match (src_mask[fx], src_mask[fy]) {
(false, false) => src_data[fx]
.partial_cmp(&src_data[fy])
.unwrap_or(std::cmp::Ordering::Equal),
(false, true) => std::cmp::Ordering::Less,
(true, false) => std::cmp::Ordering::Greater,
(true, true) => std::cmp::Ordering::Equal,
}
});
for (k, &pos) in order.iter().enumerate() {
out[flat_of(k)] = pos as u64;
}
for i in (0..outer_shape.len()).rev() {
outer_multi[i] += 1;
if outer_multi[i] < outer_shape[i] {
break;
}
outer_multi[i] = 0;
}
}
Array::<u64, IxDyn>::from_vec(IxDyn::new(&shape), out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ferray_core::Ix1;
fn arr1f(v: Vec<f64>) -> Array<f64, Ix1> {
let n = v.len();
Array::from_vec(Ix1::new([n]), v).unwrap()
}
fn mask1(v: Vec<bool>) -> Array<bool, Ix1> {
let n = v.len();
Array::from_vec(Ix1::new([n]), v).unwrap()
}
fn ma_f1(d: Vec<f64>, m: Vec<bool>) -> MaskedArray<f64, Ix1> {
MaskedArray::new(arr1f(d), mask1(m)).unwrap()
}
#[test]
fn prod_skips_masked() {
let ma = ma_f1(vec![2.0, 3.0, 5.0, 7.0], vec![false, true, false, false]);
let p = ma.prod().unwrap();
assert!((p - 70.0).abs() < 1e-10); }
#[test]
fn cumsum_propagates_mask() {
let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
let r = ma.cumsum_flat().unwrap();
let mask: Vec<bool> = r.mask().iter().copied().collect();
let data: Vec<f64> = r.data().iter().copied().collect();
assert_eq!(mask, vec![false, true, false, false]);
assert!((data[0] - 1.0).abs() < 1e-10);
assert!((data[2] - 4.0).abs() < 1e-10);
assert!((data[3] - 8.0).abs() < 1e-10);
}
#[test]
fn argmin_argmax_skip_masked() {
let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
assert_eq!(ma.argmin().unwrap(), 3);
assert_eq!(ma.argmax().unwrap(), 2);
}
#[test]
fn ptp_basic() {
let ma = ma_f1(vec![5.0, 1.0, 9.0, 3.0], vec![false, true, false, false]);
assert!((ma.ptp().unwrap() - 6.0).abs() < 1e-10);
}
#[test]
fn median_odd_and_even() {
let odd = ma_f1(vec![3.0, 1.0, 4.0, 1.0, 5.0], vec![false; 5]);
assert!((odd.median().unwrap() - 3.0).abs() < 1e-10);
let even = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
assert!((even.median().unwrap() - 2.5).abs() < 1e-10);
}
#[test]
fn average_unweighted_matches_mean() {
let ma = ma_f1(vec![2.0, 4.0, 6.0], vec![false; 3]);
assert!((ma.average(None).unwrap() - 4.0).abs() < 1e-10);
}
#[test]
fn average_weighted_skips_masked() {
let ma = ma_f1(vec![1.0, 100.0, 3.0], vec![false, true, false]);
let w = arr1f(vec![1.0, 1.0, 3.0]);
assert!((ma.average(Some(&w)).unwrap() - 2.5).abs() < 1e-10);
}
#[test]
fn anom_centers_at_zero() {
let ma = ma_f1(vec![1.0, 2.0, 3.0], vec![false; 3]);
let a = ma.anom().unwrap();
let data: Vec<f64> = a.data().iter().copied().collect();
assert!((data[0] - (-1.0)).abs() < 1e-10);
assert!((data[1] - 0.0).abs() < 1e-10);
assert!((data[2] - 1.0).abs() < 1e-10);
}
#[test]
fn masked_all_is_fully_masked() {
let ma: MaskedArray<f64, Ix1> = masked_all(Ix1::new([5])).unwrap();
let mask: Vec<bool> = ma.mask().iter().copied().collect();
assert_eq!(mask, vec![true; 5]);
}
#[test]
fn masked_values_within_tol() {
let arr = arr1f(vec![1.0, 1.0001, 2.0]);
let r = masked_values(&arr, 1.0, 1e-3, 0.0).unwrap();
let mask: Vec<bool> = r.mask().iter().copied().collect();
assert_eq!(mask, vec![true, true, false]);
}
#[test]
fn harden_mask_blocks_clearing() {
let mut ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
ma.harden_mask().unwrap();
assert!(ma.is_hard_mask());
ma.set_mask_flat(1, false).unwrap();
let mask: Vec<bool> = ma.mask().iter().copied().collect();
assert_eq!(mask, vec![false, true]);
ma.soften_mask().unwrap();
ma.set_mask_flat(1, false).unwrap();
let mask2: Vec<bool> = ma.mask().iter().copied().collect();
assert_eq!(mask2, vec![false, false]);
}
#[test]
fn mask_or_unions() {
let m1 = mask1(vec![true, false, false]);
let m2 = mask1(vec![false, true, false]);
let r = mask_or(&m1, &m2).unwrap();
let v: Vec<bool> = r.iter().copied().collect();
assert_eq!(v, vec![true, true, false]);
}
#[test]
fn make_mask_none_is_all_false() {
let m: Array<bool, Ix1> = make_mask_none(Ix1::new([3])).unwrap();
let v: Vec<bool> = m.iter().copied().collect();
assert_eq!(v, vec![false; 3]);
}
#[test]
fn clip_unmasked_only() {
let ma = ma_f1(vec![-5.0, 0.0, 5.0, 10.0], vec![false, false, false, true]);
let r = ma.clip(-1.0, 3.0).unwrap();
let data: Vec<f64> = r.data().iter().copied().collect();
assert_eq!(data, vec![-1.0, 0.0, 3.0, 10.0]);
}
#[test]
fn repeat_propagates_mask() {
let ma = ma_f1(vec![1.0, 2.0], vec![false, true]);
let r = ma.repeat(3).unwrap();
let data: Vec<f64> = r.data().iter().copied().collect();
let mask: Vec<bool> = r.mask().iter().copied().collect();
assert_eq!(data, vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
assert_eq!(mask, vec![false, false, false, true, true, true]);
}
#[test]
fn ma_unique_dedups_unmasked() {
let ma = ma_f1(
vec![3.0, 1.0, 2.0, 1.0, 3.0, 9.0],
vec![false, false, false, false, false, true],
);
let v = ma_unique(&ma).unwrap();
let data: Vec<f64> = v.iter().copied().collect();
assert_eq!(data, vec![1.0, 2.0, 3.0]);
}
#[test]
fn ma_isin_basic() {
let ma = ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false; 4]);
let r = ma_isin(&ma, &[2.0, 4.0]).unwrap();
let data: Vec<bool> = r.data().iter().copied().collect();
assert_eq!(data, vec![false, true, false, true]);
}
#[test]
fn ma_dot_flat_skips_masked_pairs() {
let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, true, false]);
let b = ma_f1(vec![4.0, 5.0, 6.0], vec![false, false, false]);
assert!((a.ma_dot_flat(&b).unwrap() - 22.0).abs() < 1e-10);
}
#[test]
fn fill_value_protocol_constants() {
assert_eq!(default_fill_value_f64(), 1e20);
assert!(!default_fill_value_bool());
assert!(maximum_fill_value::<f64>().is_infinite());
assert!(
minimum_fill_value::<f64>().is_infinite()
&& minimum_fill_value::<f64>().is_sign_negative()
);
}
#[test]
fn common_fill_value_returns_shared_or_zero() {
let a = ma_f1(vec![1.0, 2.0], vec![false, false]).with_fill_value(99.0);
let b = ma_f1(vec![3.0, 4.0], vec![false, false]).with_fill_value(99.0);
assert_eq!(common_fill_value(&a, &b), 99.0);
let c = ma_f1(vec![5.0, 6.0], vec![false, false]).with_fill_value(0.5);
assert_eq!(common_fill_value(&a, &c), 0.0); }
#[test]
fn ma_equal_and_friends_union_mask() {
let a = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, true]);
let b = ma_f1(vec![1.0, 9.0, 3.0], vec![false, true, false]);
let r = ma_equal(&a, &b).unwrap();
let data: Vec<bool> = r.data().iter().copied().collect();
let mask: Vec<bool> = r.mask().iter().copied().collect();
assert_eq!(data, vec![true, false, true]);
assert_eq!(mask, vec![false, true, true]);
}
#[test]
fn ma_logical_and_basic() {
let a = MaskedArray::new(
Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, true, false]).unwrap(),
Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
)
.unwrap();
let b = MaskedArray::new(
Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, false, true]).unwrap(),
Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false; 3]).unwrap(),
)
.unwrap();
let r = ma_logical_and(&a, &b).unwrap();
let data: Vec<bool> = r.data().iter().copied().collect();
assert_eq!(data, vec![true, false, false]);
}
#[test]
fn is_masked_array_always_true() {
let ma = ma_f1(vec![1.0], vec![false]);
assert!(is_masked_array(&ma));
assert!(is_ma(&ma));
}
#[test]
fn getmaskarray_materializes() {
let ma = MaskedArray::<f64, Ix1>::from_data(arr1f(vec![1.0, 2.0])).unwrap();
let m = getmaskarray(&ma).unwrap();
let v: Vec<bool> = m.iter().copied().collect();
assert_eq!(v, vec![false; 2]);
}
#[test]
fn diagonal_main_and_offset() {
use ferray_core::Ix2;
let data = Array::<f64, Ix2>::from_vec(
Ix2::new([3, 3]),
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
)
.unwrap();
let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
let ma = MaskedArray::new(data, mask).unwrap();
let main = ma.diagonal(0).unwrap();
let main_data: Vec<f64> = main.data().iter().copied().collect();
assert_eq!(main_data, vec![1.0, 5.0, 9.0]);
let upper = ma.diagonal(1).unwrap();
let upper_data: Vec<f64> = upper.data().iter().copied().collect();
assert_eq!(upper_data, vec![2.0, 6.0]);
}
#[test]
fn trace_sums_diagonal() {
use ferray_core::Ix2;
let data = Array::<f64, Ix2>::from_vec(
Ix2::new([3, 3]),
vec![1.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 9.0],
)
.unwrap();
let mask = Array::<bool, Ix2>::from_vec(Ix2::new([3, 3]), vec![false; 9]).unwrap();
let ma = MaskedArray::new(data, mask).unwrap();
assert!((ma.trace(0).unwrap() - 15.0).abs() < 1e-10);
}
#[test]
fn ma_apply_along_axis_sum_lane() {
use ferray_core::IxDyn;
let data =
Array::<f64, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
.unwrap();
let mask = Array::<bool, IxDyn>::from_vec(IxDyn::new(&[2, 3]), vec![false; 6]).unwrap();
let ma = MaskedArray::new(data, mask).unwrap();
let result = ma_apply_along_axis(&ma, 1, |lane| {
let s: f64 = lane.data().iter().copied().sum();
Ok((s, false))
})
.unwrap();
let v: Vec<f64> = result.data().iter().copied().collect();
assert_eq!(v, vec![6.0, 15.0]);
}
fn ab() -> (MaskedArray<f64, Ix1>, MaskedArray<f64, Ix1>) {
(
ma_f1(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]),
ma_f1(vec![3.0, 4.0, 5.0], vec![false, false, true]),
)
}
fn data_mask(m: &MaskedArray<f64, Ix1>) -> (Vec<f64>, Vec<bool>) {
(
m.data().iter().copied().collect(),
m.mask().iter().copied().collect(),
)
}
#[test]
fn ma_unique_masked_trails_one_masked_slot() -> FerrayResult<()> {
let (a, _) = ab();
let (data, mask) = data_mask(&ma_unique_masked(&a)?);
assert_eq!(&data[..3], &[1.0, 3.0, 4.0]);
assert_eq!(mask, vec![false, false, false, true]);
Ok(())
}
#[test]
fn ma_intersect1d_common_with_both_masked() -> FerrayResult<()> {
let (a, b) = ab();
let (data, mask) = data_mask(&ma_intersect1d(&a, &b)?);
assert_eq!(&data[..2], &[3.0, 4.0]);
assert_eq!(mask, vec![false, false, true]);
Ok(())
}
#[test]
fn ma_union1d_all_uniques_plus_masked() -> FerrayResult<()> {
let (a, b) = ab();
let (data, mask) = data_mask(&ma_union1d(&a, &b)?);
assert_eq!(&data[..3], &[1.0, 3.0, 4.0]);
assert_eq!(mask, vec![false, false, false, true]);
Ok(())
}
#[test]
fn ma_setdiff1d_drops_masked_when_rhs_masked() -> FerrayResult<()> {
let (a, b) = ab();
let (data, mask) = data_mask(&ma_setdiff1d(&a, &b)?);
assert_eq!(data, vec![1.0]);
assert_eq!(mask, vec![false]);
Ok(())
}
#[test]
fn ma_setxor1d_symmetric_difference() -> FerrayResult<()> {
let (a, b) = ab();
let (data, mask) = data_mask(&ma_setxor1d(&a, &b)?);
assert_eq!(data, vec![1.0]);
assert_eq!(mask, vec![false]);
Ok(())
}
fn m23() -> FerrayResult<MaskedArray<f64, Ix2>> {
let data =
Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])?;
let mask = Array::<bool, Ix2>::from_vec(
Ix2::new([2, 3]),
vec![false, false, true, false, false, false],
)?;
MaskedArray::new(data, mask)
}
#[test]
fn ma_compress_rowcols_both() -> FerrayResult<()> {
let r = ma_compress_rowcols(&m23()?, None)?;
assert_eq!(r.shape(), &[1, 2]);
let v: Vec<f64> = r.iter().copied().collect();
assert_eq!(v, vec![4.0, 5.0]);
Ok(())
}
#[test]
fn ma_compress_rows_and_cols() -> FerrayResult<()> {
let rows = ma_compress_rows(&m23()?)?;
assert_eq!(rows.shape(), &[1, 3]);
assert_eq!(
rows.iter().copied().collect::<Vec<f64>>(),
vec![4.0, 5.0, 6.0]
);
let cols = ma_compress_cols(&m23()?)?;
assert_eq!(cols.shape(), &[2, 2]);
assert_eq!(
cols.iter().copied().collect::<Vec<f64>>(),
vec![1.0, 2.0, 4.0, 5.0]
);
Ok(())
}
#[test]
fn ma_mask_rowcols_masks_whole_row_and_col() -> FerrayResult<()> {
let r = ma_mask_rowcols(&m23()?, None)?;
let mask: Vec<bool> = r.mask().iter().copied().collect();
assert_eq!(mask, vec![true, true, true, false, false, true]);
let data: Vec<f64> = r.data().iter().copied().collect();
assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
Ok(())
}
#[test]
fn ma_cov_unmasked_pairs() -> FerrayResult<()> {
let c = ma_cov(&m23()?, true, false, None)?;
let data: Vec<f64> = c.data().iter().copied().collect();
let mask: Vec<bool> = c.mask().iter().copied().collect();
let expect = [0.5, 0.5, 0.5, 1.0];
for (g, e) in data.iter().zip(expect.iter()) {
assert!((g - e).abs() < 1e-12, "cov {g} != {e}");
}
assert_eq!(mask, vec![false; 4]);
Ok(())
}
#[test]
fn ma_corrcoef_normalized() -> FerrayResult<()> {
let c = ma_corrcoef(&m23()?, true)?;
let data: Vec<f64> = c.data().iter().copied().collect();
let expect = [1.0, 0.7071067811865475, 0.7071067811865475, 1.0];
for (g, e) in data.iter().zip(expect.iter()) {
assert!((g - e).abs() < 1e-12, "corr {g} != {e}");
}
Ok(())
}
#[test]
fn clump_run_length_matches_numpy() {
let m = ma_f1(
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
vec![false, false, true, true, false, false],
);
assert_eq!(clump_masked(&m), vec![(2, 4)]);
assert_eq!(clump_unmasked(&m), vec![(0, 2), (4, 6)]);
assert_eq!(flatnotmasked_contiguous(&m), vec![(0, 2), (4, 6)]);
assert_eq!(flatnotmasked_edges(&m), Some([0, 5]));
assert_eq!(notmasked_edges(&m), Some([0, 5]));
}
#[test]
fn clump_nomask_and_allmask_edges() {
let none = ma_f1(vec![1.0, 2.0, 3.0], vec![false, false, false]);
assert_eq!(clump_masked(&none), vec![]);
assert_eq!(clump_unmasked(&none), vec![(0, 3)]);
assert_eq!(flatnotmasked_edges(&none), Some([0, 2]));
let all = ma_f1(vec![1.0, 2.0, 3.0], vec![true, true, true]);
assert_eq!(clump_unmasked(&all), vec![]);
assert_eq!(clump_masked(&all), vec![(0, 3)]);
assert_eq!(flatnotmasked_contiguous(&all), vec![]);
assert_eq!(flatnotmasked_edges(&all), None);
}
#[test]
fn clump_leading_and_trailing_masked() {
let mut mask = vec![false; 10];
for &i in &[0usize, 1, 2, 6, 8, 9] {
mask[i] = true;
}
let data: Vec<f64> = (0..10).map(|x| x as f64).collect();
let m = ma_f1(data, mask);
assert_eq!(clump_masked(&m), vec![(0, 3), (6, 7), (8, 10)]);
assert_eq!(clump_unmasked(&m), vec![(3, 6), (7, 8)]);
}
#[test]
fn notmasked_contiguous_axis_2d_matches_numpy() -> FerrayResult<()> {
use ferray_core::Ix2;
let mask = vec![
false, true, false, false, true, true, true, false, false, true, true, false,
];
let data: Vec<f64> = (0..12).map(|x| x as f64).collect();
let d = Array::<f64, Ix2>::from_vec(Ix2::new([3, 4]), data)?;
let mk = Array::<bool, Ix2>::from_vec(Ix2::new([3, 4]), mask)?;
let m = MaskedArray::new(d, mk)?;
let by0 = notmasked_contiguous_axis(&m, 0)?;
assert_eq!(
by0,
vec![vec![(0, 1), (2, 3)], vec![], vec![(0, 1)], vec![(0, 3)]]
);
let by1 = notmasked_contiguous_axis(&m, 1)?;
assert_eq!(
by1,
vec![vec![(0, 1), (2, 4)], vec![(3, 4)], vec![(0, 1), (3, 4)]]
);
Ok(())
}
#[test]
fn ma_dot_2d_matches_numpy() -> FerrayResult<()> {
use ferray_core::Ix2;
let d = Array::<f64, Ix2>::from_vec(Ix2::new([2, 2]), vec![1.0, 2.0, 3.0, 4.0])?;
let mk = Array::<bool, Ix2>::from_vec(Ix2::new([2, 2]), vec![false, true, false, false])?;
let a = MaskedArray::new(d, mk)?;
let out = a.ma_dot_2d(&a)?;
let data: Vec<f64> = out.data().iter().copied().collect();
let mask: Vec<bool> = out.mask().iter().copied().collect();
assert_eq!(data, vec![1.0, 0.0, 15.0, 16.0]);
assert_eq!(mask, vec![false, true, false, false]);
Ok(())
}
#[test]
fn argsort_axis_matches_numpy() -> FerrayResult<()> {
use ferray_core::Ix2;
let d = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![3.0, 1.0, 2.0, 6.0, 5.0, 4.0])?;
let mk = Array::<bool, Ix2>::from_vec(
Ix2::new([2, 3]),
vec![false, false, true, false, false, false],
)?;
let b = MaskedArray::new(d, mk)?;
let by1 = b.argsort_axis(1)?;
let v1: Vec<u64> = by1.iter().copied().collect();
assert_eq!(v1, vec![1, 0, 2, 2, 1, 0]);
let by0 = b.argsort_axis(0)?;
let v0: Vec<u64> = by0.iter().copied().collect();
assert_eq!(v0, vec![0, 0, 1, 1, 1, 0]);
Ok(())
}
}