Skip to main content

ferray_ufunc/ops/
logical.rs

1// ferray-ufunc: Logical functions
2//
3// logical_and, logical_or, logical_xor, logical_not, all, any
4
5use ferray_core::Array;
6use ferray_core::dimension::Dimension;
7use ferray_core::dtype::Element;
8use ferray_core::error::FerrayResult;
9
10use crate::helpers::{binary_map_op, unary_map_op};
11
12/// Trait for types that can be interpreted as boolean for logical ops.
13pub trait Logical {
14    /// Return true if the value is "truthy" (nonzero).
15    fn is_truthy(&self) -> bool;
16}
17
18impl Logical for bool {
19    #[inline]
20    fn is_truthy(&self) -> bool {
21        *self
22    }
23}
24
25macro_rules! impl_logical_numeric {
26    ($($ty:ty),*) => {
27        $(
28            impl Logical for $ty {
29                #[inline]
30                fn is_truthy(&self) -> bool {
31                    *self != 0 as $ty
32                }
33            }
34        )*
35    };
36}
37
38impl_logical_numeric!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
39
40impl Logical for f32 {
41    #[inline]
42    fn is_truthy(&self) -> bool {
43        *self != 0.0
44    }
45}
46
47impl Logical for f64 {
48    #[inline]
49    fn is_truthy(&self) -> bool {
50        *self != 0.0
51    }
52}
53
54impl Logical for num_complex::Complex<f32> {
55    #[inline]
56    fn is_truthy(&self) -> bool {
57        self.re != 0.0 || self.im != 0.0
58    }
59}
60
61impl Logical for num_complex::Complex<f64> {
62    #[inline]
63    fn is_truthy(&self) -> bool {
64        self.re != 0.0 || self.im != 0.0
65    }
66}
67
68/// Elementwise logical AND.
69pub fn logical_and<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<bool, D>>
70where
71    T: Element + Logical + Copy,
72    D: Dimension,
73{
74    binary_map_op(a, b, |x, y| x.is_truthy() && y.is_truthy())
75}
76
77/// Elementwise logical OR.
78pub fn logical_or<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<bool, D>>
79where
80    T: Element + Logical + Copy,
81    D: Dimension,
82{
83    binary_map_op(a, b, |x, y| x.is_truthy() || y.is_truthy())
84}
85
86/// Elementwise logical XOR.
87pub fn logical_xor<T, D>(a: &Array<T, D>, b: &Array<T, D>) -> FerrayResult<Array<bool, D>>
88where
89    T: Element + Logical + Copy,
90    D: Dimension,
91{
92    binary_map_op(a, b, |x, y| x.is_truthy() ^ y.is_truthy())
93}
94
95/// Elementwise logical NOT.
96pub fn logical_not<T, D>(input: &Array<T, D>) -> FerrayResult<Array<bool, D>>
97where
98    T: Element + Logical + Copy,
99    D: Dimension,
100{
101    unary_map_op(input, |x| !x.is_truthy())
102}
103
104/// Test whether all elements are truthy.
105pub fn all<T, D>(input: &Array<T, D>) -> bool
106where
107    T: Element + Logical,
108    D: Dimension,
109{
110    input.iter().all(|x| x.is_truthy())
111}
112
113/// Test whether any element is truthy.
114pub fn any<T, D>(input: &Array<T, D>) -> bool
115where
116    T: Element + Logical,
117    D: Dimension,
118{
119    input.iter().any(|x| x.is_truthy())
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use ferray_core::dimension::Ix1;
126
127    fn arr1_bool(data: Vec<bool>) -> Array<bool, Ix1> {
128        let n = data.len();
129        Array::from_vec(Ix1::new([n]), data).unwrap()
130    }
131
132    fn arr1_i32(data: Vec<i32>) -> Array<i32, Ix1> {
133        let n = data.len();
134        Array::from_vec(Ix1::new([n]), data).unwrap()
135    }
136
137    #[test]
138    fn test_logical_and() {
139        let a = arr1_bool(vec![true, true, false, false]);
140        let b = arr1_bool(vec![true, false, true, false]);
141        let r = logical_and(&a, &b).unwrap();
142        assert_eq!(r.as_slice().unwrap(), &[true, false, false, false]);
143    }
144
145    #[test]
146    fn test_logical_or() {
147        let a = arr1_bool(vec![true, true, false, false]);
148        let b = arr1_bool(vec![true, false, true, false]);
149        let r = logical_or(&a, &b).unwrap();
150        assert_eq!(r.as_slice().unwrap(), &[true, true, true, false]);
151    }
152
153    #[test]
154    fn test_logical_xor() {
155        let a = arr1_bool(vec![true, true, false, false]);
156        let b = arr1_bool(vec![true, false, true, false]);
157        let r = logical_xor(&a, &b).unwrap();
158        assert_eq!(r.as_slice().unwrap(), &[false, true, true, false]);
159    }
160
161    #[test]
162    fn test_logical_not() {
163        let a = arr1_bool(vec![true, false, true]);
164        let r = logical_not(&a).unwrap();
165        assert_eq!(r.as_slice().unwrap(), &[false, true, false]);
166    }
167
168    #[test]
169    fn test_logical_and_numeric() {
170        let a = arr1_i32(vec![1, 1, 0, 0]);
171        let b = arr1_i32(vec![1, 0, 1, 0]);
172        let r = logical_and(&a, &b).unwrap();
173        assert_eq!(r.as_slice().unwrap(), &[true, false, false, false]);
174    }
175
176    #[test]
177    fn test_all() {
178        let a = arr1_bool(vec![true, true, true]);
179        assert!(all(&a));
180        let b = arr1_bool(vec![true, false, true]);
181        assert!(!all(&b));
182    }
183
184    #[test]
185    fn test_any() {
186        let a = arr1_bool(vec![false, false, true]);
187        assert!(any(&a));
188        let b = arr1_bool(vec![false, false, false]);
189        assert!(!any(&b));
190    }
191
192    #[test]
193    fn test_all_numeric() {
194        let a = arr1_i32(vec![1, 2, 3]);
195        assert!(all(&a));
196        let b = arr1_i32(vec![1, 0, 3]);
197        assert!(!all(&b));
198    }
199}