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_or_init_device_settings};
6use crate::{ExecutionError, Scalar};
7use alloc::vec::Vec;
8use burn_std::{BoolDType, FloatDType, IndexingUpdateOp, 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_or_init_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 elements into a tensor using the specified update operation.
232 ///
233 /// `Assign` overwrites, `Add` and `Max` combine with logical or, and `Mul` and `Min`
234 /// combine with logical and.
235 ///
236 /// # Arguments
237 ///
238 /// * `dim` - The dimension to scatter to.
239 /// * `tensor` - The tensor.
240 /// * `indices` - The indices.
241 /// * `value` - The value.
242 /// * `update` - The update operation.
243 ///
244 /// # Returns
245 ///
246 /// The tensor with the values scattered.
247 fn bool_scatter(
248 dim: usize,
249 tensor: BoolTensor<B>,
250 indices: IntTensor<B>,
251 value: BoolTensor<B>,
252 update: IndexingUpdateOp,
253 ) -> BoolTensor<B>;
254
255 /// Multi-dimensional scatter for bool tensors.
256 ///
257 /// `Assign` overwrites, `Add` and `Max` combine with logical or, and `Mul` and `Min`
258 /// combine with logical and.
259 fn bool_scatter_nd(
260 data: BoolTensor<B>,
261 indices: IntTensor<B>,
262 values: BoolTensor<B>,
263 reduction: IndexingUpdateOp,
264 ) -> BoolTensor<B>;
265
266 /// Multi-dimensional gather for bool tensors.
267 fn bool_gather_nd(data: BoolTensor<B>, indices: IntTensor<B>) -> BoolTensor<B>;
268
269 /// Select tensor elements along the given dimension corresponding to the given indices.
270 ///
271 /// # Arguments
272 ///
273 /// * `tensor` - The tensor to select from.
274 /// * `dim` - The dimension to select from.
275 /// * `indices` - The indices of the elements to select.
276 ///
277 /// # Returns
278 ///
279 /// The tensor with the selected elements.
280 fn bool_select(tensor: BoolTensor<B>, dim: usize, indices: IntTensor<B>) -> BoolTensor<B>;
281
282 /// Assign selected elements along a dimension using the specified update operation.
283 ///
284 /// `Assign` overwrites, `Add` and `Max` combine with logical or, and `Mul` and `Min`
285 /// combine with logical and.
286 ///
287 /// # Arguments
288 ///
289 /// * `tensor` - The tensor to assign the values to.
290 /// * `dim` - The dimension to select from.
291 /// * `indices` - The indices of the elements to assign.
292 /// * `value` - The values to assign.
293 /// * `update` - The update operation.
294 ///
295 /// # Returns
296 ///
297 /// The tensor with the assigned values.
298 fn bool_select_assign(
299 tensor: BoolTensor<B>,
300 dim: usize,
301 indices: IntTensor<B>,
302 value: BoolTensor<B>,
303 update: IndexingUpdateOp,
304 ) -> BoolTensor<B>;
305
306 /// Repeats one dimension of the tensor a given number of times along that dimension.
307 ///
308 /// # Arguments
309 ///
310 /// * `tensor` - The tensor.
311 /// * `dim` - The dimension to repeat.
312 /// * `times` - The number of times to repeat the dimension.
313 ///
314 /// # Returns
315 ///
316 /// The tensor with the dimension repeated.
317 fn bool_repeat_dim(tensor: BoolTensor<B>, dim: usize, times: usize) -> BoolTensor<B> {
318 let device = tensor.device();
319 repeat_with_slice_assign::<B, _, _, _>(
320 tensor,
321 dim,
322 times,
323 device,
324 |shape, device, dtype| B::bool_empty(shape, device, dtype.into()),
325 B::bool_slice_assign,
326 )
327 }
328
329 /// Concatenates the tensors along the given dimension.
330 ///
331 /// # Arguments
332 ///
333 /// * `tensors` - The tensors to concatenate.
334 /// * `dim` - The dimension to concatenate along.
335 ///
336 /// # Returns
337 ///
338 /// The tensor with the tensors concatenated along the given dimension.
339 ///
340 /// # Note
341 ///
342 /// Empty tensors (where the concatenation dimension has size 0) are filtered out at the
343 /// high-level tensor API and will not be passed to this method. Backend implementations do
344 /// not need to handle empty tensors.
345 fn bool_cat(tensors: Vec<BoolTensor<B>>, dim: usize) -> BoolTensor<B> {
346 let first_tensor = tensors.first().expect("Tensors should not be empty");
347 let device = first_tensor.device();
348 cat_with_slice_assign::<B, _, _, _>(
349 tensors,
350 dim,
351 device,
352 |shape, device, dtype| B::bool_empty(shape, device, dtype.into()),
353 B::bool_slice_assign,
354 )
355 }
356
357 /// Equates the two tensors.
358 ///
359 /// # Arguments
360 ///
361 /// * `lhs` - The left hand side tensor.
362 /// * `rhs` - The right hand side tensor.
363 ///
364 /// # Returns
365 ///
366 /// The tensor with the result of the equate.
367 fn bool_equal(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B>;
368
369 /// Element-wise non-equality comparison.
370 ///
371 /// # Arguments
372 ///
373 /// * `lhs` - The left hand side tensor.
374 /// * `rhs` - The right hand side tensor.
375 ///
376 /// # Returns
377 ///
378 /// The tensor with the result of the comparison.
379 fn bool_not_equal(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B> {
380 let equal_tensor = B::bool_equal(lhs, rhs);
381 B::bool_not(equal_tensor)
382 }
383
384 /// Element-wise equality comparison with a scalar.
385 ///
386 /// # Arguments
387 ///
388 /// * `lhs` - The left-hand side tensor.
389 /// * `rhs` - The right-hand side scalar.
390 ///
391 /// # Returns
392 ///
393 /// The boolean tensor with the result of the comparison.
394 fn bool_equal_elem(lhs: BoolTensor<B>, rhs: Scalar) -> BoolTensor<B>;
395
396 /// Element-wise non-equality comparison with a scalar.
397 ///
398 /// # Arguments
399 ///
400 /// * `lhs` - The left-hand side tensor.
401 /// * `rhs` - The right-hand side scalar.
402 ///
403 /// # Returns
404 ///
405 /// The boolean tensor with the result of the comparison.
406 fn bool_not_equal_elem(lhs: BoolTensor<B>, rhs: Scalar) -> BoolTensor<B> {
407 let equal_tensor = B::bool_equal_elem(lhs, rhs);
408 B::bool_not(equal_tensor)
409 }
410
411 /// Inverses boolean values.
412 ///
413 /// # Arguments
414 ///
415 /// * `tensor` - The tensor.
416 ///
417 /// # Returns
418 ///
419 /// The tensor with the result of the negation.
420 fn bool_not(tensor: BoolTensor<B>) -> BoolTensor<B>;
421
422 /// Executes the logical and (`&&`) operation on two boolean tensors.
423 ///
424 /// # Arguments
425 ///
426 /// * `lhs` - The left hand side tensor.
427 /// * `rhs` - The right hand side tensor.
428 ///
429 /// # Returns
430 ///
431 /// The tensor with the result of the logical and.
432 fn bool_and(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B>;
433
434 /// Executes the logical or (`||`) operation on two boolean tensors.
435 ///
436 /// # Arguments
437 ///
438 /// * `lhs` - The left hand side tensor.
439 /// * `rhs` - The right hand side tensor.
440 ///
441 /// # Returns
442 ///
443 /// The tensor with the result of the logical or.
444 fn bool_or(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B>;
445
446 /// Element-wise exclusive or.
447 ///
448 /// # Arguments
449 ///
450 /// * `lhs` - The left hand side tensor.
451 /// * `rhs` - The right hand side tensor.
452 ///
453 /// # Returns
454 ///
455 /// The tensor with the result of the comparison.
456 fn bool_xor(lhs: BoolTensor<B>, rhs: BoolTensor<B>) -> BoolTensor<B> {
457 Self::bool_not_equal(lhs, rhs)
458 }
459
460 /// Transposes a bool tensor.
461 ///
462 /// # Arguments
463 ///
464 /// * `tensor` - The tensor to transpose.
465 ///
466 /// # Returns
467 ///
468 /// The transposed tensor.
469 fn bool_transpose(tensor: BoolTensor<B>) -> BoolTensor<B> {
470 let ndims = tensor.shape().num_dims();
471 Self::bool_swap_dims(tensor, ndims - 2, ndims - 1)
472 }
473
474 /// Swaps two dimensions of a bool tensor.
475 ///
476 /// # Arguments
477 ///
478 /// * `tensor` - The tensor to swap the dimensions of.
479 /// * `dim1` - The first dimension to swap.
480 /// * `dim2` - The second dimension to swap.
481 ///
482 /// # Returns
483 ///
484 /// The tensor with the dimensions swapped.
485 fn bool_swap_dims(tensor: BoolTensor<B>, dim1: usize, dim2: usize) -> BoolTensor<B>;
486
487 /// Permutes the dimensions of a tensor.
488 ///
489 /// # Arguments
490 ///
491 /// * `tensor` - The tensor to permute the dimensions of.
492 /// * `axes` - The new order of the dimensions.
493 /// # Returns
494 ///
495 /// The tensor with the dimensions permuted.
496 fn bool_permute(tensor: BoolTensor<B>, axes: &[usize]) -> BoolTensor<B>;
497
498 /// Reverse the order of elements in a tensor along the given axes.
499 ///
500 /// # Arguments
501 ///
502 /// * `tensor` - The tensor to reverse.
503 /// * `axes` - The axes to reverse.
504 ///
505 /// The tensor with the elements reversed.
506 fn bool_flip(tensor: BoolTensor<B>, axes: &[usize]) -> BoolTensor<B>;
507
508 /// Tests if any element in the boolean `tensor` evaluates to True.
509 ///
510 /// # Arguments
511 ///
512 /// * `tensor` - The tensor to test.
513 ///
514 /// # Returns
515 ///
516 /// A boolean tensor with a single element, True if any element in the tensor is True, False otherwise.
517 fn bool_any(tensor: BoolTensor<B>) -> BoolTensor<B> {
518 let dtype = tensor.dtype();
519 let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
520 let sum = B::int_sum(B::bool_into_int(tensor, int_dtype));
521 B::int_greater_elem(sum, 0.into(), dtype.into())
522 }
523
524 /// Tests if any element in the boolean `tensor` evaluates to True along a given dimension `dim`.
525 ///
526 /// # Arguments
527 ///
528 /// * `tensor` - The tensor to test.
529 /// * `dim` - The axis along which to test.
530 ///
531 /// # Returns
532 ///
533 /// A boolean tensor primitive with the same size as input `tensor`, except in the `dim` axis
534 /// where the size is 1. The elem in the `dim` axis is True if any element along this dim in the input
535 /// evaluates to True, False otherwise.
536 fn bool_any_dim(tensor: BoolTensor<B>, dim: usize) -> BoolTensor<B> {
537 let dtype = tensor.dtype();
538 let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
539 let sum = B::int_sum_dim(B::bool_into_int(tensor, int_dtype), dim);
540 B::int_greater_elem(sum, 0.into(), dtype.into())
541 }
542
543 /// Tests if all elements in the boolean `tensor` evaluate to True.
544 ///
545 /// # Arguments
546 ///
547 /// * `tensor` - The tensor to test.
548 ///
549 /// # Returns
550 ///
551 /// A boolean tensor primitive with a single element, True if all elements in the input tensor
552 /// evaluate to True, False otherwise.
553 fn bool_all(tensor: BoolTensor<B>) -> BoolTensor<B> {
554 let dtype = tensor.dtype();
555 let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
556 let num_elems = tensor.shape().num_elements() as i64;
557 let sum = B::int_sum(B::bool_into_int(tensor, int_dtype));
558 B::int_equal_elem(sum, num_elems.into(), dtype.into())
559 }
560
561 /// Tests if all elements in the boolean `tensor` evaluate to True along a given dimension `dim`.
562 ///
563 /// # Arguments
564 ///
565 /// * `tensor` - The tensor to test.
566 /// * `dim` - The axis along which to test.
567 ///
568 /// # Returns
569 ///
570 /// A boolean tensor primitive with the same size as input `tensor`, except in the `dim` axis
571 /// where the size is 1. The elem in the `dim` axis is True if all elements along this dim in the input
572 /// evaluates to True, False otherwise.
573 fn bool_all_dim(tensor: BoolTensor<B>, dim: usize) -> BoolTensor<B> {
574 let dtype = tensor.dtype();
575 let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
576 let num_elems = tensor.shape()[dim] as i64;
577 let sum = B::int_sum_dim(B::bool_into_int(tensor, int_dtype), dim);
578 B::int_equal_elem(sum, num_elems.into(), dtype.into())
579 }
580
581 /// Compute the indices of the elements that are non-zero, grouped by element.
582 ///
583 /// # Arguments
584 ///
585 /// * `tensor` - The input tensor.
586 /// * `out_dtype` - The output tensor dtype.
587 ///
588 /// # Returns
589 ///
590 /// A 2D tensor containing the indices of all non-zero elements of the given tensor.
591 /// Each row contains the indices of a non-zero element.
592 fn bool_argwhere(
593 tensor: BoolTensor<B>,
594 out_dtype: IntDType,
595 ) -> impl Future<Output = IntTensor<B>> + 'static + Send {
596 async move {
597 // Size of each output tensor is variable (= number of nonzero elements in the tensor).
598 // Reading the data to count the number of truth values might cause sync but is required.
599 let device = &tensor.device();
600 let data = B::bool_into_data(tensor)
601 .await
602 .expect("Can read the data without error");
603 argwhere_data::<B>(data, device, out_dtype)
604 }
605 }
606
607 /// Broadcasts the bool `tensor` to the given `shape`.
608 fn bool_expand(tensor: BoolTensor<B>, shape: Shape) -> BoolTensor<B>;
609
610 /// Unfold windows along a dimension.
611 ///
612 /// Returns a view of the tensor with all complete windows of size `size` in dimension `dim`;
613 /// where windows are advanced by `step` at each index.
614 ///
615 /// The number of windows is `0` when `shape[dim] < size`, and otherwise
616 /// `(shape[dim] - size) / step + 1`.
617 ///
618 /// # Arguments
619 ///
620 /// * `tensor` - The input tensor to unfold; of shape ``[pre=..., dim shape, post=...]``
621 /// * `dim` - the selected dim.
622 /// * `size` - the size of each unfolded window.
623 /// * `step` - the step between each window.
624 ///
625 /// # Returns
626 ///
627 /// A tensor view with shape ``[pre=..., windows, size, post=...]``.
628 fn bool_unfold(tensor: BoolTensor<B>, dim: usize, size: usize, step: usize) -> BoolTensor<B>;
629}