Skip to main content

burn_flex/ops/
bool.rs

1//! Bool tensor operations for the Flex backend.
2
3use alloc::vec;
4use alloc::vec::Vec;
5use burn_backend::{
6    DType, ExecutionError, TensorData,
7    ops::{BoolTensorOps, IntTensorOps},
8    tensor::{BoolTensor, Device, FloatTensor, IndexingUpdateOp, IntTensor},
9};
10use burn_std::{Bytes, FloatDType, IntDType, Shape, Slice, bf16, f16};
11
12use crate::{Flex, FlexTensor, Layout};
13
14impl BoolTensorOps<Flex> for Flex {
15    fn bool_from_data(data: TensorData, _device: &Device<Flex>) -> BoolTensor<Flex> {
16        FlexTensor::from_data(data)
17    }
18
19    async fn bool_into_data(tensor: BoolTensor<Flex>) -> Result<TensorData, ExecutionError> {
20        Ok(tensor.into_data())
21    }
22
23    fn bool_to_device(tensor: BoolTensor<Flex>, _device: &Device<Flex>) -> BoolTensor<Flex> {
24        tensor
25    }
26
27    fn bool_cat(tensors: Vec<BoolTensor<Flex>>, dim: usize) -> BoolTensor<Flex> {
28        crate::ops::cat::cat(tensors, dim)
29    }
30
31    fn bool_reshape(tensor: BoolTensor<Flex>, shape: Shape) -> BoolTensor<Flex> {
32        tensor.reshape(shape)
33    }
34
35    fn bool_slice(tensor: BoolTensor<Flex>, slices: &[Slice]) -> BoolTensor<Flex> {
36        crate::ops::slice::slice(tensor, slices)
37    }
38
39    fn bool_empty(
40        shape: Shape,
41        _device: &Device<Flex>,
42        dtype: burn_std::BoolDType,
43    ) -> BoolTensor<Flex> {
44        FlexTensor::empty(shape, DType::from(dtype))
45    }
46
47    fn bool_slice_assign(
48        tensor: BoolTensor<Flex>,
49        slices: &[Slice],
50        value: BoolTensor<Flex>,
51    ) -> BoolTensor<Flex> {
52        crate::ops::slice::slice_assign(tensor, slices, value)
53    }
54
55    fn bool_into_int(tensor: BoolTensor<Flex>, out_dtype: burn_std::IntDType) -> IntTensor<Flex> {
56        let tensor = tensor.to_contiguous();
57        let shape = tensor.layout().shape().clone();
58        let out_dt = DType::from(out_dtype);
59        let bools = tensor.bytes();
60
61        macro_rules! convert {
62            ($int_ty:ty) => {{
63                let data: Vec<$int_ty> =
64                    bools.iter().map(|&x| if x != 0 { 1 } else { 0 }).collect();
65                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
66            }};
67        }
68
69        match out_dtype {
70            IntDType::I64 => convert!(i64),
71            IntDType::I32 => convert!(i32),
72            IntDType::I16 => convert!(i16),
73            IntDType::I8 => convert!(i8),
74            IntDType::U64 => convert!(u64),
75            IntDType::U32 => convert!(u32),
76            IntDType::U16 => convert!(u16),
77            IntDType::U8 => convert!(u8),
78        }
79    }
80
81    fn bool_into_float(
82        tensor: BoolTensor<Flex>,
83        out_dtype: burn_std::FloatDType,
84    ) -> FloatTensor<Flex> {
85        let tensor = tensor.to_contiguous();
86        let shape = tensor.layout().shape().clone();
87        let out_dt = DType::from(out_dtype);
88        let bools = tensor.bytes();
89
90        match out_dtype {
91            FloatDType::F64 => {
92                let data: Vec<f64> = bools
93                    .iter()
94                    .map(|&x| if x != 0 { 1.0 } else { 0.0 })
95                    .collect();
96                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
97            }
98            FloatDType::F32 | FloatDType::Flex32 => {
99                let data: Vec<f32> = bools
100                    .iter()
101                    .map(|&x| if x != 0 { 1.0 } else { 0.0 })
102                    .collect();
103                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
104            }
105            FloatDType::F16 => {
106                let one = f16::from_f32(1.0);
107                let zero = f16::from_f32(0.0);
108                let data: Vec<f16> = bools
109                    .iter()
110                    .map(|&x| if x != 0 { one } else { zero })
111                    .collect();
112                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
113            }
114            FloatDType::BF16 => {
115                let one = bf16::from_f32(1.0);
116                let zero = bf16::from_f32(0.0);
117                let data: Vec<bf16> = bools
118                    .iter()
119                    .map(|&x| if x != 0 { one } else { zero })
120                    .collect();
121                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
122            }
123        }
124    }
125
126    fn bool_swap_dims(tensor: BoolTensor<Flex>, dim1: usize, dim2: usize) -> BoolTensor<Flex> {
127        tensor.transpose(dim1, dim2)
128    }
129
130    fn bool_permute(tensor: BoolTensor<Flex>, axes: &[usize]) -> BoolTensor<Flex> {
131        tensor.permute(axes)
132    }
133
134    fn bool_flip(tensor: BoolTensor<Flex>, axes: &[usize]) -> BoolTensor<Flex> {
135        crate::ops::flip::flip(tensor, axes)
136    }
137
138    fn bool_equal(lhs: BoolTensor<Flex>, rhs: BoolTensor<Flex>) -> BoolTensor<Flex> {
139        use crate::strided_index::StridedIter;
140
141        // Broadcast to a common shape before comparing. The contiguous fast
142        // path below uses `zip`, which silently truncates to the shorter
143        // operand; and the output shape is taken from lhs, so mismatched
144        // operands would otherwise produce a result vec shorter than the
145        // output layout claims.
146        let (lhs, rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
147
148        let out_dtype = burn_std::BoolDType::from(lhs.dtype());
149        let shape = lhs.layout().shape().clone();
150        let lhs_storage: &[u8] = lhs.bytes();
151        let rhs_storage: &[u8] = rhs.bytes();
152
153        let result: Vec<u8> = match (
154            lhs.layout().contiguous_offsets(),
155            rhs.layout().contiguous_offsets(),
156        ) {
157            (Some((l_start, l_end)), Some((r_start, r_end))) => {
158                let l_slice = &lhs_storage[l_start..l_end];
159                let r_slice = &rhs_storage[r_start..r_end];
160                l_slice
161                    .iter()
162                    .zip(r_slice)
163                    .map(|(&a, &b)| (a == b) as u8)
164                    .collect()
165            }
166            _ => {
167                let lhs_iter = StridedIter::new(lhs.layout());
168                let rhs_iter = StridedIter::new(rhs.layout());
169                lhs_iter
170                    .zip(rhs_iter)
171                    .map(|(li, ri)| (lhs_storage[li] == rhs_storage[ri]) as u8)
172                    .collect()
173            }
174        };
175
176        crate::ops::comparison::make_bool_tensor(result, shape, out_dtype)
177    }
178
179    fn bool_not(mut tensor: BoolTensor<Flex>) -> BoolTensor<Flex> {
180        use crate::strided_index::StridedIter;
181
182        debug_assert!(
183            matches!(
184                tensor.dtype(),
185                DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8)
186            ),
187            "bool_not: only Bool(Native) and Bool(U8) are supported, got {:?}",
188            tensor.dtype()
189        );
190
191        // Fast path: in-place for unique, contiguous tensors at offset 0. This
192        // preserves the input tensor's dtype tag implicitly (the in-place SIMD
193        // ops flip bytes without touching the dtype tag).
194        if tensor.is_unique()
195            && tensor.layout().is_contiguous()
196            && tensor.layout().start_offset() == 0
197        {
198            let storage = tensor.storage_mut::<u8>();
199            crate::simd::bool_not_inplace_u8(storage);
200            return tensor;
201        }
202
203        // Allocating path for shared, non-contiguous, or offset tensors:
204        // preserve the input's bool dtype for the new tensor.
205        let out_dtype = burn_std::BoolDType::from(tensor.dtype());
206        let shape = tensor.layout().shape().clone();
207        let storage: &[u8] = tensor.bytes();
208
209        let result: Vec<u8> = match tensor.layout().contiguous_offsets() {
210            Some((start, end)) => {
211                let slice = &storage[start..end];
212                let mut out = vec![0u8; slice.len()];
213                crate::simd::bool_not_u8(slice, &mut out);
214                out
215            }
216            None => StridedIter::new(tensor.layout())
217                .map(|idx| (storage[idx] == 0) as u8)
218                .collect(),
219        };
220
221        crate::ops::comparison::make_bool_tensor(result, shape, out_dtype)
222    }
223
224    fn bool_and(lhs: BoolTensor<Flex>, rhs: BoolTensor<Flex>) -> BoolTensor<Flex> {
225        bool_binary_op_simd(lhs, rhs, BoolBinaryOp::And)
226    }
227
228    fn bool_or(lhs: BoolTensor<Flex>, rhs: BoolTensor<Flex>) -> BoolTensor<Flex> {
229        bool_binary_op_simd(lhs, rhs, BoolBinaryOp::Or)
230    }
231
232    fn bool_xor(lhs: BoolTensor<Flex>, rhs: BoolTensor<Flex>) -> BoolTensor<Flex> {
233        bool_binary_op_simd(lhs, rhs, BoolBinaryOp::Xor)
234    }
235
236    fn bool_expand(tensor: BoolTensor<Flex>, shape: Shape) -> BoolTensor<Flex> {
237        crate::ops::expand::expand(tensor, shape)
238    }
239
240    // Missing methods
241    fn bool_zeros(
242        shape: Shape,
243        device: &Device<Flex>,
244        dtype: burn_std::BoolDType,
245    ) -> BoolTensor<Flex> {
246        Self::bool_empty(shape, device, dtype)
247    }
248
249    fn bool_ones(
250        shape: Shape,
251        _device: &Device<Flex>,
252        dtype: burn_std::BoolDType,
253    ) -> BoolTensor<Flex> {
254        let num_elements = shape.num_elements();
255        let data = vec![1u8; num_elements];
256        crate::ops::comparison::make_bool_tensor(data, shape, dtype)
257    }
258
259    fn bool_mask_where(
260        tensor: BoolTensor<Flex>,
261        mask: BoolTensor<Flex>,
262        value: BoolTensor<Flex>,
263    ) -> BoolTensor<Flex> {
264        crate::ops::mask::mask_where_bool(tensor, mask, value)
265    }
266
267    fn bool_mask_fill(
268        tensor: BoolTensor<Flex>,
269        mask: BoolTensor<Flex>,
270        value: burn_backend::Scalar,
271    ) -> BoolTensor<Flex> {
272        let value: bool = value.elem();
273        crate::ops::mask::mask_fill_bool(tensor, mask, value)
274    }
275
276    fn bool_gather(
277        dim: usize,
278        tensor: BoolTensor<Flex>,
279        indices: IntTensor<Flex>,
280    ) -> BoolTensor<Flex> {
281        crate::ops::gather_scatter::gather_bool(tensor, dim, indices)
282    }
283
284    fn bool_scatter(
285        dim: usize,
286        tensor: BoolTensor<Flex>,
287        indices: IntTensor<Flex>,
288        value: BoolTensor<Flex>,
289        update: IndexingUpdateOp,
290    ) -> BoolTensor<Flex> {
291        crate::ops::gather_scatter::scatter_bool(tensor, dim, indices, value, update)
292    }
293
294    fn bool_scatter_nd(
295        data: BoolTensor<Flex>,
296        indices: IntTensor<Flex>,
297        values: BoolTensor<Flex>,
298        reduction: IndexingUpdateOp,
299    ) -> BoolTensor<Flex> {
300        crate::ops::gather_scatter::scatter_nd_bool(data, indices, values, reduction)
301    }
302
303    fn bool_gather_nd(data: BoolTensor<Flex>, indices: IntTensor<Flex>) -> BoolTensor<Flex> {
304        crate::ops::gather_scatter::gather_nd_bool(data, indices)
305    }
306
307    fn bool_equal_elem(lhs: BoolTensor<Flex>, rhs: burn_backend::Scalar) -> BoolTensor<Flex> {
308        use crate::strided_index::StridedIter;
309
310        let out_dtype = burn_std::BoolDType::from(lhs.dtype());
311        let shape = lhs.layout().shape().clone();
312        let storage: &[u8] = lhs.bytes();
313        let rhs_bool: bool = rhs.elem();
314        let rhs_val = rhs_bool as u8;
315
316        let result: Vec<u8> = match lhs.layout().contiguous_offsets() {
317            Some((start, end)) => storage[start..end]
318                .iter()
319                .map(|&v| (v == rhs_val) as u8)
320                .collect(),
321            None => StridedIter::new(lhs.layout())
322                .map(|idx| (storage[idx] == rhs_val) as u8)
323                .collect(),
324        };
325
326        crate::ops::comparison::make_bool_tensor(result, shape, out_dtype)
327    }
328
329    fn bool_unfold(
330        tensor: BoolTensor<Flex>,
331        dim: usize,
332        size: usize,
333        step: usize,
334    ) -> BoolTensor<Flex> {
335        crate::ops::unfold::unfold_bool(tensor, dim, size, step)
336    }
337
338    fn bool_not_equal(lhs: BoolTensor<Flex>, rhs: BoolTensor<Flex>) -> BoolTensor<Flex> {
339        let out_dtype = burn_std::BoolDType::from(lhs.dtype());
340        crate::ops::comparison::bool_not_equal(lhs, rhs, out_dtype)
341    }
342
343    fn bool_not_equal_elem(lhs: BoolTensor<Flex>, rhs: burn_backend::Scalar) -> BoolTensor<Flex> {
344        let out_dtype = burn_std::BoolDType::from(lhs.dtype());
345        let rhs: bool = rhs.elem();
346        crate::ops::comparison::bool_not_equal_elem(lhs, rhs, out_dtype)
347    }
348
349    fn bool_any(tensor: BoolTensor<Flex>) -> BoolTensor<Flex> {
350        let out_dtype = burn_std::BoolDType::from(tensor.dtype());
351        crate::ops::comparison::any_bool(tensor, out_dtype)
352    }
353
354    fn bool_any_dim(tensor: BoolTensor<Flex>, dim: usize) -> BoolTensor<Flex> {
355        let out_dtype = burn_std::BoolDType::from(tensor.dtype());
356        crate::ops::comparison::any_bool_dim(tensor, dim, out_dtype)
357    }
358
359    fn bool_all(tensor: BoolTensor<Flex>) -> BoolTensor<Flex> {
360        let out_dtype = burn_std::BoolDType::from(tensor.dtype());
361        crate::ops::comparison::all_bool(tensor, out_dtype)
362    }
363
364    fn bool_all_dim(tensor: BoolTensor<Flex>, dim: usize) -> BoolTensor<Flex> {
365        let out_dtype = burn_std::BoolDType::from(tensor.dtype());
366        crate::ops::comparison::all_bool_dim(tensor, dim, out_dtype)
367    }
368
369    fn bool_select(
370        tensor: BoolTensor<Flex>,
371        dim: usize,
372        indices: IntTensor<Flex>,
373    ) -> BoolTensor<Flex> {
374        crate::ops::gather_scatter::select::<u8>(tensor, dim, indices)
375    }
376
377    fn bool_select_assign(
378        tensor: BoolTensor<Flex>,
379        dim: usize,
380        indices: IntTensor<Flex>,
381        value: BoolTensor<Flex>,
382        update: IndexingUpdateOp,
383    ) -> BoolTensor<Flex> {
384        crate::ops::gather_scatter::select_assign_bool(tensor, dim, indices, value, update)
385    }
386
387    fn bool_transpose(tensor: BoolTensor<Flex>) -> BoolTensor<Flex> {
388        let ndims = tensor.layout().num_dims();
389        if ndims < 2 {
390            return tensor;
391        }
392        tensor.transpose(ndims - 2, ndims - 1)
393    }
394
395    fn bool_repeat_dim(tensor: BoolTensor<Flex>, dim: usize, times: usize) -> BoolTensor<Flex> {
396        crate::ops::repeat_dim::repeat_dim(tensor, dim, times)
397    }
398
399    async fn bool_argwhere(tensor: BoolTensor<Flex>, out_dtype: IntDType) -> IntTensor<Flex> {
400        let tensor = tensor.to_contiguous();
401        let shape = tensor.layout().shape().clone();
402        let ndims = shape.num_dims();
403        let data: &[u8] = tensor.storage();
404        let n = shape.num_elements();
405
406        let count = data[..n].iter().filter(|&&v| v != 0).count();
407        let mut coords: Vec<isize> = Vec::with_capacity(count * ndims);
408        let strides = crate::layout::contiguous_strides_usize(&shape);
409
410        for (flat_idx, &val) in data[..n].iter().enumerate() {
411            if val != 0 {
412                let mut remaining = flat_idx;
413                for &s in &strides {
414                    coords.push((remaining / s) as isize);
415                    remaining %= s;
416                }
417            }
418        }
419
420        let out_shape = Shape::from(vec![count, ndims]);
421        let result = FlexTensor::new(
422            Bytes::from_elems(coords),
423            Layout::contiguous(out_shape),
424            crate::ops::INDEX_DTYPE,
425        );
426        if result.dtype() != DType::from(out_dtype) {
427            Flex::int_cast(result, out_dtype)
428        } else {
429            result
430        }
431    }
432}
433
434/// Boolean binary operation type.
435#[derive(Clone, Copy)]
436enum BoolBinaryOp {
437    And,
438    Or,
439    Xor,
440}
441
442fn bool_binary_op_simd(lhs: FlexTensor, rhs: FlexTensor, op: BoolBinaryOp) -> FlexTensor {
443    use crate::strided_index::StridedIter;
444
445    debug_assert_eq!(lhs.dtype(), rhs.dtype(), "bool_binary_op: dtype mismatch");
446
447    // Broadcast to a common shape before dispatching. The scalar/SIMD helpers
448    // below assume equal-length operands; without this, mismatched shapes
449    // either silently keep the lhs shape or OOB-panic inside the helpers.
450    let (mut lhs, mut rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
451
452    // Preserve the input bool dtype (taken from lhs; rhs is assumed to match
453    // in dtype, checked above).
454    let out_dtype = burn_std::BoolDType::from(lhs.dtype());
455    let shape = lhs.layout().shape().clone();
456    let l_offsets = lhs.layout().contiguous_offsets();
457    let r_offsets = rhs.layout().contiguous_offsets();
458
459    // Fast path 1: lhs is unique and contiguous at offset 0 -> in-place on lhs
460    if lhs.is_unique()
461        && let (Some((0, l_end)), Some((r_start, r_end))) = (l_offsets, r_offsets)
462    {
463        let rhs_storage: &[u8] = rhs.bytes();
464        let r_slice = &rhs_storage[r_start..r_end];
465        let lhs_storage: &mut [u8] = lhs.storage_mut();
466        let l_slice = &mut lhs_storage[..l_end];
467
468        match op {
469            BoolBinaryOp::And => crate::simd::bool_and_inplace_u8(l_slice, r_slice),
470            BoolBinaryOp::Or => crate::simd::bool_or_inplace_u8(l_slice, r_slice),
471            BoolBinaryOp::Xor => crate::simd::bool_xor_inplace_u8(l_slice, r_slice),
472        }
473        return lhs;
474    }
475
476    // Fast path 2: rhs is unique and contiguous at offset 0 -> in-place on rhs
477    // (And/Or/Xor are commutative, so we can swap operands)
478    if rhs.is_unique()
479        && let (Some((l_start, l_end)), Some((0, r_end))) = (l_offsets, r_offsets)
480    {
481        let lhs_storage: &[u8] = lhs.bytes();
482        let l_slice = &lhs_storage[l_start..l_end];
483        let rhs_storage: &mut [u8] = rhs.storage_mut();
484        let r_slice = &mut rhs_storage[..r_end];
485
486        match op {
487            BoolBinaryOp::And => crate::simd::bool_and_inplace_u8(r_slice, l_slice),
488            BoolBinaryOp::Or => crate::simd::bool_or_inplace_u8(r_slice, l_slice),
489            BoolBinaryOp::Xor => crate::simd::bool_xor_inplace_u8(r_slice, l_slice),
490        }
491        return rhs;
492    }
493
494    // Allocating path: neither tensor is suitable for in-place
495    let lhs_storage: &[u8] = lhs.bytes();
496    let rhs_storage: &[u8] = rhs.bytes();
497
498    let result: Vec<u8> = match (l_offsets, r_offsets) {
499        (Some((l_start, l_end)), Some((r_start, r_end))) => {
500            let l_slice = &lhs_storage[l_start..l_end];
501            let r_slice = &rhs_storage[r_start..r_end];
502            let mut out = vec![0u8; l_slice.len()];
503            match op {
504                BoolBinaryOp::And => crate::simd::bool_and_u8(l_slice, r_slice, &mut out),
505                BoolBinaryOp::Or => crate::simd::bool_or_u8(l_slice, r_slice, &mut out),
506                BoolBinaryOp::Xor => crate::simd::bool_xor_u8(l_slice, r_slice, &mut out),
507            }
508            out
509        }
510        _ => {
511            // Strided/broadcast fallback: collapsed loop nest with an
512            // autovectorized bitwise inner loop. Separate zip_map calls
513            // per op keep the closures monomorphized. StridedIter only
514            // remains for layouts the nest can't handle (negative
515            // strides, rank > 8).
516            let zipped = match op {
517                BoolBinaryOp::And => crate::zip::zip_map(
518                    lhs_storage,
519                    lhs.layout(),
520                    rhs_storage,
521                    rhs.layout(),
522                    |a, b| a & b,
523                ),
524                BoolBinaryOp::Or => crate::zip::zip_map(
525                    lhs_storage,
526                    lhs.layout(),
527                    rhs_storage,
528                    rhs.layout(),
529                    |a, b| a | b,
530                ),
531                BoolBinaryOp::Xor => crate::zip::zip_map(
532                    lhs_storage,
533                    lhs.layout(),
534                    rhs_storage,
535                    rhs.layout(),
536                    |a, b| a ^ b,
537                ),
538            };
539            match zipped {
540                Some(result) => result,
541                None => {
542                    let lhs_iter = StridedIter::new(lhs.layout());
543                    let rhs_iter = StridedIter::new(rhs.layout());
544                    match op {
545                        BoolBinaryOp::And => lhs_iter
546                            .zip(rhs_iter)
547                            .map(|(li, ri)| lhs_storage[li] & rhs_storage[ri])
548                            .collect(),
549                        BoolBinaryOp::Or => lhs_iter
550                            .zip(rhs_iter)
551                            .map(|(li, ri)| lhs_storage[li] | rhs_storage[ri])
552                            .collect(),
553                        BoolBinaryOp::Xor => lhs_iter
554                            .zip(rhs_iter)
555                            .map(|(li, ri)| lhs_storage[li] ^ rhs_storage[ri])
556                            .collect(),
557                    }
558                }
559            }
560        }
561    };
562
563    crate::ops::comparison::make_bool_tensor(result, shape, out_dtype)
564}
565
566// Tests kept here exercise flex-specific dtype storage selection via
567// explicit IntDType/FloatDType. Plain bool ops, bool-to-int/float
568// casts, and negative-stride (flipped) bool coverage have been migrated
569// to crates/burn-backend-tests/tests/tensor/bool/ops/{logical,cast}.rs
570// so they run against every backend. When adding new tests, keep them
571// here only if they probe flex dtype dispatch; otherwise add them
572// there.
573#[cfg(test)]
574mod tests {
575    use alloc::vec;
576    use burn_backend::TensorData;
577    use burn_backend::ops::BoolTensorOps;
578    use burn_std::{FloatDType, IntDType};
579
580    use crate::{Flex, FlexTensor};
581
582    #[test]
583    fn test_bool_into_int_u8() {
584        let t = FlexTensor::from_data(TensorData::from([true, false, true]));
585        let result = Flex::bool_into_int(t, IntDType::U8);
586        assert_eq!(result.dtype(), burn_backend::DType::U8);
587        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
588        assert_eq!(data, vec![1u8, 0, 1]);
589    }
590
591    #[test]
592    fn test_bool_into_float_f64() {
593        let t = FlexTensor::from_data(TensorData::from([true, false, true]));
594        let result = Flex::bool_into_float(t, FloatDType::F64);
595        assert_eq!(result.dtype(), burn_backend::DType::F64);
596        let data: Vec<f64> = result.into_data().try_into_vec().unwrap();
597        assert_eq!(data, vec![1.0f64, 0.0, 1.0]);
598    }
599}