Skip to main content

ferray_ma/
lib.rs

1// ferray-ma: Masked arrays with mask propagation
2//
3// This crate implements `numpy.ma`-style masked arrays for the ferray workspace.
4// A `MaskedArray<T, D>` pairs a data array with a boolean mask array where
5// `true` = masked/invalid. All operations (arithmetic, reductions, ufuncs)
6// respect the mask by skipping masked elements.
7//
8// # Modules
9// - `masked_array`: The core `MaskedArray<T, D>` type
10// - `reductions`: Masked mean, sum, min, max, var, std, count
11// - `constructors`: masked_where, masked_invalid, masked_equal, etc.
12// - `arithmetic`: Masked binary ops with mask union
13// - `ufunc_support`: Wrapper functions for ufunc operations on MaskedArrays
14// - `sorting`: Masked sort, argsort
15// - `mask_ops`: harden_mask, soften_mask, getmask, getdata, is_masked, count_masked
16// - `filled`: filled, compressed
17
18pub mod arithmetic;
19pub mod constructors;
20pub mod filled;
21pub mod mask_ops;
22pub mod masked_array;
23pub mod reductions;
24pub mod sorting;
25pub mod ufunc_support;
26
27// Re-export the primary type at crate root
28pub use masked_array::MaskedArray;
29
30// Re-export masking constructors
31pub use constructors::{
32    masked_equal, masked_greater, masked_greater_equal, masked_inside, masked_invalid, masked_less,
33    masked_less_equal, masked_not_equal, masked_outside, masked_where,
34};
35
36// Re-export arithmetic operations
37pub use arithmetic::{
38    masked_add, masked_add_array, masked_div, masked_div_array, masked_mul, masked_mul_array,
39    masked_sub, masked_sub_array,
40};
41
42// Re-export mask manipulation functions
43pub use mask_ops::{count_masked, getdata, getmask, is_masked};
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use ferray_core::Array;
49    use ferray_core::dimension::Ix1;
50
51    // -----------------------------------------------------------------------
52    // AC-1: MaskedArray::new([1,2,3,4,5], [false,false,true,false,false]).mean() == 3.0
53    // -----------------------------------------------------------------------
54    #[test]
55    fn ac1_masked_mean_skips_masked() {
56        let data =
57            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
58        let mask =
59            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, false, true, false, false])
60                .unwrap();
61        let ma = MaskedArray::new(data, mask).unwrap();
62        let mean = ma.mean().unwrap();
63        // (1 + 2 + 4 + 5) / 4 = 3.0
64        assert!((mean - 3.0).abs() < 1e-10);
65    }
66
67    // -----------------------------------------------------------------------
68    // AC-2: filled(0.0) replaces masked elements with 0.0
69    // -----------------------------------------------------------------------
70    #[test]
71    fn ac2_filled_replaces_masked() {
72        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
73        let mask =
74            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![false, true, false, true]).unwrap();
75        let ma = MaskedArray::new(data, mask).unwrap();
76        let filled = ma.filled(0.0).unwrap();
77        assert_eq!(filled.as_slice().unwrap(), &[1.0, 0.0, 3.0, 0.0]);
78    }
79
80    // -----------------------------------------------------------------------
81    // AC-3: compressed() returns only unmasked elements as 1D
82    // -----------------------------------------------------------------------
83    #[test]
84    fn ac3_compressed_returns_unmasked() {
85        let data =
86            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![10.0, 20.0, 30.0, 40.0, 50.0]).unwrap();
87        let mask =
88            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, true, false, true, false])
89                .unwrap();
90        let ma = MaskedArray::new(data, mask).unwrap();
91        let compressed = ma.compressed().unwrap();
92        assert_eq!(compressed.as_slice().unwrap(), &[10.0, 30.0, 50.0]);
93    }
94
95    // -----------------------------------------------------------------------
96    // AC-4: masked_invalid masks NaN and Inf
97    // -----------------------------------------------------------------------
98    #[test]
99    fn ac4_masked_invalid_nan_inf() {
100        let data =
101            Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, f64::NAN, 3.0, f64::INFINITY])
102                .unwrap();
103        let ma = masked_invalid(&data).unwrap();
104        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
105        assert_eq!(mask_vals, vec![false, true, false, true]);
106    }
107
108    // -----------------------------------------------------------------------
109    // AC-5: ma1 + ma2 produces correct mask union and correct values
110    // -----------------------------------------------------------------------
111    #[test]
112    fn ac5_add_mask_union() {
113        let d1 = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
114        let m1 =
115            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![false, true, false, false]).unwrap();
116        let ma1 = MaskedArray::new(d1, m1).unwrap();
117
118        let d2 = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![10.0, 20.0, 30.0, 40.0]).unwrap();
119        let m2 =
120            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![false, false, true, false]).unwrap();
121        let ma2 = MaskedArray::new(d2, m2).unwrap();
122
123        let result = masked_add(&ma1, &ma2).unwrap();
124        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
125        // Mask union: [false, true, true, false]
126        assert_eq!(mask_vals, vec![false, true, true, false]);
127        // Unmasked values: 1+10=11, 4+40=44; masked get 0.0
128        let data_vals: Vec<f64> = result.data().iter().copied().collect();
129        assert!((data_vals[0] - 11.0).abs() < 1e-10);
130        assert!((data_vals[3] - 44.0).abs() < 1e-10);
131    }
132
133    // -----------------------------------------------------------------------
134    // AC-7: sin(masked_array) returns same mask, correct values
135    // -----------------------------------------------------------------------
136    #[test]
137    fn ac7_ufunc_sin_masked() {
138        use std::f64::consts::FRAC_PI_2;
139        let data =
140            Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![0.0, FRAC_PI_2, FRAC_PI_2]).unwrap();
141        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
142        let ma = MaskedArray::new(data, mask).unwrap();
143        let result = ufunc_support::sin(&ma).unwrap();
144        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
145        assert_eq!(mask_vals, vec![false, true, false]);
146        let data_vals: Vec<f64> = result.data().iter().copied().collect();
147        // sin(0) = 0, masked = 0.0 (skipped), sin(pi/2) = 1.0
148        assert!((data_vals[0] - 0.0).abs() < 1e-10);
149        assert!((data_vals[2] - 1.0).abs() < 1e-10);
150    }
151
152    // -----------------------------------------------------------------------
153    // AC-8: sort places masked at end; harden_mask prevents clearing
154    // -----------------------------------------------------------------------
155    #[test]
156    fn ac8_sort_masked_at_end() {
157        let data =
158            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![5.0, 1.0, 3.0, 2.0, 4.0]).unwrap();
159        let mask =
160            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, false, true, false, false])
161                .unwrap();
162        let ma = MaskedArray::new(data, mask).unwrap();
163        let sorted = ma.sort().unwrap();
164        let data_vals: Vec<f64> = sorted.data().iter().copied().collect();
165        let mask_vals: Vec<bool> = sorted.mask().iter().copied().collect();
166        // Unmasked [5, 1, 2, 4] sorted = [1, 2, 4, 5], then masked [3]
167        assert_eq!(data_vals, vec![1.0, 2.0, 4.0, 5.0, 3.0]);
168        assert_eq!(mask_vals, vec![false, false, false, false, true]);
169    }
170
171    #[test]
172    fn ac8_harden_mask_prevents_clearing() {
173        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
174        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
175        let mut ma = MaskedArray::new(data, mask).unwrap();
176
177        ma.harden_mask().unwrap();
178        assert!(ma.is_hard_mask());
179
180        // Try to clear the mask at index 1 — should be silently ignored
181        ma.set_mask_flat(1, false).unwrap();
182        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
183        assert_eq!(mask_vals, vec![false, true, false]);
184
185        // Setting a mask bit to true should still work
186        ma.set_mask_flat(0, true).unwrap();
187        let mask_vals2: Vec<bool> = ma.mask().iter().copied().collect();
188        assert_eq!(mask_vals2, vec![true, true, false]);
189
190        // Soften and then clearing should work
191        ma.soften_mask().unwrap();
192        assert!(!ma.is_hard_mask());
193        ma.set_mask_flat(1, false).unwrap();
194        let mask_vals3: Vec<bool> = ma.mask().iter().copied().collect();
195        assert_eq!(mask_vals3, vec![true, false, false]);
196    }
197
198    // -----------------------------------------------------------------------
199    // AC-9: is_masked returns true/false correctly
200    // -----------------------------------------------------------------------
201    #[test]
202    fn ac9_is_masked() {
203        let data1 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
204        let mask1 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
205        let ma1 = MaskedArray::new(data1, mask1).unwrap();
206        assert!(is_masked(&ma1).unwrap());
207
208        let data2 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
209        let mask2 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, false]).unwrap();
210        let ma2 = MaskedArray::new(data2, mask2).unwrap();
211        assert!(!is_masked(&ma2).unwrap());
212    }
213
214    // -----------------------------------------------------------------------
215    // Additional tests
216    // -----------------------------------------------------------------------
217
218    #[test]
219    fn shape_mismatch_error() {
220        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
221        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([2]), vec![false, true]).unwrap();
222        assert!(MaskedArray::new(data, mask).is_err());
223    }
224
225    #[test]
226    fn from_data_no_mask() {
227        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
228        let ma = MaskedArray::from_data(data).unwrap();
229        assert!(!is_masked(&ma).unwrap());
230        assert_eq!(ma.count().unwrap(), 3);
231    }
232
233    #[test]
234    fn sum_skips_masked() {
235        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
236        let mask =
237            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![false, true, false, true]).unwrap();
238        let ma = MaskedArray::new(data, mask).unwrap();
239        assert!((ma.sum().unwrap() - 4.0).abs() < 1e-10);
240    }
241
242    #[test]
243    fn min_max_skip_masked() {
244        let data =
245            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![5.0, 1.0, 3.0, 2.0, 4.0]).unwrap();
246        let mask =
247            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, true, false, false, false])
248                .unwrap();
249        let ma = MaskedArray::new(data, mask).unwrap();
250        assert!((ma.min().unwrap() - 2.0).abs() < 1e-10);
251        assert!((ma.max().unwrap() - 5.0).abs() < 1e-10);
252    }
253
254    #[test]
255    fn var_std_skip_masked() {
256        // values: [2, 4, 6] (mask out index 1 and 4)
257        let data =
258            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![2.0, 99.0, 4.0, 6.0, 99.0]).unwrap();
259        let mask =
260            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, true, false, false, true])
261                .unwrap();
262        let ma = MaskedArray::new(data, mask).unwrap();
263        let mean = ma.mean().unwrap();
264        assert!((mean - 4.0).abs() < 1e-10);
265        // var = ((2-4)^2 + (4-4)^2 + (6-4)^2) / 3 = 8/3
266        let v = ma.var().unwrap();
267        assert!((v - 8.0 / 3.0).abs() < 1e-10);
268        let s = ma.std().unwrap();
269        assert!((s - (8.0_f64 / 3.0).sqrt()).abs() < 1e-10);
270    }
271
272    #[test]
273    fn count_elements() {
274        let data = Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0; 5]).unwrap();
275        let mask =
276            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, true, true, false, false])
277                .unwrap();
278        let ma = MaskedArray::new(data, mask).unwrap();
279        assert_eq!(ma.count().unwrap(), 3);
280    }
281
282    #[test]
283    fn masked_equal_test() {
284        let data =
285            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0, 2.0, 3.0, 2.0, 1.0]).unwrap();
286        let ma = masked_equal(&data, 2.0).unwrap();
287        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
288        assert_eq!(mask_vals, vec![false, true, false, true, false]);
289    }
290
291    #[test]
292    fn masked_greater_test() {
293        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
294        let ma = masked_greater(&data, 2.0).unwrap();
295        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
296        assert_eq!(mask_vals, vec![false, false, true, true]);
297    }
298
299    #[test]
300    fn masked_less_test() {
301        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
302        let ma = masked_less(&data, 3.0).unwrap();
303        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
304        assert_eq!(mask_vals, vec![true, true, false, false]);
305    }
306
307    #[test]
308    fn masked_not_equal_test() {
309        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
310        let ma = masked_not_equal(&data, 2.0).unwrap();
311        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
312        assert_eq!(mask_vals, vec![true, false, true]);
313    }
314
315    #[test]
316    fn masked_greater_equal_test() {
317        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
318        let ma = masked_greater_equal(&data, 3.0).unwrap();
319        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
320        assert_eq!(mask_vals, vec![false, false, true, true]);
321    }
322
323    #[test]
324    fn masked_less_equal_test() {
325        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
326        let ma = masked_less_equal(&data, 2.0).unwrap();
327        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
328        assert_eq!(mask_vals, vec![true, true, false, false]);
329    }
330
331    #[test]
332    fn masked_inside_test() {
333        let data =
334            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
335        let ma = masked_inside(&data, 2.0, 4.0).unwrap();
336        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
337        assert_eq!(mask_vals, vec![false, true, true, true, false]);
338    }
339
340    #[test]
341    fn masked_outside_test() {
342        let data =
343            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
344        let ma = masked_outside(&data, 2.0, 4.0).unwrap();
345        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
346        assert_eq!(mask_vals, vec![true, false, false, false, true]);
347    }
348
349    #[test]
350    fn masked_where_test() {
351        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
352        let cond =
353            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![true, false, true, false]).unwrap();
354        let ma = masked_where(&cond, &data).unwrap();
355        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
356        assert_eq!(mask_vals, vec![true, false, true, false]);
357    }
358
359    #[test]
360    fn argsort_test() {
361        let data =
362            Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![5.0, 1.0, 3.0, 2.0, 4.0]).unwrap();
363        let mask =
364            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false, false, true, false, false])
365                .unwrap();
366        let ma = MaskedArray::new(data, mask).unwrap();
367        let indices = ma.argsort().unwrap();
368        let idx_vals: Vec<usize> = indices.iter().copied().collect();
369        // Unmasked: index 1 (1.0), 3 (2.0), 4 (4.0), 0 (5.0); masked: 2
370        assert_eq!(idx_vals, vec![1, 3, 4, 0, 2]);
371    }
372
373    #[test]
374    fn getmask_getdata_test() {
375        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
376        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
377        let ma = MaskedArray::new(data.clone(), mask.clone()).unwrap();
378
379        let got_mask = getmask(&ma).unwrap();
380        let got_data = getdata(&ma).unwrap();
381
382        assert_eq!(got_mask.as_slice().unwrap(), mask.as_slice().unwrap());
383        assert_eq!(got_data.as_slice().unwrap(), data.as_slice().unwrap());
384    }
385
386    #[test]
387    fn count_masked_test() {
388        let data = Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0; 5]).unwrap();
389        let mask =
390            Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![true, false, true, true, false])
391                .unwrap();
392        let ma = MaskedArray::new(data, mask).unwrap();
393        assert_eq!(count_masked(&ma, None).unwrap(), 3);
394    }
395
396    #[test]
397    fn masked_add_array_test() {
398        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
399        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
400        let ma = MaskedArray::new(data, mask).unwrap();
401        let arr = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![10.0, 20.0, 30.0]).unwrap();
402        let result = masked_add_array(&ma, &arr).unwrap();
403        let data_vals: Vec<f64> = result.data().iter().copied().collect();
404        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
405        assert_eq!(mask_vals, vec![false, true, false]);
406        assert!((data_vals[0] - 11.0).abs() < 1e-10);
407        assert!((data_vals[2] - 33.0).abs() < 1e-10);
408    }
409
410    #[test]
411    fn all_masked_mean_is_nan() {
412        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
413        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, true, true]).unwrap();
414        let ma = MaskedArray::new(data, mask).unwrap();
415        assert!(ma.mean().unwrap().is_nan());
416    }
417
418    #[test]
419    fn all_masked_min_errors() {
420        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
421        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![true, true, true]).unwrap();
422        let ma = MaskedArray::new(data, mask).unwrap();
423        assert!(ma.min().is_err());
424    }
425
426    #[test]
427    fn ufunc_exp_masked() {
428        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![0.0, 1.0, 2.0]).unwrap();
429        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
430        let ma = MaskedArray::new(data, mask).unwrap();
431        let result = ufunc_support::exp(&ma).unwrap();
432        let data_vals: Vec<f64> = result.data().iter().copied().collect();
433        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
434        assert_eq!(mask_vals, vec![false, true, false]);
435        assert!((data_vals[0] - 1.0).abs() < 1e-10); // exp(0) = 1
436        assert!((data_vals[2] - 2.0_f64.exp()).abs() < 1e-10);
437    }
438
439    #[test]
440    fn ufunc_sqrt_masked() {
441        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![4.0, 9.0, 16.0, 25.0]).unwrap();
442        let mask =
443            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![false, true, false, true]).unwrap();
444        let ma = MaskedArray::new(data, mask).unwrap();
445        let result = ufunc_support::sqrt(&ma).unwrap();
446        let data_vals: Vec<f64> = result.data().iter().copied().collect();
447        assert!((data_vals[0] - 2.0).abs() < 1e-10);
448        assert!((data_vals[2] - 4.0).abs() < 1e-10);
449    }
450
451    #[test]
452    fn set_mask_hardened() {
453        let data = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
454        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
455        let mut ma = MaskedArray::new(data, mask).unwrap();
456        ma.harden_mask().unwrap();
457
458        // set_mask with all-false should not clear the existing true
459        let new_mask =
460            Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, false]).unwrap();
461        ma.set_mask(new_mask).unwrap();
462        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
463        // Hard mask: union of old [false, true, false] and new [false, false, false] = [false, true, false]
464        assert_eq!(mask_vals, vec![false, true, false]);
465    }
466
467    #[test]
468    fn masked_sub_test() {
469        let d1 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![10.0, 20.0, 30.0]).unwrap();
470        let m1 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, true]).unwrap();
471        let ma1 = MaskedArray::new(d1, m1).unwrap();
472
473        let d2 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, 2.0, 3.0]).unwrap();
474        let m2 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
475        let ma2 = MaskedArray::new(d2, m2).unwrap();
476
477        let result = masked_sub(&ma1, &ma2).unwrap();
478        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
479        assert_eq!(mask_vals, vec![false, true, true]);
480        let data_vals: Vec<f64> = result.data().iter().copied().collect();
481        assert!((data_vals[0] - 9.0).abs() < 1e-10);
482    }
483
484    #[test]
485    fn masked_mul_test() {
486        let d1 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![2.0, 3.0, 4.0]).unwrap();
487        let m1 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
488        let ma1 = MaskedArray::new(d1, m1).unwrap();
489
490        let d2 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![5.0, 6.0, 7.0]).unwrap();
491        let m2 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, false]).unwrap();
492        let ma2 = MaskedArray::new(d2, m2).unwrap();
493
494        let result = masked_mul(&ma1, &ma2).unwrap();
495        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
496        assert_eq!(mask_vals, vec![false, true, false]);
497        let data_vals: Vec<f64> = result.data().iter().copied().collect();
498        assert!((data_vals[0] - 10.0).abs() < 1e-10);
499        assert!((data_vals[2] - 28.0).abs() < 1e-10);
500    }
501
502    #[test]
503    fn masked_div_test() {
504        let d1 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![10.0, 20.0, 30.0]).unwrap();
505        let m1 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, true]).unwrap();
506        let ma1 = MaskedArray::new(d1, m1).unwrap();
507
508        let d2 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![2.0, 5.0, 6.0]).unwrap();
509        let m2 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, false]).unwrap();
510        let ma2 = MaskedArray::new(d2, m2).unwrap();
511
512        let result = masked_div(&ma1, &ma2).unwrap();
513        let data_vals: Vec<f64> = result.data().iter().copied().collect();
514        assert!((data_vals[0] - 5.0).abs() < 1e-10);
515        assert!((data_vals[1] - 4.0).abs() < 1e-10);
516    }
517
518    #[test]
519    fn masked_invalid_negative_inf() {
520        let data =
521            Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.0, f64::NEG_INFINITY, 3.0]).unwrap();
522        let ma = masked_invalid(&data).unwrap();
523        let mask_vals: Vec<bool> = ma.mask().iter().copied().collect();
524        assert_eq!(mask_vals, vec![false, true, false]);
525    }
526
527    #[test]
528    fn empty_array_operations() {
529        let data = Array::<f64, Ix1>::from_vec(Ix1::new([0]), vec![]).unwrap();
530        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([0]), vec![]).unwrap();
531        let ma = MaskedArray::new(data, mask).unwrap();
532        assert_eq!(ma.count().unwrap(), 0);
533        assert!(ma.mean().unwrap().is_nan());
534        let compressed = ma.compressed().unwrap();
535        assert_eq!(compressed.size(), 0);
536    }
537
538    #[test]
539    fn ndim_shape_size() {
540        let data = Array::<f64, Ix1>::from_vec(Ix1::new([5]), vec![1.0; 5]).unwrap();
541        let mask = Array::<bool, Ix1>::from_vec(Ix1::new([5]), vec![false; 5]).unwrap();
542        let ma = MaskedArray::new(data, mask).unwrap();
543        assert_eq!(ma.ndim(), 1);
544        assert_eq!(ma.shape(), &[5]);
545        assert_eq!(ma.size(), 5);
546    }
547
548    #[test]
549    fn ufunc_binary_power() {
550        let d1 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![2.0, 3.0, 4.0]).unwrap();
551        let m1 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, true, false]).unwrap();
552        let ma1 = MaskedArray::new(d1, m1).unwrap();
553
554        let d2 = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![3.0, 2.0, 2.0]).unwrap();
555        let m2 = Array::<bool, Ix1>::from_vec(Ix1::new([3]), vec![false, false, false]).unwrap();
556        let ma2 = MaskedArray::new(d2, m2).unwrap();
557
558        let result = ufunc_support::power(&ma1, &ma2).unwrap();
559        let data_vals: Vec<f64> = result.data().iter().copied().collect();
560        let mask_vals: Vec<bool> = result.mask().iter().copied().collect();
561        assert_eq!(mask_vals, vec![false, true, false]);
562        assert!((data_vals[0] - 8.0).abs() < 1e-10); // 2^3 = 8
563        assert!((data_vals[2] - 16.0).abs() < 1e-10); // 4^2 = 16
564    }
565
566    #[test]
567    fn filled_with_custom_value() {
568        let data = Array::<f64, Ix1>::from_vec(Ix1::new([4]), vec![1.0, 2.0, 3.0, 4.0]).unwrap();
569        let mask =
570            Array::<bool, Ix1>::from_vec(Ix1::new([4]), vec![true, false, true, false]).unwrap();
571        let ma = MaskedArray::new(data, mask).unwrap();
572        let filled = ma.filled(-999.0).unwrap();
573        assert_eq!(filled.as_slice().unwrap(), &[-999.0, 2.0, -999.0, 4.0]);
574    }
575}