Skip to main content

burn_tensor/tensor/api/
bool.rs

1use crate::{Bool, Cast, Device, Int, Shape, Tensor, TensorData, ops::BridgeTensor};
2use alloc::{vec, vec::Vec};
3use burn_backend::ops::BoolTensorOps;
4use burn_dispatch::Dispatch;
5
6use crate::try_read_sync;
7
8/// The part of the tensor to keep when creating a triangular mask.
9enum TriPart {
10    /// Upper triangular part.
11    Upper,
12
13    /// Lower triangular part.
14    Lower,
15
16    /// Diagonal part.
17    Diagonal,
18}
19
20impl<const D: usize> Tensor<D, Bool> {
21    /// Create a boolean tensor from data on the given device.
22    ///
23    /// # Arguments
24    ///
25    /// * `data` - The tensor data.
26    /// * `device` - The device on which the tensor will be allocated.
27    ///
28    /// # Returns
29    ///
30    /// A boolean tensor.
31    ///
32    /// # Example
33    ///
34    /// ```rust
35    /// use burn_tensor::{Tensor, Bool};
36    ///
37    /// let device = Default::default();
38    /// let tensor = Tensor::<2, Bool>::from_bool([[true, false], [false, true]], &device);
39    /// println!("{tensor}");
40    /// ```
41    pub fn from_bool<A: Into<TensorData>>(data: A, device: &Device) -> Self {
42        Self::from_data(data.into(), device)
43    }
44
45    /// Convert the bool tensor into an int tensor.
46    ///
47    /// # Returns
48    ///
49    /// An integer tensor where `true` is converted to `1` and `false` to `0`.
50    ///
51    /// # Example
52    ///
53    /// ```rust
54    /// use burn_tensor::{Tensor, Bool};
55    ///
56    /// let device = Default::default();
57    /// let bool_tensor = Tensor::<1, Bool>::from_bool([true, false, true], &device);
58    /// let int_tensor = bool_tensor.int();
59    /// println!("{int_tensor}"); // [1, 0, 1]
60    /// ```
61    pub fn int(self) -> Tensor<D, Int> {
62        let device = self.device();
63        Tensor::new(bool_to_int_impl(self.primitive, device))
64    }
65
66    /// Convert the bool tensor into a float tensor.
67    ///
68    /// # Returns
69    ///
70    /// A float tensor where `true` is converted to `1.0` and `false` to `0.0`.
71    ///
72    /// # Example
73    ///
74    /// ```rust
75    /// use burn_tensor::{Tensor, Bool};
76    ///
77    /// let device = Default::default();
78    /// let bool_tensor = Tensor::<1, Bool>::from_bool([true, false, true], &device);
79    /// let float_tensor = bool_tensor.float();
80    /// println!("{float_tensor}"); // [1.0, 0.0, 1.0]
81    /// ```
82    pub fn float(self) -> Tensor<D> {
83        let device = self.device();
84        Tensor::new(bool_to_float_impl(self.primitive, device))
85    }
86
87    /// Converts a bool tensor to the specified data type.
88    ///
89    /// Supports casting to [`IntDType`](crate::IntDType) (producing an int tensor)
90    /// or [`FloatDType`](crate::FloatDType) (producing a float tensor).
91    ///
92    /// # Example
93    ///
94    /// ```rust
95    /// use burn_tensor::{Tensor, Bool, IntDType, FloatDType};
96    ///
97    /// let device = Default::default();
98    /// let bool_tensor = Tensor::<1, Bool>::from_bool([true, false, true], &device);
99    ///
100    /// // Cast to int
101    /// let int_tensor = bool_tensor.clone().cast(IntDType::I64);
102    ///
103    /// // Cast to float
104    /// let float_tensor = bool_tensor.cast(FloatDType::F32);
105    /// ```
106    #[must_use]
107    pub fn cast<T: Cast<D, Bool>>(self, dtype: T) -> Tensor<D, T::OutputKind> {
108        T::cast(self, dtype)
109    }
110
111    /// Inverses boolean values.
112    ///
113    /// # Example
114    ///
115    /// ```rust
116    /// use burn_tensor::{Tensor, Bool};
117    ///
118    /// let device = Default::default();
119    /// let tensor = Tensor::<2, Bool>::from_bool([[true, false], [false, true]], &device);
120    /// let inverted = tensor.bool_not();
121    /// println!("{inverted}"); // [[false, true], [true, false]]
122    /// ```
123    pub fn bool_not(self) -> Self {
124        Tensor::new(bool_not_impl(self.primitive))
125    }
126
127    /// Performs logical and (`&&`) on two boolean tensors.
128    ///
129    /// # Arguments
130    ///
131    /// * `rhs` - The right-hand side tensor for the AND operation.
132    ///
133    /// # Returns
134    ///
135    /// A boolean tensor where each element is the result of `self[i] && rhs[i]`.
136    ///
137    /// # Example
138    ///
139    /// ```rust
140    /// use burn_tensor::{Tensor, Bool};
141    ///
142    /// let device = Default::default();
143    /// let a = Tensor::<2, Bool>::from_bool([[true, true], [false, false]], &device);
144    /// let b = Tensor::<2, Bool>::from_bool([[true, false], [true, false]], &device);
145    /// let result = a.bool_and(b);
146    /// println!("{result}"); // [[true, false], [false, false]]
147    /// ```
148    pub fn bool_and(self, rhs: Tensor<D, Bool>) -> Tensor<D, Bool> {
149        Tensor::new(bool_and_impl(self.primitive, rhs.primitive))
150    }
151
152    /// Performs logical or (`||`) on two boolean tensors.
153    ///
154    /// # Arguments
155    ///
156    /// * `rhs` - The right-hand side tensor for the OR operation.
157    ///
158    /// # Returns
159    ///
160    /// A boolean tensor where each element is the result of `self[i] || rhs[i]`.
161    ///
162    /// # Example
163    ///
164    /// ```rust
165    /// use burn_tensor::{Tensor, Bool};
166    ///
167    /// let device = Default::default();
168    /// let a = Tensor::<2, Bool>::from_bool([[true, true], [false, false]], &device);
169    /// let b = Tensor::<2, Bool>::from_bool([[true, false], [true, false]], &device);
170    /// let result = a.bool_or(b);
171    /// println!("{result}"); // [[true, true], [true, false]]
172    /// ```
173    pub fn bool_or(self, rhs: Tensor<D, Bool>) -> Tensor<D, Bool> {
174        Tensor::new(bool_or_impl(self.primitive, rhs.primitive))
175    }
176
177    /// Performs logical xor (`^`) on two boolean tensors.
178    ///
179    /// # Arguments
180    ///
181    /// * `rhs` - The right-hand side tensor for the XOR operation.
182    ///
183    /// # Returns
184    ///
185    /// A boolean tensor where each element is the result of `self[i] ^ rhs[i]`.
186    /// Returns `true` when exactly one of the operands is `true`.
187    ///
188    /// # Example
189    ///
190    /// ```rust
191    /// use burn_tensor::{Tensor, Bool};
192    ///
193    /// let device = Default::default();
194    /// let a = Tensor::<2, Bool>::from_bool([[true, true], [false, false]], &device);
195    /// let b = Tensor::<2, Bool>::from_bool([[true, false], [true, false]], &device);
196    /// let result = a.bool_xor(b);
197    /// println!("{result}"); // [[false, true], [true, false]]
198    /// ```
199    pub fn bool_xor(self, rhs: Tensor<D, Bool>) -> Tensor<D, Bool> {
200        Tensor::new(bool_xor_impl(self.primitive, rhs.primitive))
201    }
202
203    /// Compute the indices of `true` elements in the tensor (i.e., non-zero for boolean tensors).
204    ///
205    /// # Returns
206    ///
207    /// A vector of tensors, one for each dimension of the given tensor, containing the indices of
208    /// the non-zero elements in that dimension.
209    ///
210    /// # Example
211    ///
212    /// ```rust
213    /// use burn_tensor::{Tensor, Bool};
214    ///
215    /// let device = Default::default();
216    /// let tensor = Tensor::<2, Bool>::from_bool(
217    ///     [[true, false, true], [false, true, false], [false, true, false]],
218    ///     &device,
219    /// );
220    /// let indices = tensor.nonzero();
221    /// println!("{}", indices[0]); // [0, 0, 1, 2]
222    /// println!("{}", indices[1]); // [0, 2, 1, 1]
223    /// ```
224    pub fn nonzero(self) -> Vec<Tensor<1, Int>> {
225        try_read_sync(self.nonzero_async())
226            .expect("Failed to read tensor data synchronously. Try using nonzero_async instead.")
227    }
228
229    /// Compute the indices of `true` elements in the tensor (i.e., non-zero for boolean tensors).
230    ///
231    /// # Returns
232    ///
233    /// A vector of tensors, one for each dimension of the given tensor, containing the indices of
234    /// the non-zero elements in that dimension.
235    pub async fn nonzero_async(self) -> Vec<Tensor<1, Int>> {
236        let indices = self.argwhere_async().await;
237
238        if indices.shape().num_elements() == 0 {
239            // Return empty vec when all elements are zero
240            return vec![];
241        }
242
243        let dims = indices.shape();
244        indices
245            .chunk(dims[1], 1)
246            .into_iter()
247            .map(|t| t.reshape(Shape::new([dims[0]])))
248            .collect()
249    }
250
251    /// Compute the indices of the elements that are true, grouped by element.
252    ///
253    /// # Returns
254    ///
255    /// A tensor containing the indices of all non-zero elements of the given tensor. Each row in the
256    /// result contains the indices of a non-zero element.
257    ///
258    /// # Example
259    ///
260    /// ```rust
261    /// use burn_tensor::{Tensor, Bool};
262    ///
263    /// let device = Default::default();
264    /// let tensor = Tensor::<2, Bool>::from_bool(
265    ///     [[true, false, true], [false, true, false], [false, true, false]],
266    ///     &device,
267    /// );
268    /// let indices = tensor.argwhere();
269    /// println!("{indices}"); // [[0, 0], [0, 2], [1, 1], [2, 1]]
270    /// ```
271    pub fn argwhere(self) -> Tensor<2, Int> {
272        try_read_sync(self.argwhere_async())
273            .expect("Failed to read tensor data synchronously. Try using argwhere_async instead.")
274    }
275
276    /// Compute the indices of the elements that are true, grouped by element.
277    ///
278    /// # Returns
279    ///
280    /// A tensor containing the indices of all non-zero elements of the given tensor. Each row in the
281    /// result contains the indices of a non-zero element.
282    pub async fn argwhere_async(self) -> Tensor<2, Int> {
283        let out_dtype = self.device().settings().int_dtype;
284        let inner = Dispatch::bool_argwhere(self.primitive.into(), out_dtype).await;
285        Tensor::new(BridgeTensor::int(inner))
286    }
287
288    /// Creates a mask for the upper, lower triangle, or diagonal of a matrix, which can be used to
289    /// fill the specified area with a value.
290    fn tri_mask<S: Into<Shape>>(shape: S, tri_part: TriPart, offset: i64, device: &Device) -> Self {
291        let shape: Shape = shape.into();
292        let height = shape[D - 2];
293        let width = shape[D - 1];
294
295        // Generate row and column index tensors.
296        let row_indices: Tensor<1, Int> = Tensor::arange(0..height as i64, device);
297        let col_indices: Tensor<1, Int> = Tensor::arange(0..width as i64, device);
298
299        // Prepare shapes for broadcasting.
300        let mut row_shape = [1; D];
301        row_shape[D - 2] = height;
302        let mut col_shape = [1; D];
303        col_shape[D - 1] = width;
304
305        // Reshape for broadcasting.
306        let row_broadcast: Tensor<D, Int> = row_indices.reshape(Shape::new(row_shape));
307        let col_broadcast = col_indices.reshape(Shape::new(col_shape));
308
309        // Broadcasting trick to create a matrix that facilitates comparison for mask generation.
310        let matrix = row_broadcast.clone() - (col_broadcast.clone() - offset);
311
312        // Select the appropriate comparison function based on `tri_part`.
313        let compare = match tri_part {
314            TriPart::Upper => Tensor::greater_scalar,
315            TriPart::Lower => Tensor::lower_scalar,
316            TriPart::Diagonal => Tensor::not_equal_scalar,
317        };
318
319        // Generate and return the mask by applying the comparison to the matrix.
320        compare(matrix, 0).unsqueeze()
321    }
322
323    /// Creates a mask for the upper triangle of a matrix, which can be used to fill the specified
324    /// area with a value.
325    ///
326    /// This function generates a boolean tensor representing the mask of the upper triangle of a matrix.
327    ///
328    /// # Arguments
329    ///
330    /// * `shape`: The shape of the matrix.
331    /// * `offset`: The offset from the diagonal, where 0 means the diagonal, and positive values shift
332    ///   towards the upper triangle.
333    /// * `device`: The device on which the tensor will be allocated.
334    ///
335    /// # Returns
336    ///
337    /// Returns a boolean tensor where `false` indicates the elements of the matrix that are part of the
338    /// upper triangle taking into account the specified `offset`. All other elements are `true`.
339    ///
340    /// # Example
341    /// ```rust
342    /// use burn_tensor::{Tensor, Bool};
343    ///
344    /// let mask = Tensor::<2, Bool>::triu_mask([3, 3], 0, &Default::default());
345    /// println!("{mask}");
346    /// // [[false, false, false],
347    /// //  [true, false, false],
348    /// //  [true, true, false]]
349    /// ```
350    pub fn triu_mask<S: Into<Shape>>(shape: S, offset: i64, device: &Device) -> Self {
351        Self::tri_mask(shape, TriPart::Upper, offset, device)
352    }
353
354    /// Creates a mask for the lower triangle of a matrix, which can be used to fill the specified
355    /// area with a value.
356    ///
357    /// This function generates a boolean tensor representing the mask of the lower triangle of a matrix.
358    ///
359    /// # Arguments
360    ///
361    /// * `shape`: The shape of the matrix.
362    /// * `offset`: The offset from the diagonal, where 0 means the diagonal, and negative values shift
363    ///   towards the lower triangle.
364    /// * `device`: The device on which the tensor will be allocated.
365    ///
366    /// # Returns
367    ///
368    /// Returns a boolean tensor where `false` indicates the elements of the matrix that are part of the
369    /// lower triangle taking into account the specified `offset`. All other elements are `true`.
370    ///
371    /// # Example
372    /// ```rust
373    /// use burn_tensor::{Tensor, Bool};
374    ///
375    /// let mask = Tensor::<2, Bool>::tril_mask([3, 3], 0, &Default::default());
376    /// println!("{mask}");
377    /// // [[false, true, true],
378    /// //  [false, false, true],
379    /// //  [false, false, false]]
380    /// ```
381    pub fn tril_mask<S: Into<Shape>>(shape: S, offset: i64, device: &Device) -> Self {
382        Self::tri_mask(shape, TriPart::Lower, offset, device)
383    }
384
385    /// Creates a mask for the diagonal of a matrix, which can be used to fill the specified
386    /// area with a value.
387    ///
388    /// This function generates a boolean tensor representing the mask of the diagonal of a matrix.
389    ///
390    /// # Arguments
391    ///
392    /// * `shape`: The shape of the matrix.
393    /// * `offset`: The offset from the diagonal, where 0 means the diagonal, and positive values shift
394    ///   towards the upper triangle.
395    /// * `device`: The device on which the tensor will be allocated.
396    ///
397    /// # Returns
398    ///
399    /// Returns a boolean tensor where `false` indicates the elements of the matrix that are part of the
400    /// diagonal. All other elements are `true`.
401    ///
402    /// # Example
403    /// ```rust
404    /// use burn_tensor::{Tensor, Bool};
405    ///
406    /// let mask = Tensor::<2, Bool>::diag_mask([3, 3], 0, &Default::default());
407    /// println!("{mask}");
408    /// // [[false, true, true],
409    /// //  [true, false, true],
410    /// //  [true, true, false]]
411    /// ```
412    pub fn diag_mask<S: Into<Shape>>(shape: S, offset: i64, device: &Device) -> Self {
413        Self::tri_mask(shape, TriPart::Diagonal, offset, device)
414    }
415}
416
417// !tensor (bool only)
418impl<const D: usize> core::ops::Not for Tensor<D, Bool> {
419    type Output = Tensor<D, Bool>;
420
421    fn not(self) -> Self::Output {
422        self.bool_not()
423    }
424}
425
426// tensor & tensor (bool only)
427impl<const D: usize> core::ops::BitAnd for Tensor<D, Bool> {
428    type Output = Tensor<D, Bool>;
429
430    fn bitand(self, tensor: Tensor<D, Bool>) -> Self::Output {
431        self.bool_and(tensor)
432    }
433}
434
435// tensor | tensor (bool only)
436impl<const D: usize> core::ops::BitOr for Tensor<D, Bool> {
437    type Output = Tensor<D, Bool>;
438
439    fn bitor(self, tensor: Tensor<D, Bool>) -> Self::Output {
440        self.bool_or(tensor)
441    }
442}
443
444// tensor ^ tensor (bool only)
445impl<const D: usize> core::ops::BitXor for Tensor<D, Bool> {
446    type Output = Tensor<D, Bool>;
447
448    fn bitxor(self, tensor: Tensor<D, Bool>) -> Self::Output {
449        self.bool_xor(tensor)
450    }
451}
452
453// =========================================================================
454// Non-generic implementation helpers (outlined from the generic API).
455// See the crate-level docs for the rationale behind this pattern.
456// =========================================================================
457
458fn bool_to_int_impl(p: BridgeTensor, device: Device) -> BridgeTensor {
459    let out_dtype = device.settings().int_dtype;
460    BridgeTensor::int(Dispatch::bool_into_int(p.into(), out_dtype))
461}
462
463fn bool_to_float_impl(p: BridgeTensor, device: Device) -> BridgeTensor {
464    let out_dtype = device.settings().float_dtype;
465    BridgeTensor::float(Dispatch::bool_into_float(p.into(), out_dtype))
466}
467
468fn bool_not_impl(p: BridgeTensor) -> BridgeTensor {
469    BridgeTensor::bool(Dispatch::bool_not(p.into()))
470}
471
472fn bool_and_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
473    BridgeTensor::bool(Dispatch::bool_and(lhs.into(), rhs.into()))
474}
475
476fn bool_or_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
477    BridgeTensor::bool(Dispatch::bool_or(lhs.into(), rhs.into()))
478}
479
480fn bool_xor_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
481    BridgeTensor::bool(Dispatch::bool_xor(lhs.into(), rhs.into()))
482}