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}