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}