Skip to main content

burn_backend/backend/ops/
bool_tensor.rs

1use super::{
2    argwhere::argwhere_data, cat::cat_with_slice_assign, repeat_dim::repeat_with_slice_assign,
3};
4use crate::tensor::{BoolTensor, Device, FloatTensor, IntTensor};
5use crate::{Backend, TensorData, TensorMetadata, get_device_settings};
6use crate::{ExecutionError, Scalar};
7use alloc::vec::Vec;
8use burn_std::{BoolDType, FloatDType, IntDType, Shape, Slice};
9use core::future::Future;
10
11/// Bool Tensor API for basic operations, see
12#[cfg_attr(doc, doc = crate::doc_tensor!())]
13#[cfg_attr(not(doc), doc = "`Tensor`")]
14/// for documentation on each function.
15pub trait BoolTensorOps<B: Backend> {
16    /// Creates a new bool tensor.
17    ///
18    /// # Arguments
19    ///
20    /// * `shape` - The shape of the tensor.
21    /// * `device` - The device to create the tensor on.
22    /// * `dtype` - The target data type.
23    ///
24    /// # Returns
25    ///
26    /// The boolean tensor with the given shape.
27    fn bool_empty(shape: Shape, device: &Device<B>, dtype: BoolDType) -> BoolTensor<B>;
28
29    /// Creates a new bool tensor filled false.
30    ///
31    /// # Arguments
32    ///
33    /// * `shape` - The shape of the tensor.
34    /// * `device` - The device to create the tensor on.
35    /// * `dtype` - The target data type.
36    ///
37    /// # Returns
38    ///
39    /// The boolean tensor filled with false.
40    fn bool_zeros(shape: Shape, device: &Device<B>, dtype: BoolDType) -> BoolTensor<B>;
41
42    /// Creates a new bool tensor filled true.
43    ///
44    /// # Arguments
45    ///
46    /// * `shape` - The shape of the tensor.
47    /// * `device` - The device to create the tensor on.
48    /// * `dtype` - The target data type.
49    ///
50    /// # Returns
51    ///
52    /// The boolean tensor filled with true.
53    fn bool_ones(shape: Shape, device: &Device<B>, dtype: BoolDType) -> BoolTensor<B>;
54
55    /// Converts the tensor to a data structure.
56    ///
57    /// # Arguments
58    ///
59    /// * `tensor` - The tensor.
60    ///
61    /// # Returns
62    ///
63    /// The data structure with the tensor's data.
64    fn bool_into_data(
65        tensor: BoolTensor<B>,
66    ) -> impl Future<Output = Result<TensorData, ExecutionError>> + Send;
67
68    /// Creates a tensor from the data structure.
69    ///
70    /// # Arguments
71    ///
72    /// * `data` - The data structure.
73    /// * `device` - The device to create the tensor on.
74    ///
75    /// # Returns
76    ///
77    /// The tensor with the data.
78    fn bool_from_data(data: TensorData, device: &Device<B>) -> BoolTensor<B>;
79
80    /// Converts bool tensor to int tensor.
81    ///
82    /// # Arguments
83    ///
84    /// * `tensor` - The tensor.
85    /// * `out_dtype` - The output tensor dtype.
86    ///
87    /// # Returns
88    ///
89    /// The int tensor with the same data as the bool tensor.
90    fn bool_into_int(tensor: BoolTensor<B>, out_dtype: IntDType) -> IntTensor<B>;
91
92    /// Converts bool tensor to float tensor.
93    ///
94    /// # Arguments
95    ///
96    /// * `tensor` - The tensor.
97    /// * `out_dtype` - The output tensor dtype.
98    ///
99    /// # Returns
100    ///
101    /// The float tensor with the same data as the bool tensor.
102    fn bool_into_float(tensor: BoolTensor<B>, out_dtype: FloatDType) -> FloatTensor<B>;
103
104    /// Moves the tensor to the device.
105    fn bool_to_device(tensor: BoolTensor<B>, device: &Device<B>) -> BoolTensor<B>;
106
107    /// Reshapes the tensor.
108    ///
109    /// # Arguments
110    ///
111    /// * `tensor` - The tensor.
112    /// * `shape` - The new shape.
113    ///
114    /// # Returns
115    ///
116    /// The tensor with the new shape.
117    fn bool_reshape(tensor: BoolTensor<B>, shape: Shape) -> BoolTensor<B>;
118
119    /// Gets the values from the tensor for the given ranges.
120    ///
121    /// # Arguments
122    ///
123    /// * `tensor` - The tensor.
124    /// * `slices` - The slices specifying ranges and steps for each dimension.
125    ///
126    /// # Returns
127    ///
128    /// The tensor with the values for the given slices.
129    ///
130    /// # Note
131    ///
132    /// Empty slices (where start >= end) are handled at the high-level tensor API and will not
133    /// be passed to this method. Backend implementations do not need to handle empty slices.
134    fn bool_slice(tensor: BoolTensor<B>, slices: &[Slice]) -> BoolTensor<B>;
135
136    /// Sets the values in the tensor for the given ranges.
137    ///
138    /// # Arguments
139    ///
140    /// * `tensor` - The tensor.
141    /// * `ranges` - The ranges to set the values for.
142    /// * `value` - The values to set.
143    ///
144    /// # Returns
145    ///
146    /// The tensor with the values set for the given ranges.
147    ///
148    /// # Note
149    ///
150    /// Empty slice assignments (where any slice range produces 0 elements) are handled at the
151    /// high-level tensor API and will not be passed to this method. Backend implementations do
152    /// not need to handle empty slice assignments.
153    fn bool_slice_assign(
154        tensor: BoolTensor<B>,
155        slices: &[Slice],
156        value: BoolTensor<B>,
157    ) -> BoolTensor<B>;
158
159    /// Fills the tensor with values from the value tensor if the mask is true at the given
160    /// indices.
161    ///
162    /// # Arguments
163    ///
164    /// * `tensor` - The tensor.
165    /// * `mask` - The mask.
166    /// * `value` - The value tensor.
167    ///
168    /// # Returns
169    ///
170    /// The tensor with the values filled.
171    fn bool_mask_where(
172        tensor: BoolTensor<B>,
173        mask: BoolTensor<B>,
174        value: BoolTensor<B>,
175    ) -> BoolTensor<B>;
176
177    /// Fills the tensor with the given value if the mask is true at the given indices.
178    ///
179    /// # Arguments
180    ///
181    /// * `tensor` - The tensor.
182    /// * `mask` - The mask.
183    /// * `value` - The value.
184    ///
185    /// # Returns
186    ///
187    /// The tensor with the values filled.
188    fn bool_mask_fill(tensor: BoolTensor<B>, mask: BoolTensor<B>, value: Scalar) -> BoolTensor<B>;
189
190    /// Selects the elements of the tensor where the mask is true, returned as a 1D tensor.
191    ///
192    /// The elements are collected in row-major order. Because the number of selected elements
193    /// depends on the mask values, the output shape is data-dependent: computing it may require
194    /// synchronizing with the device, which is why this operation is asynchronous.
195    ///
196    /// # Arguments
197    ///
198    /// * `tensor` - The tensor to select from.
199    /// * `mask` - The boolean mask, with the same shape as the tensor.
200    ///
201    /// # Returns
202    ///
203    /// A 1D tensor containing the selected elements.
204    fn bool_mask_select(
205        tensor: BoolTensor<B>,
206        mask: BoolTensor<B>,
207    ) -> impl Future<Output = BoolTensor<B>> + 'static + Send {
208        async move {
209            // Data-dependent output length, so we defer to `bool_argwhere` (the only pre-existing
210            // data-dependent op) to collect the flat indices of the true mask values, then select.
211            let n = mask.shape().num_elements();
212            let int_dtype = get_device_settings::<B>(&mask.device()).int_dtype;
213            let mask = B::bool_reshape(mask, Shape::new([n]));
214            let indices = B::bool_argwhere(mask, int_dtype).await; // [count, 1]
215            let count = indices.shape()[0];
216            let indices = B::int_reshape(indices, Shape::new([count])); // squeeze to [count]
217            let tensor = B::bool_reshape(tensor, Shape::new([n]));
218            B::bool_select(tensor, 0, indices)
219        }
220    }
221
222    /// Gather elements from the tensor at the given indices.
223    ///
224    /// # Arguments
225    ///
226    /// * `dim` - The dimension to gather from.
227    /// * `tensor` - The tensor.
228    /// * `indices` - The indices.
229    fn bool_gather(dim: usize, tensor: BoolTensor<B>, indices: IntTensor<B>) -> BoolTensor<B>;
230
231    /// Scatter a given value to the tensor at the given indices using boolean or reduction.
232    ///
233    /// # Arguments
234    ///
235    /// * `dim` - The dimension to scatter to.
236    /// * `tensor` - The tensor.
237    /// * `indices` - The indices.
238    /// * `value` - The value.
239    ///
240    /// # Returns
241    ///
242    /// The tensor with the values scattered.
243    fn bool_scatter_or(
244        dim: usize,
245        tensor: BoolTensor<B>,
246        indices: IntTensor<B>,
247        value: BoolTensor<B>,
248    ) -> BoolTensor<B>;
249
250    /// Select tensor elements along the given dimension corresponding to the given indices.
251    ///
252    /// # Arguments
253    ///
254    /// * `tensor` - The tensor to select from.
255    /// * `dim` - The dimension to select from.
256    /// * `indices` - The indices of the elements to select.
257    ///
258    /// # Returns
259    ///
260    /// The tensor with the selected elements.
261    fn bool_select(tensor: BoolTensor<B>, dim: usize, indices: IntTensor<B>) -> BoolTensor<B>;
262
263    /// Assign the selected elements along the given dimension corresponding to the given indices
264    /// to the given value using sum reduction.
265    ///
266    /// # Arguments
267    ///
268    /// * `tensor` - The tensor to assign the values to.
269    /// * `dim` - The dimension to select from.
270    /// * `indices` - The indices of the elements to assign.
271    /// * `value` - The values to assign.
272    ///
273    /// # Returns
274    ///
275    /// The tensor with the assigned values.
276    fn bool_select_or(
277        tensor: BoolTensor<B>,
278        dim: usize,
279        indices: IntTensor<B>,
280        value: BoolTensor<B>,
281    ) -> BoolTensor<B>;
282
283    /// Repeats one dimension of the tensor a given number of times along that dimension.
284    ///
285    /// # Arguments
286    ///
287    /// * `tensor` - The tensor.
288    /// * `dim` - The dimension to repeat.
289    /// * `times` - The number of times to repeat the dimension.
290    ///
291    /// # Returns
292    ///
293    /// The tensor with the dimension repeated.
294    fn bool_repeat_dim(tensor: BoolTensor<B>, dim: usize, times: usize) -> BoolTensor<B> {
295        let device = tensor.device();
296        repeat_with_slice_assign::<B, _, _, _>(
297            tensor,
298            dim,
299            times,
300            device,
301            |shape, device, dtype| B::bool_empty(shape, device, dtype.into()),
302            B::bool_slice_assign,
303        )
304    }
305
306    /// Concatenates the tensors along the given dimension.
307    ///
308    /// # Arguments
309    ///
310    /// * `tensors` - The tensors to concatenate.
311    /// * `dim` - The dimension to concatenate along.
312    ///
313    /// # Returns
314    ///
315    /// The tensor with the tensors concatenated along the given dimension.
316    ///
317    /// # Note
318    ///
319    /// Empty tensors (where the concatenation dimension has size 0) are filtered out at the
320    /// high-level tensor API and will not be passed to this method. Backend implementations do
321    /// not need to handle empty tensors.
322    fn bool_cat(tensors: Vec<BoolTensor<B>>, dim: usize) -> BoolTensor<B> {
323        let first_tensor = tensors.first().expect("Tensors should not be empty");
324        let device = first_tensor.device();
325        cat_with_slice_assign::<B, _, _, _>(
326            tensors,
327            dim,
328            device,
329            |shape, device, dtype| B::bool_empty(shape, device, dtype.into()),
330            B::bool_slice_assign,
331        )
332    }
333
334    /// Equates the two tensors.
335    ///
336    /// # Arguments
337    ///
338    /// * `lhs` - The left hand side tensor.
339    /// * `rhs` - The right hand side tensor.
340    ///
341    /// # Returns
342    ///
343    /// The tensor with the result of the equate.
344    fn bool_equal(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B>;
345
346    /// Element-wise non-equality comparison.
347    ///
348    /// # Arguments
349    ///
350    /// * `lhs` - The left hand side tensor.
351    /// * `rhs` - The right hand side tensor.
352    ///
353    /// # Returns
354    ///
355    /// The tensor with the result of the comparison.
356    fn bool_not_equal(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B> {
357        let equal_tensor = B::bool_equal(lhs, rhs);
358        B::bool_not(equal_tensor)
359    }
360
361    /// Element-wise equality comparison with a scalar.
362    ///
363    /// # Arguments
364    ///
365    /// * `lhs` - The left-hand side tensor.
366    /// * `rhs` - The right-hand side scalar.
367    ///
368    /// # Returns
369    ///
370    /// The boolean tensor with the result of the comparison.
371    fn bool_equal_elem(lhs: BoolTensor<B>, rhs: Scalar) -> BoolTensor<B>;
372
373    /// Element-wise non-equality comparison with a scalar.
374    ///
375    /// # Arguments
376    ///
377    /// * `lhs` - The left-hand side tensor.
378    /// * `rhs` - The right-hand side scalar.
379    ///
380    /// # Returns
381    ///
382    /// The boolean tensor with the result of the comparison.
383    fn bool_not_equal_elem(lhs: BoolTensor<B>, rhs: Scalar) -> BoolTensor<B> {
384        let equal_tensor = B::bool_equal_elem(lhs, rhs);
385        B::bool_not(equal_tensor)
386    }
387
388    /// Inverses boolean values.
389    ///
390    /// # Arguments
391    ///
392    /// * `tensor` - The tensor.
393    ///
394    /// # Returns
395    ///
396    /// The tensor with the result of the negation.
397    fn bool_not(tensor: BoolTensor<B>) -> BoolTensor<B>;
398
399    /// Executes the logical and (`&&`) operation on two boolean tensors.
400    ///
401    /// # Arguments
402    ///
403    /// * `lhs` - The left hand side tensor.
404    /// * `rhs` - The right hand side tensor.
405    ///
406    /// # Returns
407    ///
408    /// The tensor with the result of the logical and.
409    fn bool_and(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B>;
410
411    /// Executes the logical or (`||`) operation on two boolean tensors.
412    ///
413    /// # Arguments
414    ///
415    /// * `lhs` - The left hand side tensor.
416    /// * `rhs` - The right hand side tensor.
417    ///
418    /// # Returns
419    ///
420    /// The tensor with the result of the logical or.
421    fn bool_or(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B>;
422
423    /// Element-wise exclusive or.
424    ///
425    /// # Arguments
426    ///
427    /// * `lhs` - The left hand side tensor.
428    /// * `rhs` - The right hand side tensor.
429    ///
430    /// # Returns
431    ///
432    /// The tensor with the result of the comparison.
433    fn bool_xor(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B> {
434        Self::bool_not_equal(lhs, rhs)
435    }
436
437    /// Transposes a bool tensor.
438    ///
439    /// # Arguments
440    ///
441    /// * `tensor` - The tensor to transpose.
442    ///
443    /// # Returns
444    ///
445    /// The transposed tensor.
446    fn bool_transpose(tensor: BoolTensor<B>) -> BoolTensor<B> {
447        let ndims = tensor.shape().num_dims();
448        Self::bool_swap_dims(tensor, ndims - 2, ndims - 1)
449    }
450
451    /// Swaps two dimensions of a bool tensor.
452    ///
453    /// # Arguments
454    ///
455    /// * `tensor` - The tensor to swap the dimensions of.
456    /// * `dim1` - The first dimension to swap.
457    /// * `dim2` - The second dimension to swap.
458    ///
459    /// # Returns
460    ///
461    /// The tensor with the dimensions swapped.
462    fn bool_swap_dims(tensor: BoolTensor<B>, dim1: usize, dim2: usize) -> BoolTensor<B>;
463
464    /// Permutes the dimensions of a tensor.
465    ///
466    /// # Arguments
467    ///
468    /// * `tensor` - The tensor to permute the dimensions of.
469    /// * `axes` - The new order of the dimensions.
470    /// # Returns
471    ///
472    /// The tensor with the dimensions permuted.
473    fn bool_permute(tensor: BoolTensor<B>, axes: &[usize]) -> BoolTensor<B>;
474
475    /// Reverse the order of elements in a tensor along the given axes.
476    ///
477    /// # Arguments
478    ///
479    /// * `tensor` - The tensor to reverse.
480    /// * `axes` - The axes to reverse.
481    ///
482    /// The tensor with the elements reversed.
483    fn bool_flip(tensor: BoolTensor<B>, axes: &[usize]) -> BoolTensor<B>;
484
485    /// Tests if any element in the boolean `tensor` evaluates to True.
486    ///
487    /// # Arguments
488    ///
489    /// * `tensor` - The tensor to test.
490    ///
491    /// # Returns
492    ///
493    /// A boolean tensor with a single element, True if any element in the tensor is True, False otherwise.
494    fn bool_any(tensor: BoolTensor<B>) -> BoolTensor<B> {
495        let dtype = tensor.dtype();
496        let int_dtype = get_device_settings::<B>(&tensor.device()).int_dtype;
497        let sum = B::int_sum(B::bool_into_int(tensor, int_dtype));
498        B::int_greater_elem(sum, 0.into(), dtype.into())
499    }
500
501    /// Tests if any element in the boolean `tensor` evaluates to True along a given dimension `dim`.
502    ///
503    /// # Arguments
504    ///
505    /// * `tensor` - The tensor to test.
506    /// * `dim` - The axis along which to test.
507    ///
508    /// # Returns
509    ///
510    /// A boolean tensor `Tensor<B, D, Bool>` with the same size as input `tensor`, except in the `dim` axis
511    /// where the size is 1. The elem in the `dim` axis is True if any element along this dim in the input
512    /// evaluates to True, False otherwise.
513    fn bool_any_dim(tensor: BoolTensor<B>, dim: usize) -> BoolTensor<B> {
514        let dtype = tensor.dtype();
515        let int_dtype = get_device_settings::<B>(&tensor.device()).int_dtype;
516        let sum = B::int_sum_dim(B::bool_into_int(tensor, int_dtype), dim);
517        B::int_greater_elem(sum, 0.into(), dtype.into())
518    }
519
520    /// Tests if all elements in the boolean `tensor` evaluate to True.
521    ///
522    /// # Arguments
523    ///
524    /// * `tensor` - The tensor to test.
525    ///
526    /// # Returns
527    ///
528    /// A boolean tensor `Tensor<B, 1, Bool>` with a single element, True if all elements in the input tensor
529    /// evaluate to True, False otherwise.
530    fn bool_all(tensor: BoolTensor<B>) -> BoolTensor<B> {
531        let dtype = tensor.dtype();
532        let int_dtype = get_device_settings::<B>(&tensor.device()).int_dtype;
533        let num_elems = tensor.shape().num_elements() as i64;
534        let sum = B::int_sum(B::bool_into_int(tensor, int_dtype));
535        B::int_equal_elem(sum, num_elems.into(), dtype.into())
536    }
537
538    /// Tests if all elements in the boolean `tensor` evaluate to True along a given dimension `dim`.
539    ///
540    /// # Arguments
541    ///
542    /// * `tensor` - The tensor to test.
543    /// * `dim` - The axis along which to test.
544    ///
545    /// # Returns
546    ///
547    /// A boolean tensor `Tensor<B, D, Bool>` with the same size as input `tensor`, except in the `dim` axis
548    /// where the size is 1. The elem in the `dim` axis is True if all elements along this dim in the input
549    /// evaluates to True, False otherwise.
550    fn bool_all_dim(tensor: BoolTensor<B>, dim: usize) -> BoolTensor<B> {
551        let dtype = tensor.dtype();
552        let int_dtype = get_device_settings::<B>(&tensor.device()).int_dtype;
553        let num_elems = tensor.shape()[dim] as i64;
554        let sum = B::int_sum_dim(B::bool_into_int(tensor, int_dtype), dim);
555        B::int_equal_elem(sum, num_elems.into(), dtype.into())
556    }
557
558    /// Compute the indices of the elements that are non-zero, grouped by element.
559    ///
560    /// # Arguments
561    ///
562    /// * `tensor` - The input tensor.
563    /// * `out_dtype` - The output tensor dtype.
564    ///
565    /// # Returns
566    ///
567    /// A 2D tensor containing the indices of all non-zero elements of the given tensor.
568    /// Each row contains the indices of a non-zero element.
569    fn bool_argwhere(
570        tensor: BoolTensor<B>,
571        out_dtype: IntDType,
572    ) -> impl Future<Output = IntTensor<B>> + 'static + Send {
573        async move {
574            // Size of each output tensor is variable (= number of nonzero elements in the tensor).
575            // Reading the data to count the number of truth values might cause sync but is required.
576            let device = &tensor.device();
577            let data = B::bool_into_data(tensor)
578                .await
579                .expect("Can read the data without error");
580            argwhere_data::<B>(data, device, out_dtype)
581        }
582    }
583
584    /// Broadcasts the bool `tensor` to the given `shape`.
585    fn bool_expand(tensor: BoolTensor<B>, shape: Shape) -> BoolTensor<B>;
586
587    /// Unfold windows along a dimension.
588    ///
589    /// Returns a view of the tensor with all complete windows of size `size` in dimension `dim`;
590    /// where windows are advanced by `step` at each index.
591    ///
592    /// The number of windows is `max(0, (shape[dim] - size).ceil_div(step))`.
593    ///
594    /// # Arguments
595    ///
596    /// * `tensor` - The input tensor to unfold; of shape ``[pre=..., dim shape, post=...]``
597    /// * `dim` - the selected dim.
598    /// * `size` - the size of each unfolded window.
599    /// * `step` - the step between each window.
600    ///
601    /// # Returns
602    ///
603    /// A tensor view with shape ``[pre=..., windows, size, post=...]``.
604    fn bool_unfold(tensor: BoolTensor<B>, dim: usize, size: usize, step: usize) -> BoolTensor<B>;
605}