1use ferray_core::dimension::Dimension;
8use ferray_core::dtype::Element;
9use ferray_core::error::FerrayResult;
10use num_traits::Float;
11
12use ferray_core::Array;
13use ferray_core::error::FerrayError;
14
15use crate::MaskedArray;
16use crate::arithmetic::{masked_binary_op, masked_unary_op};
17
18pub fn masked_unary_domain<T, D, F, Dom>(
48 ma: &MaskedArray<T, D>,
49 f: F,
50 in_domain: Dom,
51) -> FerrayResult<MaskedArray<T, D>>
52where
53 T: Element + Copy,
54 D: Dimension,
55 F: Fn(T) -> T,
56 Dom: Fn(T) -> bool,
57{
58 let fill = ma.fill_value();
59 let n = ma.size();
60 let mut data_out: Vec<T> = Vec::with_capacity(n);
61 let mut mask_out: Vec<bool> = Vec::with_capacity(n);
62 for (&v, &m) in ma.data().iter().zip(ma.mask().iter()) {
63 let should_mask = m || !in_domain(v);
65 if should_mask {
66 data_out.push(fill);
67 mask_out.push(true);
68 } else {
69 data_out.push(f(v));
70 mask_out.push(false);
71 }
72 }
73 let data_arr = Array::from_vec(ma.dim().clone(), data_out)?;
74 let mask_arr = Array::from_vec(ma.dim().clone(), mask_out)?;
75 let mut out = MaskedArray::new(data_arr, mask_arr)?;
76 out.set_fill_value(fill);
77 Ok(out)
78}
79
80pub fn masked_binary_domain<T, D, F, Dom>(
90 a: &MaskedArray<T, D>,
91 b: &MaskedArray<T, D>,
92 f: F,
93 in_domain: Dom,
94 op_name: &str,
95) -> FerrayResult<MaskedArray<T, D>>
96where
97 T: Element + Copy,
98 D: Dimension,
99 F: Fn(T, T) -> T,
100 Dom: Fn(T, T) -> bool,
101{
102 if a.shape() != b.shape() {
103 return Err(FerrayError::shape_mismatch(format!(
104 "{op_name}: shapes {:?} and {:?} differ (broadcasting not supported for domain-aware ops)",
105 a.shape(),
106 b.shape()
107 )));
108 }
109 let fill = a.fill_value();
110 let n = a.size();
111 let mut data_out: Vec<T> = Vec::with_capacity(n);
112 let mut mask_out: Vec<bool> = Vec::with_capacity(n);
113 for (((&x, &y), &ma_bit), &mb_bit) in a
114 .data()
115 .iter()
116 .zip(b.data().iter())
117 .zip(a.mask().iter())
118 .zip(b.mask().iter())
119 {
120 let should_mask = ma_bit || mb_bit || !in_domain(x, y);
123 if should_mask {
124 data_out.push(fill);
125 mask_out.push(true);
126 } else {
127 data_out.push(f(x, y));
128 mask_out.push(false);
129 }
130 }
131 let data_arr = Array::from_vec(a.dim().clone(), data_out)?;
132 let mask_arr = Array::from_vec(a.dim().clone(), mask_out)?;
133 let mut out = MaskedArray::new(data_arr, mask_arr)?;
134 out.set_fill_value(fill);
135 Ok(out)
136}
137
138pub fn log_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
144where
145 T: Element + Float,
146 D: Dimension,
147{
148 let zero = <T as Element>::zero();
149 masked_unary_domain(ma, T::ln, move |x| x > zero)
150}
151
152pub fn log2_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
154where
155 T: Element + Float,
156 D: Dimension,
157{
158 let zero = <T as Element>::zero();
159 masked_unary_domain(ma, T::log2, move |x| x > zero)
160}
161
162pub fn log10_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
164where
165 T: Element + Float,
166 D: Dimension,
167{
168 let zero = <T as Element>::zero();
169 masked_unary_domain(ma, T::log10, move |x| x > zero)
170}
171
172pub fn sqrt_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
177where
178 T: Element + Float,
179 D: Dimension,
180{
181 let zero = <T as Element>::zero();
182 masked_unary_domain(ma, T::sqrt, move |x| x >= zero)
183}
184
185pub fn arcsin_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
189where
190 T: Element + Float,
191 D: Dimension,
192{
193 let one = <T as Element>::one();
194 masked_unary_domain(ma, T::asin, move |x| x.abs() <= one)
195}
196
197pub fn arccos_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
201where
202 T: Element + Float,
203 D: Dimension,
204{
205 let one = <T as Element>::one();
206 masked_unary_domain(ma, T::acos, move |x| x.abs() <= one)
207}
208
209pub fn arccosh_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
213where
214 T: Element + Float,
215 D: Dimension,
216{
217 let one = <T as Element>::one();
218 masked_unary_domain(ma, T::acosh, move |x| x >= one)
219}
220
221pub fn arctanh_domain<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
225where
226 T: Element + Float,
227 D: Dimension,
228{
229 let one = <T as Element>::one();
230 masked_unary_domain(ma, T::atanh, move |x| x.abs() < one)
231}
232
233pub fn divide_domain<T, D>(
238 a: &MaskedArray<T, D>,
239 b: &MaskedArray<T, D>,
240) -> FerrayResult<MaskedArray<T, D>>
241where
242 T: Element + Float,
243 D: Dimension,
244{
245 let zero = <T as Element>::zero();
246 masked_binary_domain(a, b, |x, y| x / y, move |_x, y| y != zero, "divide_domain")
247}
248
249pub fn masked_unary<T, D, F>(ma: &MaskedArray<T, D>, f: F) -> FerrayResult<MaskedArray<T, D>>
274where
275 T: Element + Copy,
276 D: Dimension,
277 F: Fn(T) -> T,
278{
279 masked_unary_op(ma, f)
280}
281
282pub fn masked_binary<T, D, F>(
296 a: &MaskedArray<T, D>,
297 b: &MaskedArray<T, D>,
298 f: F,
299 op_name: &str,
300) -> FerrayResult<MaskedArray<T, D>>
301where
302 T: Element + Copy,
303 D: Dimension,
304 F: Fn(T, T) -> T,
305{
306 masked_binary_op(a, b, f, op_name)
307}
308
309pub fn sin<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
318where
319 T: Element + Float,
320 D: Dimension,
321{
322 masked_unary_op(ma, T::sin)
323}
324
325pub fn cos<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
330where
331 T: Element + Float,
332 D: Dimension,
333{
334 masked_unary_op(ma, T::cos)
335}
336
337pub fn tan<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
342where
343 T: Element + Float,
344 D: Dimension,
345{
346 masked_unary_op(ma, T::tan)
347}
348
349pub fn arcsin<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
354where
355 T: Element + Float,
356 D: Dimension,
357{
358 masked_unary_op(ma, T::asin)
359}
360
361pub fn arccos<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
366where
367 T: Element + Float,
368 D: Dimension,
369{
370 masked_unary_op(ma, T::acos)
371}
372
373pub fn arctan<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
378where
379 T: Element + Float,
380 D: Dimension,
381{
382 masked_unary_op(ma, T::atan)
383}
384
385pub fn exp<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
394where
395 T: Element + Float,
396 D: Dimension,
397{
398 masked_unary_op(ma, T::exp)
399}
400
401pub fn exp2<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
406where
407 T: Element + Float,
408 D: Dimension,
409{
410 masked_unary_op(ma, T::exp2)
411}
412
413pub fn log<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
418where
419 T: Element + Float,
420 D: Dimension,
421{
422 masked_unary_op(ma, T::ln)
423}
424
425pub fn log2<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
430where
431 T: Element + Float,
432 D: Dimension,
433{
434 masked_unary_op(ma, T::log2)
435}
436
437pub fn log10<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
442where
443 T: Element + Float,
444 D: Dimension,
445{
446 masked_unary_op(ma, T::log10)
447}
448
449pub fn floor<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
458where
459 T: Element + Float,
460 D: Dimension,
461{
462 masked_unary_op(ma, T::floor)
463}
464
465pub fn ceil<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
470where
471 T: Element + Float,
472 D: Dimension,
473{
474 masked_unary_op(ma, T::ceil)
475}
476
477pub fn sqrt<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
486where
487 T: Element + Float,
488 D: Dimension,
489{
490 masked_unary_op(ma, T::sqrt)
491}
492
493pub fn absolute<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
498where
499 T: Element + Float,
500 D: Dimension,
501{
502 masked_unary_op(ma, T::abs)
503}
504
505pub fn negative<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
510where
511 T: Element + Float,
512 D: Dimension,
513{
514 masked_unary_op(ma, T::neg)
515}
516
517pub fn reciprocal<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
522where
523 T: Element + Float,
524 D: Dimension,
525{
526 masked_unary_op(ma, T::recip)
527}
528
529pub fn square<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
534where
535 T: Element + Float,
536 D: Dimension,
537{
538 masked_unary_op(ma, |v| v * v)
539}
540
541pub fn sinh<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
543where
544 T: Element + Float,
545 D: Dimension,
546{
547 masked_unary_op(ma, T::sinh)
548}
549
550pub fn cosh<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
552where
553 T: Element + Float,
554 D: Dimension,
555{
556 masked_unary_op(ma, T::cosh)
557}
558
559pub fn tanh<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
561where
562 T: Element + Float,
563 D: Dimension,
564{
565 masked_unary_op(ma, T::tanh)
566}
567
568pub fn arcsinh<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
570where
571 T: Element + Float,
572 D: Dimension,
573{
574 masked_unary_op(ma, T::asinh)
575}
576
577pub fn arccosh<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
579where
580 T: Element + Float,
581 D: Dimension,
582{
583 masked_unary_op(ma, T::acosh)
584}
585
586pub fn arctanh<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
588where
589 T: Element + Float,
590 D: Dimension,
591{
592 masked_unary_op(ma, T::atanh)
593}
594
595pub fn log1p<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
597where
598 T: Element + Float,
599 D: Dimension,
600{
601 masked_unary_op(ma, T::ln_1p)
602}
603
604pub fn expm1<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
606where
607 T: Element + Float,
608 D: Dimension,
609{
610 masked_unary_op(ma, T::exp_m1)
611}
612
613pub fn trunc<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
615where
616 T: Element + Float,
617 D: Dimension,
618{
619 masked_unary_op(ma, T::trunc)
620}
621
622pub fn round<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
625where
626 T: Element + Float,
627 D: Dimension,
628{
629 masked_unary_op(ma, T::round)
630}
631
632pub fn sign<T, D>(ma: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
639where
640 T: Element + Float,
641 D: Dimension,
642{
643 let zero = <T as Element>::zero();
644 let one = <T as Element>::one();
645 masked_unary_op(ma, move |v| {
646 if v.is_nan() {
647 v
648 } else if v == zero {
649 zero
650 } else if v < zero {
651 -one
652 } else {
653 one
654 }
655 })
656}
657
658pub fn add<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
667where
668 T: Element + Float,
669 D: Dimension,
670{
671 masked_binary_op(a, b, |x, y| x + y, "add")
672}
673
674pub fn subtract<T, D>(
679 a: &MaskedArray<T, D>,
680 b: &MaskedArray<T, D>,
681) -> FerrayResult<MaskedArray<T, D>>
682where
683 T: Element + Float,
684 D: Dimension,
685{
686 masked_binary_op(a, b, |x, y| x - y, "subtract")
687}
688
689pub fn multiply<T, D>(
694 a: &MaskedArray<T, D>,
695 b: &MaskedArray<T, D>,
696) -> FerrayResult<MaskedArray<T, D>>
697where
698 T: Element + Float,
699 D: Dimension,
700{
701 masked_binary_op(a, b, |x, y| x * y, "multiply")
702}
703
704pub fn divide<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
709where
710 T: Element + Float,
711 D: Dimension,
712{
713 masked_binary_op(a, b, |x, y| x / y, "divide")
714}
715
716pub fn power<T, D>(a: &MaskedArray<T, D>, b: &MaskedArray<T, D>) -> FerrayResult<MaskedArray<T, D>>
721where
722 T: Element + Float,
723 D: Dimension,
724{
725 masked_binary_op(a, b, T::powf, "power")
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731 use ferray_core::Array;
732 use ferray_core::dimension::Ix1;
733
734 fn make_ma(data: Vec<f64>, mask: Vec<bool>) -> MaskedArray<f64, Ix1> {
735 let n = data.len();
736 let d = Array::<f64, Ix1>::from_vec(Ix1::new([n]), data).unwrap();
737 let m = Array::<bool, Ix1>::from_vec(Ix1::new([n]), mask).unwrap();
738 MaskedArray::new(d, m).unwrap()
739 }
740
741 #[test]
744 fn masked_unary_applies_closure_to_unmasked_only() {
745 let ma = make_ma(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
747 let r = masked_unary(&ma, |x| x.mul_add(10.0, 1.0)).unwrap();
748 let d: Vec<f64> = r.data().iter().copied().collect();
749 assert_eq!(d, vec![11.0, 0.0, 31.0, 41.0]);
751 let m: Vec<bool> = r.mask().iter().copied().collect();
753 assert_eq!(m, vec![false, true, false, false]);
754 }
755
756 #[test]
757 fn masked_unary_preserves_fill_value() {
758 let mut ma = make_ma(vec![1.0, 2.0, 3.0], vec![false, true, false]);
759 ma.set_fill_value(-99.0);
760 let r = masked_unary(&ma, f64::sqrt).unwrap();
761 let d: Vec<f64> = r.data().iter().copied().collect();
762 assert_eq!(d[1], -99.0);
763 assert_eq!(r.fill_value(), -99.0);
764 }
765
766 #[test]
767 fn masked_binary_mask_union_and_custom_closure() {
768 let a = make_ma(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
770 let b = make_ma(
771 vec![10.0, 20.0, 30.0, 40.0],
772 vec![false, false, true, false],
773 );
774 let r = masked_binary(&a, &b, |x, y| x.mul_add(2.0, y), "test_op").unwrap();
775 let d: Vec<f64> = r.data().iter().copied().collect();
776 assert_eq!(d[0], 12.0);
779 assert_eq!(d[3], 48.0);
780 let m: Vec<bool> = r.mask().iter().copied().collect();
782 assert_eq!(m, vec![false, true, true, false]);
783 }
784
785 #[test]
786 fn masked_binary_broadcasts_cross_shape() {
787 use ferray_core::dimension::Ix2;
789 let d1 = Array::<f64, Ix2>::from_vec(Ix2::new([2, 3]), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
790 .unwrap();
791 let m1 = Array::<bool, Ix2>::from_vec(Ix2::new([2, 3]), vec![false; 6]).unwrap();
792 let a = MaskedArray::new(d1, m1).unwrap();
793 let d2 = Array::<f64, Ix2>::from_vec(Ix2::new([1, 3]), vec![10.0, 20.0, 30.0]).unwrap();
794 let m2 = Array::<bool, Ix2>::from_vec(Ix2::new([1, 3]), vec![false; 3]).unwrap();
795 let b = MaskedArray::new(d2, m2).unwrap();
796 let r = masked_binary(&a, &b, |x, y| x + y, "add_broadcast").unwrap();
797 let d: Vec<f64> = r.data().iter().copied().collect();
798 assert_eq!(d, vec![11.0, 22.0, 33.0, 14.0, 25.0, 36.0]);
799 }
800
801 #[test]
804 fn sinh_cosh_tanh_skip_masked() {
805 let ma = make_ma(vec![0.0, 1.0, -1.0], vec![false, true, false]);
806 let sh = sinh(&ma).unwrap();
807 let ch = cosh(&ma).unwrap();
808 let th = tanh(&ma).unwrap();
809 let sd: Vec<f64> = sh.data().iter().copied().collect();
810 let cd: Vec<f64> = ch.data().iter().copied().collect();
811 let td: Vec<f64> = th.data().iter().copied().collect();
812 assert!((sd[0] - 0.0).abs() < 1e-12);
813 assert!((cd[0] - 1.0).abs() < 1e-12);
814 assert!((td[0] - 0.0).abs() < 1e-12);
815 assert_eq!(sd[1], 0.0);
817 assert_eq!(cd[1], 0.0);
818 assert_eq!(td[1], 0.0);
819 assert!((sd[2] - (-1.0_f64).sinh()).abs() < 1e-12);
820 }
821
822 #[test]
823 fn log1p_expm1_are_precise_near_zero() {
824 let ma = make_ma(vec![1e-15, 0.5, -0.5], vec![false, true, false]);
825 let l = log1p(&ma).unwrap();
826 let e = expm1(&ma).unwrap();
827 let ld: Vec<f64> = l.data().iter().copied().collect();
828 let ed: Vec<f64> = e.data().iter().copied().collect();
829 assert!((ld[0] - 1e-15_f64.ln_1p()).abs() < 1e-25);
831 assert!((ed[2] - (-0.5_f64).exp_m1()).abs() < 1e-12);
832 }
833
834 #[test]
835 fn trunc_round_sign_basic() {
836 let ma = make_ma(vec![1.7, -2.5, 0.0, -3.2], vec![false, false, false, false]);
837 let t = trunc(&ma).unwrap();
838 let r = round(&ma).unwrap();
839 let s = sign(&ma).unwrap();
840 let td: Vec<f64> = t.data().iter().copied().collect();
841 let rd: Vec<f64> = r.data().iter().copied().collect();
842 let sd: Vec<f64> = s.data().iter().copied().collect();
843 assert_eq!(td, vec![1.0, -2.0, 0.0, -3.0]);
844 assert_eq!(rd, vec![2.0, -3.0, 0.0, -3.0]);
847 assert_eq!(sd, vec![1.0, -1.0, 0.0, -1.0]);
848 }
849
850 #[test]
851 fn arcsinh_arccosh_arctanh_masked_positions_use_fill() {
852 let ma = make_ma(vec![0.0, 2.0, 0.5, -1.0], vec![false, true, false, true]);
853 let a = arcsinh(&ma).unwrap();
854 let ad: Vec<f64> = a.data().iter().copied().collect();
855 assert!((ad[0] - 0.0_f64.asinh()).abs() < 1e-12);
856 assert_eq!(ad[1], 0.0);
858 assert_eq!(ad[3], 0.0);
859
860 let ma2 = make_ma(vec![1.0, 2.0, 5.0], vec![false, false, false]);
862 let ac = arccosh(&ma2).unwrap();
863 let acd: Vec<f64> = ac.data().iter().copied().collect();
864 assert!((acd[0] - 0.0).abs() < 1e-12); assert!((acd[1] - 2.0_f64.acosh()).abs() < 1e-12);
866
867 let ma3 = make_ma(vec![0.0, 0.5, -0.5], vec![false, false, false]);
869 let at = arctanh(&ma3).unwrap();
870 let atd: Vec<f64> = at.data().iter().copied().collect();
871 assert!((atd[1] - 0.5_f64.atanh()).abs() < 1e-12);
872 }
873
874 #[test]
875 fn masked_unary_and_named_sin_agree() {
876 let ma = make_ma(vec![0.0, 1.0, 2.0, 3.0], vec![false, true, false, false]);
878 let via_named = sin(&ma).unwrap();
879 let via_generic = masked_unary(&ma, f64::sin).unwrap();
880 let vn: Vec<f64> = via_named.data().iter().copied().collect();
881 let vg: Vec<f64> = via_generic.data().iter().copied().collect();
882 assert_eq!(vn, vg);
883 let mn: Vec<bool> = via_named.mask().iter().copied().collect();
884 let mg: Vec<bool> = via_generic.mask().iter().copied().collect();
885 assert_eq!(mn, mg);
886 }
887
888 #[test]
889 fn masked_binary_and_named_add_agree() {
890 let a = make_ma(vec![1.0, 2.0, 3.0], vec![false, true, false]);
891 let b = make_ma(vec![10.0, 20.0, 30.0], vec![false, false, true]);
892 let via_named = add(&a, &b).unwrap();
893 let via_generic = masked_binary(&a, &b, |x, y| x + y, "add_generic").unwrap();
894 let vn: Vec<f64> = via_named.data().iter().copied().collect();
895 let vg: Vec<f64> = via_generic.data().iter().copied().collect();
896 assert_eq!(vn, vg);
897 }
898
899 #[test]
902 fn log_domain_masks_non_positive_inputs() {
903 let ma = make_ma(
905 vec![1.0, 2.0, -1.0, 0.0, 3.0],
906 vec![false, false, false, false, false],
907 );
908 let r = log_domain(&ma).unwrap();
909 let m: Vec<bool> = r.mask().iter().copied().collect();
910 assert_eq!(m, vec![false, false, true, true, false]);
912 let d: Vec<f64> = r.data().iter().copied().collect();
914 assert!((d[0] - 0.0).abs() < 1e-12);
915 assert!((d[1] - 2.0_f64.ln()).abs() < 1e-12);
916 assert!((d[4] - 3.0_f64.ln()).abs() < 1e-12);
917 assert_eq!(d[2], 0.0);
919 assert_eq!(d[3], 0.0);
920 }
921
922 #[test]
923 fn log_domain_preserves_existing_mask() {
924 let ma = make_ma(vec![1.0, 2.0, 5.0, -1.0], vec![false, true, false, false]);
926 let r = log_domain(&ma).unwrap();
927 let m: Vec<bool> = r.mask().iter().copied().collect();
928 assert_eq!(m, vec![false, true, false, true]);
929 }
930
931 #[test]
932 fn log_domain_vs_plain_log_on_negative_input() {
933 let ma = make_ma(vec![1.0, -2.0, 3.0], vec![false, false, false]);
936 let plain = log(&ma).unwrap();
937 let domain = log_domain(&ma).unwrap();
938 let pd: Vec<f64> = plain.data().iter().copied().collect();
939 let dd: Vec<f64> = domain.data().iter().copied().collect();
940 assert!(pd[1].is_nan());
942 assert_eq!(dd[1], 0.0);
944 assert!(domain.mask().as_slice().unwrap()[1]);
945 }
946
947 #[test]
948 fn sqrt_domain_masks_negative_inputs() {
949 let ma = make_ma(
950 vec![0.0, 1.0, 4.0, -9.0, -1e-10],
951 vec![false, false, false, false, false],
952 );
953 let r = sqrt_domain(&ma).unwrap();
954 let m: Vec<bool> = r.mask().iter().copied().collect();
955 assert_eq!(m, vec![false, false, false, true, true]);
957 let d: Vec<f64> = r.data().iter().copied().collect();
958 assert!((d[0] - 0.0).abs() < 1e-12);
959 assert!((d[1] - 1.0).abs() < 1e-12);
960 assert!((d[2] - 2.0).abs() < 1e-12);
961 }
962
963 #[test]
964 fn arcsin_domain_masks_out_of_range() {
965 let ma = make_ma(
966 vec![-1.5, -0.5, 0.0, 0.5, 1.5],
967 vec![false, false, false, false, false],
968 );
969 let r = arcsin_domain(&ma).unwrap();
970 let m: Vec<bool> = r.mask().iter().copied().collect();
971 assert_eq!(m, vec![true, false, false, false, true]);
973 let d: Vec<f64> = r.data().iter().copied().collect();
974 assert!((d[1] - (-0.5_f64).asin()).abs() < 1e-12);
975 assert!((d[2] - 0.0).abs() < 1e-12);
976 }
977
978 #[test]
979 fn arccos_domain_masks_out_of_range() {
980 let ma = make_ma(vec![-1.5, 0.0, 1.0, 2.0], vec![false, false, false, false]);
981 let r = arccos_domain(&ma).unwrap();
982 let m: Vec<bool> = r.mask().iter().copied().collect();
983 assert_eq!(m, vec![true, false, false, true]);
984 }
985
986 #[test]
987 fn arccosh_domain_masks_below_one() {
988 let ma = make_ma(vec![0.5, 1.0, 2.0, 10.0], vec![false, false, false, false]);
990 let r = arccosh_domain(&ma).unwrap();
991 let m: Vec<bool> = r.mask().iter().copied().collect();
992 assert_eq!(m, vec![true, false, false, false]);
993 let d: Vec<f64> = r.data().iter().copied().collect();
994 assert!((d[1] - 0.0).abs() < 1e-12); assert!((d[2] - 2.0_f64.acosh()).abs() < 1e-12);
996 }
997
998 #[test]
999 fn arctanh_domain_masks_boundary_and_beyond() {
1000 let ma = make_ma(
1002 vec![-1.0, -0.5, 0.0, 0.5, 1.0],
1003 vec![false, false, false, false, false],
1004 );
1005 let r = arctanh_domain(&ma).unwrap();
1006 let m: Vec<bool> = r.mask().iter().copied().collect();
1007 assert_eq!(m, vec![true, false, false, false, true]);
1008 }
1009
1010 #[test]
1011 fn divide_domain_masks_zero_denominators() {
1012 let num = make_ma(vec![1.0, 2.0, 3.0, 4.0], vec![false, false, false, false]);
1013 let den = make_ma(vec![2.0, 0.0, 1.0, 0.0], vec![false, false, false, false]);
1014 let r = divide_domain(&num, &den).unwrap();
1015 let m: Vec<bool> = r.mask().iter().copied().collect();
1016 assert_eq!(m, vec![false, true, false, true]);
1018 let d: Vec<f64> = r.data().iter().copied().collect();
1019 assert!((d[0] - 0.5).abs() < 1e-12);
1020 assert!((d[2] - 3.0).abs() < 1e-12);
1021 }
1022
1023 #[test]
1024 fn divide_domain_preserves_numerator_and_denominator_masks() {
1025 let num = make_ma(vec![1.0, 2.0, 3.0, 4.0], vec![false, true, false, false]);
1026 let den = make_ma(vec![2.0, 5.0, 0.0, 4.0], vec![false, false, false, true]);
1027 let r = divide_domain(&num, &den).unwrap();
1028 let m: Vec<bool> = r.mask().iter().copied().collect();
1029 assert_eq!(m, vec![false, true, true, true]);
1031 }
1032
1033 #[test]
1034 fn masked_unary_domain_generic_path() {
1035 let ma = make_ma(
1037 vec![1.0, 2.0, 3.0, 4.0, 5.0],
1038 vec![false, false, false, false, false],
1039 );
1040 let r = masked_unary_domain(&ma, |x| x * 2.0, |x| (x as i32) % 2 == 0).unwrap();
1041 let m: Vec<bool> = r.mask().iter().copied().collect();
1042 assert_eq!(m, vec![true, false, true, false, true]);
1043 let d: Vec<f64> = r.data().iter().copied().collect();
1044 assert_eq!(d[1], 4.0);
1045 assert_eq!(d[3], 8.0);
1046 }
1047
1048 #[test]
1049 fn masked_binary_domain_rejects_mismatched_shapes() {
1050 let a = make_ma(vec![1.0, 2.0], vec![false, false]);
1051 let b = make_ma(vec![1.0, 2.0, 3.0], vec![false, false, false]);
1052 assert!(divide_domain(&a, &b).is_err());
1053 }
1054
1055 #[test]
1056 fn log_domain_with_custom_fill_value() {
1057 let mut ma = make_ma(vec![1.0, -1.0, 2.0], vec![false, false, false]);
1058 ma.set_fill_value(-999.0);
1059 let r = log_domain(&ma).unwrap();
1060 let d: Vec<f64> = r.data().iter().copied().collect();
1061 assert_eq!(d[1], -999.0);
1063 assert_eq!(r.fill_value(), -999.0);
1064 }
1065}