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