Skip to main content

burn_tensor/tensor/api/
float.rs

1use crate::AsIndex;
2use crate::Cast;
3use crate::DType;
4use crate::Device;
5use crate::Tensor;
6use crate::cast::ToElement;
7use crate::check;
8use crate::check::TensorCheck;
9use crate::check::unwrap_dim_index;
10use crate::kind::FloatMath;
11use crate::ops::{BridgeKind, BridgeTensor};
12use crate::quantization::{QuantScheme, QuantizationParameters, global_scale_dtype};
13use crate::tensor::stats;
14use crate::tensor::{Distribution, TensorData};
15use crate::{Bool, Float, Int, TensorPrimitive};
16#[cfg(feature = "std")]
17use burn_backend::AutodiffBackend;
18use burn_backend::ElementConversion;
19use burn_backend::Scalar;
20use burn_backend::TensorMetadata;
21#[cfg(feature = "std")]
22use burn_backend::distributed::DistributedParamId;
23use burn_backend::ops::ActivationOps;
24use burn_backend::ops::FloatTensorOps;
25use burn_backend::ops::GridSampleOptions;
26use burn_backend::ops::QTensorOps;
27use burn_backend::quantization::QuantizationParametersPrimitive;
28use burn_dispatch::Dispatch;
29use core::f32;
30
31/// Default RTOL value for `is_close` and `all_close`.
32pub const DEFAULT_RTOL: f64 = 1e-5;
33
34/// Default ATOL value for `is_close` and `all_close`.
35pub const DEFAULT_ATOL: f64 = 1e-8;
36
37impl<const D: usize> Tensor<D> {
38    /// Applies the [error function](https://en.wikipedia.org/wiki/Error_function) element wise.
39    ///
40    #[cfg_attr(
41        doc,
42        doc = r#"
43$y_i = \text{erf}\(x_i\)$
44
45The error function is defined as:
46
47$$\text{erf}\(x\) = \frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2} dt$$
48"#
49    )]
50    #[cfg_attr(not(doc), doc = "`y_i = erf(x_i)`")]
51    pub fn erf(self) -> Self {
52        Self::new(erf_impl(self.primitive))
53    }
54
55    /// Applies [hypotenuse operation](https://en.wikipedia.org/wiki/Hypotenuse) element wise.
56    ///
57    #[cfg_attr(doc, doc = r#"$y_i = \sqrt{x_i^2 + y_i^2}$"#)]
58    #[cfg_attr(not(doc), doc = "`y_i = sqrt(x_i^2 + y_i^2)`")]
59    pub fn hypot(self, other: Self) -> Self {
60        check!(TensorCheck::binary_ops_ew("Hypot", &self, &other));
61        Self::new(hypot_impl(self.primitive, other.primitive))
62    }
63
64    /// Applies [reciprocal operation](https://en.wikipedia.org/wiki/Multiplicative_inverse)
65    /// (or multiplicative inverse) element wise.
66    ///
67    #[cfg_attr(doc, doc = r#"$y_i = \frac{1}{x_i}$"#)]
68    #[cfg_attr(not(doc), doc = "`y_i = 1/x_i`")]
69    pub fn recip(self) -> Self {
70        Self::new(recip_impl(self.primitive))
71    }
72
73    /// Converts each of the elements of the input tensor from angles in degrees to radians.
74    ///
75    /// # Example
76    /// ```ignore
77    /// let tensor_in_radians = tensor.deg2rad();
78    /// ```
79    pub fn deg2rad(self) -> Self {
80        self.mul_scalar(core::f64::consts::PI / 180.0)
81    }
82
83    /// Converts each of the elements of the input tensor from angles in radians to degrees.
84    ///
85    /// # Example
86    /// ```ignore
87    /// let tensor_in_degrees = tensor.rad2deg();
88    /// ```
89    pub fn rad2deg(self) -> Self {
90        self.mul_scalar(180.0 / core::f64::consts::PI)
91    }
92
93    /// Applies element wise round operation.
94    ///
95    /// This function implements the [round half to even](https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even)
96    /// strategy, with halfway cases rounded to the nearest even integer value.
97    pub fn round(self) -> Self {
98        Self::new(round_impl(self.primitive))
99    }
100
101    /// Applies element wise floor operation.
102    pub fn floor(self) -> Self {
103        Self::new(floor_impl(self.primitive))
104    }
105
106    /// Applies element wise ceil operation.
107    pub fn ceil(self) -> Self {
108        Self::new(ceil_impl(self.primitive))
109    }
110
111    /// Create a tensor from floats (f32) on a given device.
112    ///
113    /// # Example
114    ///
115    /// ```rust
116    /// use burn_tensor::Tensor;
117    ///
118    /// let device = Default::default();
119    /// let _ = Tensor::<1>::from_floats([1.0, 2.0], &device);
120    /// let _ = Tensor::<2>::from_floats([[1.0, 2.0], [3.0, 4.0]], &device);
121    /// ```
122    pub fn from_floats<A: Into<TensorData>>(floats: A, device: &Device) -> Self {
123        Self::from_data(floats.into().convert::<f32>(), device)
124    }
125
126    /// Returns a new tensor with the same shape and device as the current tensor and the data
127    /// cast to Integer.
128    ///
129    /// # Example
130    ///
131    /// ```rust
132    /// use burn_tensor::Tensor;
133    ///
134    /// let device = Default::default();
135    /// let float_tensor = Tensor::<1>::from_floats([1.0, 2.0], &device);
136    /// let int_tensor = float_tensor.int();
137    /// ```
138    pub fn int(self) -> Tensor<D, Int> {
139        let device = self.device();
140        Tensor::new(int_impl(self.primitive, device))
141    }
142
143    /// Returns a new tensor with the same shape, dtype, and device as the current tensor filled random
144    /// values sampled from the given distribution.
145    ///
146    /// # Panics
147    ///
148    /// If the tensor is quantized. This method preserves the input dtype,
149    /// but quantized tensor creation is not supported.
150    pub fn random_like(&self, distribution: Distribution) -> Self {
151        check!(TensorCheck::quantized_unsupported(
152            "Random Like",
153            self.dtype()
154        ));
155        Self::new(random_like_impl(&self.primitive, distribution))
156    }
157
158    /// Calculate the variance along the given dimension.
159    ///
160    /// Negative dimensions are supported and count from the end.
161    pub fn var<I: AsIndex>(self, dim: I) -> Self {
162        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var");
163        stats::var(self, dim)
164    }
165
166    /// Calculate the variance along the given dimension without applying the Bessel’s correction.
167    ///
168    /// Negative dimensions are supported and count from the end.
169    pub fn var_bias<I: AsIndex>(self, dim: I) -> Self {
170        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var Bias");
171        stats::var_bias(self, dim)
172    }
173
174    /// Calculate the variance along the given dimension and also returns the mean.
175    ///
176    /// Negative dimensions are supported and count from the end.
177    pub fn var_mean<I: AsIndex>(self, dim: I) -> (Self, Self) {
178        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var Mean");
179        let mean = self.clone().mean_dim(dim);
180        let var = stats::var_with_mean(self, mean.clone(), dim);
181        (var, mean)
182    }
183
184    /// Calculate the variance along the given dimension without applying the Bessel’s correction and also returns the mean.
185    ///
186    /// Negative dimensions are supported and count from the end.
187    pub fn var_mean_bias<I: AsIndex>(self, dim: I) -> (Self, Self) {
188        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var Mean Bias");
189        let mean = self.clone().mean_dim(dim);
190        let var = stats::var_with_mean_bias(self, mean.clone(), dim);
191        (var, mean)
192    }
193
194    /// Returns the median value along the specified dimension.
195    ///
196    /// The median is not unique for input tensors with an even number of elements
197    /// in the reduced dimension. In this case, the lower of the two medians is returned,
198    /// following PyTorch's behavior.
199    ///
200    /// # Note
201    ///
202    /// The current implementation performs a full sort along the specified dimension,
203    /// which has O(nlog(n)) complexity. Additionally, most backends currently fall back
204    /// to CPU for the sort operation, which may result in slower performance compared
205    /// to native GPU operations.
206    ///
207    /// # Arguments
208    ///
209    /// - `dim` - The dimension along which to compute the median.
210    ///   Negative dimensions are supported and count from the end.
211    ///
212    /// # Returns
213    ///
214    /// - A tensor containing the median values along the specified dimension.
215    ///
216    /// # Example 1
217    ///
218    /// ```ignore
219    /// let device = Default::default();
220    /// let tensor = Tensor::<2>::from_data(
221    ///     [[1.0, 5.0, 3.0, 2.0], [8.0, 4.0, 6.0, 7.0]],
222    ///     &device,
223    /// );
224    ///
225    /// // Median along dimension 0:
226    /// // sorted columns are [1.0, 8.0], [4.0, 5.0], [3.0, 6.0], [2.0, 7.0]
227    /// let median = tensor.median(0);
228    /// // Result: [[1.0, 4.0, 3.0, 2.0]]
229    ///
230    /// // Median along dimension 1:
231    /// // sorted rows are [1.0, 2.0, 3.0, 5.0] and [4.0, 6.0, 7.0, 8.0]
232    /// let median = tensor.median(1);
233    /// // Result: [[2.0], [6.0]]
234    /// ```
235    ///
236    /// # Example 2
237    ///
238    /// The median across all elements can be calculated as follows:
239    ///
240    /// ```ignore
241    /// // D is the number of dimensions of the tensor
242    /// let flattened_tensor: Tensor<1> = tensor.flatten(0, D - 1);
243    ///
244    /// // Calculate median for dim 0 since the tensor has become 1 dimensional
245    /// let median = flattened_tensor.median(0);
246    /// // Result: [4.0]
247    /// ```
248    pub fn median<I: AsIndex>(self, dim: I) -> Self {
249        let dim = unwrap_dim_index(dim.try_dim_index(D), "Median");
250        // TODO: Allow backend specialization. Optimally, implement a median kernel for cubecl
251        // instead of leveraging a full sort to get the median.
252        stats::median(self, dim)
253    }
254
255    /// Returns the median value along the specified dimension and its index.
256    ///
257    /// The median is not unique for input tensors with an even number of elements
258    /// in the reduced dimension. In this case, the lower of the two medians is returned,
259    /// following PyTorch's behavior.
260    ///
261    /// # Note
262    ///
263    /// The current implementation performs a full sort along the specified dimension,
264    /// which has O(nlog(n)) complexity. Additionally, most backends currently fall back
265    /// to CPU for the sort operation, which may result in slower performance compared
266    /// to native GPU operations.
267    ///
268    /// # Arguments
269    ///
270    /// - `dim` - The dimension along which to compute the median.
271    ///   Negative dimensions are supported and count from the end.
272    ///
273    /// # Returns
274    ///
275    /// A tuple containing:
276    /// - A tensor with the median values.
277    /// - A tensor with the indices of the median values in the original tensor.
278    ///
279    /// # Example
280    ///
281    /// ```ignore
282    /// let device = Default::default();
283    /// let tensor = Tensor::<2>::from_data(
284    ///     [[1.0, 5.0, 3.0, 2.0], [8.0, 4.0, 6.0, 7.0]],
285    ///     &device,
286    /// );
287    ///
288    /// // Median along dimension 1:
289    /// // sorted rows are [1.0, 2.0, 3.0, 5.0] and [4.0, 6.0, 7.0, 8.0]
290    /// let (values, indices) = tensor.median_with_indices(1);
291    /// // values: [[2.0], [6.0]], indices: [[3], [2]] (position in the original tensor)
292    /// ```
293    pub fn median_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
294        let dim = unwrap_dim_index(dim.try_dim_index(D), "Median With Indices");
295        // TODO: Allow backend specialization. Optimally, implement a median kernel for cubecl
296        // instead of leveraging a full sort to get the median.
297        stats::median_with_indices(self, dim)
298    }
299
300    /// Converts a tensor to the specified data type.
301    ///
302    /// Supports both within-kind casting (e.g., `FloatDType::F64`) and cross-kind casting
303    /// (e.g., `IntDType::I64` to produce an int tensor).
304    ///
305    /// This is a no-op when casting to the current dtype within the same kind.
306    ///
307    /// # Example
308    ///
309    /// ```rust
310    /// use burn_tensor::{Tensor, FloatDType, IntDType};
311    ///
312    /// let device = Default::default();
313    /// let float_tensor = Tensor::<1>::from_floats([1.0, 2.5], &device);
314    ///
315    /// // Within-kind cast (float to float)
316    /// let f64_tensor = float_tensor.clone().cast(FloatDType::F64);
317    ///
318    /// // Cross-kind cast (float to int)
319    /// let int_tensor = float_tensor.cast(IntDType::I64);
320    /// ```
321    #[must_use]
322    pub fn cast<T: Cast<D, Float>>(self, dtype: T) -> Tensor<D, T::OutputKind> {
323        T::cast(self, dtype)
324    }
325
326    /// Detach the current tensor from the autodiff graph.
327    ///
328    /// The returned tensor keeps its autodiff association but starts a new graph lineage, so
329    /// previous operations aren't considered during backward. A leaf tensor also preserves its
330    /// current `require_grad` setting. This function does nothing when autodiff isn't enabled.
331    #[must_use]
332    pub fn detach(self) -> Self {
333        Self::new(detach_impl(self.primitive))
334    }
335
336    /// Mark the tensor to keep gradients during the backward pass.
337    ///
338    /// This function does nothing when the tensor is quantized.
339    /// Enabling gradient retention doesn't enable autodiff; use [`autodiff`](Tensor::autodiff)
340    /// first when needed.
341    ///
342    /// # Panics
343    ///
344    /// For non-quantized tensors, panics if autodiff is disabled or the tensor is a non-leaf.
345    /// Enable autodiff with [`autodiff`](Tensor::autodiff) before requesting gradients.
346    /// Use [`detach`](Tensor::detach) first to start a new graph lineage.
347    #[must_use]
348    pub fn require_grad(self) -> Self {
349        self.set_require_grad(true)
350    }
351
352    /// Returns whether this tensor's gradient is retained after backward.
353    ///
354    /// This is distinct from [`Tensor::is_autodiff`], which reports whether operations use an
355    /// autodiff context, and `Tensor::is_tracked()`, which reports graph participation when the
356    /// `autodiff` feature is enabled.
357    pub fn is_require_grad(&self) -> bool {
358        is_require_grad_impl(&self.primitive)
359    }
360
361    /// Sets whether this tensor's gradient is retained after backward.
362    ///
363    /// This function does nothing when the tensor is quantized.
364    /// Setting this to `false` on a non-leaf tensor starts a new graph lineage, like
365    /// [`detach`](Tensor::detach), while leaving gradient retention disabled.
366    ///
367    /// # Panics
368    ///
369    /// Panics when setting this to `true` without autodiff or on a non-leaf tensor.
370    /// Enable autodiff with [`autodiff`](Tensor::autodiff) first. Setting this to `false`
371    /// on a tensor without autodiff is harmless. Quantized tensors remain unchanged.
372    #[must_use]
373    pub fn set_require_grad(self, require_grad: bool) -> Self {
374        Self::new(set_require_grad_impl(self.primitive, require_grad))
375    }
376
377    /// Applies the relu function to the tensor.
378    pub(crate) fn relu(self) -> Self {
379        Self::new(relu_impl(self.primitive))
380    }
381
382    /// Calculate covaraince matrix between different entries alongside a given dimension.
383    ///
384    /// # Arguments
385    ///
386    /// * `dim` - The dimension along which to calculate the covariance.
387    ///   Negative dimensions are supported and count from the end.
388    /// * `correction_factor` - Is usually 1 for samples and 0 for population.
389    pub fn cov<I: AsIndex>(self, dim: I, correction_factor: usize) -> Tensor<D> {
390        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cov");
391        let n = self.dims()[dim];
392        let centered = (self.clone() - self.mean_dim(dim)).swap_dims(dim, 0);
393        centered
394            .clone()
395            .transpose()
396            .matmul(centered)
397            .div_scalar(n as f32 - correction_factor as f32)
398    }
399
400    /// Convert the tensor to a lower precision data type based on the quantization scheme.
401    ///
402    /// # Arguments
403    ///
404    /// * `scheme` - The quantization scheme.
405    /// * `qparams` - The pre-computed quantization parameters.
406    ///
407    /// # Returns
408    ///
409    /// The quantized tensor.
410    pub fn quantize(self, scheme: &QuantScheme, qparams: QuantizationParameters) -> Tensor<D> {
411        assert_eq!(
412            global_scale_dtype(scheme).is_some(),
413            qparams.global.is_some(),
414            "{scheme:?} does not match a per-tensor scale of {:?}",
415            qparams.global,
416        );
417        if let Some(global) = &qparams.global {
418            assert_eq!(
419                global.dims()[0],
420                1,
421                "the per-tensor scale must have exactly one element, got {:?}",
422                global.dims()
423            );
424            assert_eq!(
425                global.dtype(),
426                DType::F32,
427                "the per-tensor scale must be an f32 tensor, got {:?}",
428                global.dtype()
429            );
430        }
431        Tensor::new(quantize_impl(
432            self.primitive,
433            scheme,
434            qparams.scales.primitive,
435            qparams.global.map(|global| global.primitive),
436        ))
437    }
438
439    /// Dynamically convert the tensor to a lower precision data type based on the quantization scheme.
440    ///
441    /// # Arguments
442    ///
443    /// * `scheme` - The quantization scheme.
444    ///
445    /// # Returns
446    ///
447    /// The quantized tensor.
448    ///
449    /// # Notes
450    /// This uses [min-max calibration](crate::quantization::Calibration::MinMax).
451    pub fn quantize_dynamic(self, scheme: &QuantScheme) -> Tensor<D> {
452        Tensor::new(quantize_dynamic_impl(self.primitive, scheme))
453    }
454
455    /// Convert the tensor back to a higher precision data type.
456    ///
457    /// If the tensor is not quantized, its value is simply returned.
458    ///
459    /// # Returns
460    ///
461    /// The dequantized tensor.
462    pub fn dequantize(self) -> Tensor<D> {
463        Tensor::new(dequantize_impl(self.primitive))
464    }
465
466    /// Checks element wise if the tensor is close to another tensor.
467    ///
468    /// The tolerance is defined by the following equation:
469    ///
470    /// ```text
471    /// abs(a - b) <= (atol + rtol * abs(b))
472    ///
473    /// where `a` is the first tensor, `b` is the second tensor, `rtol` is the relative tolerance,
474    /// and `atol` is the absolute tolerance.
475    /// ```
476    ///
477    /// # Arguments
478    ///
479    /// * `other` - The tensor to compare with.
480    /// * `rtol` - Optional relative tolerance. Default is 1e-5; see `DEFAULT_RTOL`.
481    /// * `atol` - Optional absolute tolerance. Default is 1e-8; see `DEFAULT_ATOL`.
482    ///
483    /// # Returns
484    ///
485    /// A boolean tensor with the same shape as the input tensors.
486    ///
487    /// # Example
488    ///
489    /// ```rust
490    /// use burn_tensor::{Tensor, Shape};
491    ///
492    /// let device = Default::default();
493    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
494    /// let tensor2 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
495    /// let tensor = tensor1.is_close(tensor2, None, None);
496    /// println!("{tensor}");
497    /// // [[true, true, true], [true, true, true]]
498    /// ```
499    pub fn is_close(self, other: Self, rtol: Option<f64>, atol: Option<f64>) -> Tensor<D, Bool> {
500        let rtol = rtol.unwrap_or(DEFAULT_RTOL);
501        let atol = atol.unwrap_or(DEFAULT_ATOL);
502
503        // check finite difference is close
504        let is_close_finite_val = self
505            .clone()
506            .sub(other.clone())
507            .abs()
508            .lower_equal(other.clone().abs().mul_scalar(rtol).add_scalar(atol))
509            .bool_and(self.clone().is_finite())
510            .bool_and(other.clone().is_finite());
511
512        // check if both are infinite and have same sign
513        let inf_same_sign = self
514            .clone()
515            .is_finite()
516            .bool_not()
517            .bool_and(other.clone().is_finite().bool_not())
518            .bool_and(self.equal(other));
519
520        is_close_finite_val.bool_or(inf_same_sign)
521    }
522
523    /// Checks if all elements are close to another tensor.
524    ///
525    /// The tolerance is defined by the following equation:
526    ///
527    /// ```text
528    ///
529    /// abs(a - b) <= (atol + rtol * abs(b))
530    ///
531    /// where `a` is the first tensor, `b` is the second tensor, `rtol` is the relative tolerance,
532    /// and `atol` is the absolute tolerance.
533    ///
534    /// ```
535    ///
536    /// # Arguments
537    ///
538    /// * `other` - The tensor to compare with.
539    /// * `rtol` - Optional relative tolerance. Default is 1e-5; see `DEFAULT_RTOL`.
540    /// * `atol` - Optional absolute tolerance. Default is 1e-8; see `DEFAULT_ATOL`.
541    ///
542    /// # Returns
543    ///
544    /// A boolean scalar.
545    ///
546    /// # Remarks
547    ///
548    /// # Example
549    ///
550    /// ```rust
551    /// use burn_tensor::{Tensor, Shape};
552    ///
553    /// let device = Default::default();
554    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
555    /// let tensor2 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
556    /// let result = tensor1.all_close(tensor2, None, None);
557    /// println!("{}", result);
558    /// // true
559    /// ```
560    pub fn all_close(self, other: Self, rtol: Option<f64>, atol: Option<f64>) -> bool {
561        self.is_close(other, rtol, atol)
562            .all()
563            .into_scalar::<u8>()
564            .to_bool()
565    }
566
567    /// Returns a new tensor with boolean elements indicating whether each element of the input is NaN.
568    ///
569    /// # Returns
570    ///
571    /// A boolean tensor where `true` indicates NaN and `false` indicates a non-NaN value.
572    ///
573    /// # Example
574    ///
575    /// ```rust
576    /// use burn_tensor::{Tensor, Bool, Shape};
577    ///
578    /// let device = Default::default();
579    /// let tensor = Tensor::<2>::from_data([[1.0, f64::NAN, 3.0], [5.0, 9.0, 6.0]], &device);
580    /// let tensor = tensor.is_nan();
581    /// println!("{tensor}");
582    /// // [[false, true, false], [false, false, false]]
583    /// ```
584    pub fn is_nan(self) -> Tensor<D, Bool> {
585        Tensor::new(is_nan_impl(self.primitive))
586    }
587
588    /// Checks if the tensor contains any NaN values.
589    ///
590    /// # Returns
591    ///
592    /// A boolean tensor with a single element indicating whether the tensor contains any NaN values.
593    ///
594    /// # Example
595    ///
596    /// ```rust
597    /// use burn_tensor::{Tensor, Bool, Shape};
598    ///
599    /// let device = Default::default();
600    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [f64::NAN, 9.0, 6.0]], &device);
601    /// let tensor = tensor.contains_nan();
602    /// println!("{tensor}");
603    /// // [true]
604    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
605    /// let tensor = tensor.contains_nan();
606    /// println!("{tensor}");
607    /// // [false]
608    /// ```
609    pub fn contains_nan(self) -> Tensor<1, Bool> {
610        self.is_nan().any()
611    }
612
613    /// Returns a new tensor with boolean elements indicating whether each element of the input is infinite (either +INF or -INF).
614    ///
615    /// # Returns
616    ///
617    /// A boolean tensor where `true` indicates that the value is infinite
618    ///
619    /// # Example
620    ///
621    /// ```rust
622    /// use burn_tensor::{Tensor, Bool, Shape};
623    ///
624    /// let device = Default::default();
625    /// let tensor = Tensor::<2>::from_data([[1.0, f64::INFINITY, 3.0], [f64::NAN, 9.0, 6.0]], &device);
626    /// let tensor = tensor.is_finite();
627    /// println!("{tensor}");
628    /// // [[false, true, false], [false, false, false]]
629    /// ```
630    pub fn is_inf(self) -> Tensor<D, Bool> {
631        Tensor::new(is_inf_impl(self.primitive))
632    }
633
634    /// Returns a new tensor with boolean elements indicating whether each element of the input is finite
635    ///
636    /// # Returns
637    ///
638    /// A boolean tensor where `true` indicates that the value is finite and `false` indicates
639    /// either INF, -INF or NAN
640    ///
641    /// # Example
642    ///
643    /// ```rust
644    /// use burn_tensor::{Tensor, Bool, Shape};
645    ///
646    /// let device = Default::default();
647    /// let tensor = Tensor::<2>::from_data([[1.0, f64::INFINITY, 3.0], [f64::NAN, 9.0, 6.0]], &device);
648    /// let tensor = tensor.is_finite();
649    /// println!("{tensor}");
650    /// // [[true, false, true], [false, true, true]]
651    /// ```
652    pub fn is_finite(self) -> Tensor<D, Bool> {
653        self.clone()
654            .is_nan()
655            .bool_not()
656            .bool_and(self.is_inf().bool_not())
657    }
658
659    /// Samples tensor as a two-dimensional spatial grid of (possibly multi-channel) values,
660    /// using the given locations in [-1, 1].
661    ///
662    /// # Arguments
663    ///
664    /// * `grid` - A tensor of locations, with shape (N, H_out, W_out, 2). Values are [-1, 1].
665    ///   A [x = -1, y = -1] means top-left, and [x = 1, y = 1] means bottom-right
666    /// * `options` - Grid sampling options (mode, padding_mode, align_corners)
667    ///
668    /// # Returns
669    ///
670    /// A tensor with shape (N, C, H_out, W_out)
671    ///
672    /// # Example
673    ///
674    /// ```ignore
675    /// use burn_tensor::ops::{GridSampleOptions, GridSamplePaddingMode, InterpolateMode};
676    ///
677    /// // Default options (bilinear, zeros padding, align_corners=false)
678    /// let output = tensor.grid_sample_2d(grid, GridSampleOptions::default());
679    ///
680    /// // Custom options
681    /// let options = GridSampleOptions::new(InterpolateMode::Bilinear)
682    ///     .with_padding_mode(GridSamplePaddingMode::Border)
683    ///     .with_align_corners(true);
684    /// let output = tensor.grid_sample_2d(grid, options);
685    /// ```
686    pub fn grid_sample_2d(
687        self,
688        grid: Tensor<D>,
689        options: impl Into<GridSampleOptions>,
690    ) -> Tensor<D> {
691        Tensor::new(grid_sample_2d_impl(
692            self.primitive,
693            grid.primitive,
694            options.into(),
695        ))
696    }
697
698    /// Computes the cross product of `self` and another tensor along a given dimension.
699    ///
700    /// Both `self` and `other` **must have size 3** along the specified `dim`,
701    /// because the cross product is only defined in three-dimensional space.
702    ///
703    /// # Arguments
704    ///
705    /// * `other` - The other tensor to take the cross product with.
706    /// * `dim`   - The dimension along which to compute the cross product.
707    ///
708    /// # Returns
709    ///
710    /// A tensor containing the cross product of `self` and `other` along `dim`.
711    pub fn cross<Dim: AsIndex>(self, other: Tensor<D>, dim: Dim) -> Tensor<D> {
712        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cross");
713        check!(TensorCheck::cross(&self, &other, dim));
714        Tensor::new(cross_impl(self.primitive, other.primitive, dim))
715    }
716
717    /// Applies element wise power operation with a float Tensor
718    ///
719    /// # Arguments
720    ///
721    /// * `other` - The tensor to apply the power operation with.
722    ///
723    /// # Example
724    ///
725    /// ```rust
726    /// use burn_tensor::{Tensor, Shape};
727    ///
728    /// let device = Default::default();
729    /// let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
730    /// let tensor2 = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
731    /// let tensor = tensor1.powf(tensor2);
732    /// println!("{tensor}");
733    /// // [[1.0, 8.0, 81.0], [5.0, 81.0, 216.0]]
734    /// ```
735    pub fn powf(self, other: Self) -> Self {
736        check!(TensorCheck::binary_ops_ew("Powf", &self, &other));
737        Tensor::new(powf_impl(self.primitive, other.primitive))
738    }
739
740    /// Applies element wise power operation with a float scalar
741    ///
742    /// # Arguments
743    ///
744    /// * `other` - The scalar to apply the power operation with.
745    ///
746    /// # Example
747    ///
748    /// ```rust
749    /// use burn_tensor::{Tensor, Shape};
750    ///
751    /// let device = Default::default();
752    /// let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
753    /// let tensor = tensor.powf_scalar(2.0);
754    /// println!("{tensor}");
755    /// // [[1.0, 4.0, 9.0], [25.0, 81.0, 36.0]]
756    /// ```
757    pub fn powf_scalar<E: ElementConversion>(self, other: E) -> Self {
758        let rhs = Scalar::new(other, &self.dtype());
759        Tensor::new(powf_scalar_impl(self.primitive, rhs))
760    }
761}
762
763impl<const D: usize> Tensor<D> {
764    /// Draws samples from a categorical distribution defined by the last dimension
765    /// of the input tensor.
766    ///
767    /// The last dimension is treated as a (possibly unnormalized) set of weights
768    /// defining a categorical distribution over categories. All leading dimensions
769    /// are treated as batch dimensions. The method returns integer indices of the
770    /// sampled categories.
771    ///
772    /// # Arguments
773    ///
774    /// * `num_samples` - Number of samples to draw per distribution. Must be >= 1.
775    ///
776    /// # Panics
777    ///
778    /// Panics if `num_samples` is 0.
779    ///
780    /// # Note
781    ///
782    /// Distributions with all-zero weights produce undefined (NaN-based) sampling
783    /// results. Callers should ensure each distribution has at least one positive
784    /// weight.
785    ///
786    /// # Returns
787    ///
788    /// An integer tensor with the same shape as the input, except the last dimension
789    /// is replaced by `num_samples`, containing sampled category indices in
790    /// `[0, num_categories)`.
791    ///
792    /// # Example
793    ///
794    /// ```rust
795    /// use burn_tensor::Tensor;
796    ///
797    /// let device = Default::default();
798    /// let probs = Tensor::<2>::from_floats(
799    ///     [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
800    ///     &device,
801    /// );
802    /// let samples = probs.categorical(4);
803    /// // First row always samples index 1, second row always samples index 2
804    /// println!("{samples}");
805    /// ```
806    pub fn categorical(self, num_samples: usize) -> Tensor<D, Int> {
807        assert!(num_samples > 0, "categorical: num_samples must be >= 1");
808
809        let shape = self.shape();
810        let num_categories = shape[D - 1];
811        let batch_size = (shape.num_elements() / num_categories).max(1);
812        let device = self.device();
813        let dtype = self.dtype();
814
815        // Flatten leading dimensions into a single batch dimension: [batch, categories]
816        let flat: Tensor<2> = self.reshape([batch_size, num_categories]);
817
818        // Normalize weights to probabilities
819        let sum = flat.clone().sum_dim(1); // [batch, 1]
820        let probs = flat / sum;
821
822        // Cumulative sum along categories dimension
823        let cumsum = probs.cumsum(1); // [batch, categories]
824
825        // Uniform random values for each sample
826        let uniform = Tensor::<2>::random(
827            [batch_size, num_samples],
828            Distribution::Uniform(0.0, 1.0),
829            (&device, dtype),
830        ); // [batch, num_samples]
831
832        // Expand dimensions for broadcasting:
833        //   cumsum: [batch, categories, 1]
834        //   uniform: [batch, 1, num_samples]
835        let cumsum_3d: Tensor<3> = cumsum.unsqueeze_dim(2);
836        let uniform_3d: Tensor<3> = uniform.unsqueeze_dim(1);
837
838        // Count categories where cumsum < uniform (inverse CDF)
839        let mask: Tensor<3, Bool> = cumsum_3d.lower(uniform_3d);
840        let indices: Tensor<2, Int> = mask.int().sum_dim(1).squeeze_dim::<2>(1);
841
842        // Clamp to valid range to guard against floating-point imprecision in cumsum
843        let indices = indices.clamp(0, num_categories as i64 - 1);
844
845        // Reshape back to [...leading_dims, num_samples]
846        let mut out_shape = shape;
847        out_shape[D - 1] = num_samples;
848        indices.reshape(out_shape)
849    }
850}
851
852#[cfg(feature = "std")]
853impl<const D: usize> Tensor<D> {
854    /// Returns true if the tensor is marked as distributed.
855    pub fn is_distributed(&self) -> bool {
856        is_distributed_impl(&self.primitive)
857    }
858
859    /// Mark the tensor as distributed.
860    ///
861    /// This function does nothing when autodiff or distributed is not enabled.
862    pub fn set_distributed(self, param_id: DistributedParamId) -> Self {
863        Self::new(set_distributed_impl(self.primitive, param_id))
864    }
865}
866
867impl<const D: usize, K> Tensor<D, K>
868where
869    K: FloatMath,
870{
871    /// Applies element wise square operation.
872    ///
873    #[cfg_attr(doc, doc = r#"$y_i = x_i * x_i$"#)]
874    #[cfg_attr(not(doc), doc = "`y_i = x_i * x_i`")]
875    pub fn square(self) -> Self {
876        Self::new(K::square(self.primitive))
877    }
878
879    /// Applies element wise exponential operation.
880    ///
881    #[cfg_attr(doc, doc = r#"$y_i = e^{x_i}$"#)]
882    #[cfg_attr(not(doc), doc = "`y = e^x`")]
883    pub fn exp(self) -> Self {
884        Self::new(K::exp(self.primitive))
885    }
886
887    /// Applies element wise natural logarithm of one plus the input tensor.
888    ///
889    #[cfg_attr(doc, doc = r#"$y_i = \log_e\(x_i + 1\)$"#)]
890    #[cfg_attr(not(doc), doc = "`y_i = log1p(x_i)`")]
891    pub fn log1p(self) -> Self {
892        Self::new(K::log1p(self.primitive))
893    }
894
895    /// Applies element wise natural log operation *ln*.
896    ///
897    #[cfg_attr(doc, doc = r#"$y_i = \log_e\(x_i\)$"#)]
898    #[cfg_attr(not(doc), doc = "`y_i = log(x_i)`")]
899    pub fn log(self) -> Self {
900        Self::new(K::log(self.primitive))
901    }
902
903    /// Applies element wise square root operation.
904    ///
905    pub fn sqrt(self) -> Self {
906        Tensor::new(K::sqrt(self.primitive))
907    }
908    /// Applies element wise cosine operation.
909    ///
910    #[cfg_attr(doc, doc = r#"$y_i = \cos\(x_i\)$"#)]
911    #[cfg_attr(not(doc), doc = "`y_i = cos(x_i)`")]
912    pub fn cos(self) -> Self {
913        Tensor::new(K::cos(self.primitive))
914    }
915
916    /// Applies element wise sine operation.
917    ///
918    #[cfg_attr(doc, doc = r#"$y_i = \sin\(x_i\)$"#)]
919    #[cfg_attr(not(doc), doc = "`y_i = sin(x_i)`")]
920    pub fn sin(self) -> Self {
921        Tensor::new(K::sin(self.primitive))
922    }
923
924    /// Applies element wise tangent operation.
925    ///
926    #[cfg_attr(doc, doc = r#"$y_i = \tan\(x_i\)$"#)]
927    #[cfg_attr(not(doc), doc = "`y_i = tan(x_i)`")]
928    pub fn tan(self) -> Self {
929        Tensor::new(K::tan(self.primitive))
930    }
931
932    /// Applies element wise hyperbolic cosine operation.
933    ///
934    #[cfg_attr(doc, doc = r#"$y_i = \cosh\(x_i\)$"#)]
935    #[cfg_attr(not(doc), doc = "`y_i = cosh(x_i)`")]
936    ///
937    /// # Example
938    ///
939    /// ```rust
940    /// use burn_tensor::Tensor;
941    ///
942    /// let device = Default::default();
943    ///
944    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
945    /// println!("{}", tensor.cosh()); // [1.0, 1.5430, 3.7621]
946    /// ```
947    pub fn cosh(self) -> Self {
948        Tensor::new(K::cosh(self.primitive))
949    }
950
951    /// Applies element wise hyperbolic sine operation.
952    ///
953    #[cfg_attr(doc, doc = r#"$y_i = \sinh\(x_i\)$"#)]
954    #[cfg_attr(not(doc), doc = "`y_i = sinh(x_i)`")]
955    ///
956    /// # Example
957    ///
958    /// ```rust
959    /// use burn_tensor::Tensor;
960    ///
961    /// let device = Default::default();
962    ///
963    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
964    /// println!("{}", tensor.sinh()); // [0.0, -1.1752, 3.6269]
965    /// ```
966    pub fn sinh(self) -> Self {
967        Tensor::new(K::sinh(self.primitive))
968    }
969
970    /// Applies element wise hyperbolic tangent operation.
971    ///
972    #[cfg_attr(doc, doc = r#"$y_i = \tanh\(x_i\)$"#)]
973    #[cfg_attr(not(doc), doc = "`y_i = tanh(x_i)`")]
974    ///
975    /// # Example
976    ///
977    /// ```rust
978    /// use burn_tensor::Tensor;
979    ///
980    /// let device = Default::default();
981    ///
982    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
983    /// println!("{}", tensor.tanh()); // [0.0, -0.7616, 0.9640]
984    /// ```
985    pub fn tanh(self) -> Self {
986        Tensor::new(K::tanh(self.primitive))
987    }
988
989    /// Applies element wise inverse cosine operation.
990    ///
991    #[cfg_attr(doc, doc = r#"$y_i = \acos\(x_i\)$"#)]
992    #[cfg_attr(not(doc), doc = "`y_i = acos(x_i)`")]
993    ///
994    /// # Example
995    ///
996    /// ```rust
997    /// use burn_tensor::Tensor;
998    ///
999    /// let device = Default::default();
1000    ///
1001    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 1.0], &device);
1002    /// println!("{}", tensor.acos()); // [1.5708, 3.1416, 0.0]
1003    /// ```
1004    pub fn acos(self) -> Self {
1005        Tensor::new(K::acos(self.primitive))
1006    }
1007
1008    /// Applies element wise inverse hyperbolic cosine operation.
1009    ///
1010    #[cfg_attr(doc, doc = r#"$y_i = \acosh\(x_i\)$"#)]
1011    #[cfg_attr(not(doc), doc = "`y_i = acosh(x_i)`")]
1012    ///
1013    /// # Example
1014    ///
1015    /// ```rust
1016    /// use burn_tensor::Tensor;
1017    ///
1018    /// let device = Default::default();
1019    ///
1020    /// let tensor = Tensor::<1>::from_data([1.0, 2.0, 3.0], &device);
1021    /// println!("{}", tensor.acosh()); // [0.0000, 1.3170, 1.7627]
1022    /// ```
1023    pub fn acosh(self) -> Self {
1024        Tensor::new(K::acosh(self.primitive))
1025    }
1026
1027    /// Applies element wise inverse sine operation.
1028    ///
1029    #[cfg_attr(doc, doc = r#"$y_i = \asin\(x_i\)$"#)]
1030    #[cfg_attr(not(doc), doc = "`y_i = asin(x_i)`")]
1031    ///
1032    /// # Example
1033    ///
1034    /// ```rust
1035    /// use burn_tensor::Tensor;
1036    ///
1037    /// let device = Default::default();
1038    ///
1039    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 1.0], &device);
1040    /// println!("{}", tensor.asin()); // [ 0.0000, -1.5708,  1.5708]
1041    /// ```
1042    pub fn asin(self) -> Self {
1043        Tensor::new(K::asin(self.primitive))
1044    }
1045
1046    /// Applies element wise inverse hyperbolic sine operation.
1047    ///
1048    #[cfg_attr(doc, doc = r#"$y_i = \asinh\(x_i\)$"#)]
1049    #[cfg_attr(not(doc), doc = "`y_i = asinh(x_i)`")]
1050    ///
1051    /// # Example
1052    ///
1053    /// ```rust
1054    /// use burn_tensor::Tensor;
1055    ///
1056    /// let device = Default::default();
1057    ///
1058    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 1.0], &device);
1059    /// println!("{}", tensor.asinh()); // [ 0.0000, -0.8814,  0.8814]
1060    /// ```
1061    pub fn asinh(self) -> Self {
1062        Tensor::new(K::asinh(self.primitive))
1063    }
1064
1065    /// Applies element wise inverse tangent operation.
1066    ///
1067    #[cfg_attr(doc, doc = r#"$y_i = \atan\(x_i\)$"#)]
1068    #[cfg_attr(not(doc), doc = "`y_i = atan(x_i)`")]
1069    ///
1070    /// # Example
1071    ///
1072    /// ```rust
1073    /// use burn_tensor::Tensor;
1074    ///
1075    /// let device = Default::default();
1076    ///
1077    /// let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
1078    /// println!("{}", tensor.atan()); // [ 0.0, -0.7854,  1.1071]
1079    /// ```
1080    pub fn atan(self) -> Self {
1081        Tensor::new(K::atan(self.primitive))
1082    }
1083
1084    /// Applies element wise inverse hyperbolic tangent operation.
1085    ///
1086    #[cfg_attr(doc, doc = r#"$y_i = \atanh\(x_i\)$"#)]
1087    #[cfg_attr(not(doc), doc = "`y_i = atanh(x_i)`")]
1088    ///
1089    /// # Example
1090    ///
1091    /// ```rust
1092    /// use burn_tensor::Tensor;
1093    ///
1094    /// let device = Default::default();
1095    ///
1096    /// let tensor = Tensor::<1>::from_data([0.0, -0.5, 0.5], &device);
1097    /// println!("{}", tensor.atanh()); // [ 0.0, -0.5493,  0.5493]
1098    /// ```
1099    pub fn atanh(self) -> Self {
1100        Tensor::new(K::atanh(self.primitive))
1101    }
1102
1103    /// Applies element wise inverse tangent operation using the signs of arguments to determine the correct quadrant.
1104    ///
1105    #[cfg_attr(doc, doc = r#"$z_i = \atan2\(y_i, x_i\)$"#)]
1106    #[cfg_attr(not(doc), doc = "`z_i = atan2(y_i, x_i)`")]
1107    ///
1108    /// # Example
1109    ///
1110    /// ```rust
1111    /// use burn_tensor::Tensor;
1112    ///
1113    /// let device = Default::default();
1114    ///
1115    /// let lhs = Tensor::<1>::from_data([-2.0, 2.0, -2.0], &device);
1116    /// let rhs = Tensor::<1>::from_data([1.0, -1.0, -1.0], &device);
1117    /// println!("{}", lhs.atan2(rhs)); // [-1.1071,  2.0344, -2.0344]
1118    /// ```
1119    pub fn atan2(self, other: Self) -> Self {
1120        check!(TensorCheck::binary_ops_ew("Atan2", &self, &other));
1121        Tensor::new(K::atan2(self.primitive, other.primitive))
1122    }
1123}
1124
1125// =========================================================================
1126// Non-generic implementation helpers (outlined from the public generic API).
1127// See the crate-level docs for the rationale behind this pattern.
1128// =========================================================================
1129
1130fn erf_impl(p: BridgeTensor) -> BridgeTensor {
1131    BridgeTensor::float(Dispatch::float_erf(p.into_float()))
1132}
1133
1134fn recip_impl(p: BridgeTensor) -> BridgeTensor {
1135    BridgeTensor::float(Dispatch::float_recip(p.into_float()))
1136}
1137
1138fn hypot_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
1139    BridgeTensor::float(Dispatch::float_hypot(lhs.into_float(), rhs.into_float()))
1140}
1141
1142fn round_impl(p: BridgeTensor) -> BridgeTensor {
1143    BridgeTensor::float(Dispatch::float_round(p.into_float()))
1144}
1145
1146fn floor_impl(p: BridgeTensor) -> BridgeTensor {
1147    BridgeTensor::float(Dispatch::float_floor(p.into_float()))
1148}
1149
1150fn ceil_impl(p: BridgeTensor) -> BridgeTensor {
1151    BridgeTensor::float(Dispatch::float_ceil(p.into_float()))
1152}
1153
1154fn int_impl(p: BridgeTensor, device: Device) -> BridgeTensor {
1155    let out_dtype = device.get_or_init_settings().int_dtype;
1156    BridgeTensor::int(Dispatch::float_into_int(p.into_float(), out_dtype))
1157}
1158
1159fn random_like_impl(p: &BridgeTensor, distribution: Distribution) -> BridgeTensor {
1160    BridgeTensor::float(Dispatch::float_random(
1161        p.shape(),
1162        distribution,
1163        &p.as_dispatch().device(),
1164        p.dtype().into(),
1165    ))
1166}
1167
1168fn detach_impl(p: BridgeTensor) -> BridgeTensor {
1169    BridgeTensor::float(Dispatch::float_detach(p.into_float()))
1170}
1171
1172fn is_require_grad_impl(p: &BridgeTensor) -> bool {
1173    let (kind, tensor) = p.as_parts();
1174    match kind {
1175        BridgeKind::Float => Dispatch::float_is_require_grad(tensor),
1176        BridgeKind::QFloat => Dispatch::q_is_require_grad(tensor),
1177        _ => panic!("Should be Float primitive kind"),
1178    }
1179}
1180
1181fn set_require_grad_impl(p: BridgeTensor, require_grad: bool) -> BridgeTensor {
1182    let (kind, tensor) = p.into_parts();
1183    match kind {
1184        BridgeKind::Float => {
1185            assert!(
1186                !require_grad
1187                    || tensor.autodiff != burn_dispatch::DispatchAutodiffContext::Disabled,
1188                "Tensor::require_grad requires autodiff; call Tensor::autodiff first"
1189            );
1190            BridgeTensor::float(Dispatch::float_set_require_grad(tensor, require_grad))
1191        }
1192        BridgeKind::QFloat => {
1193            BridgeTensor::qfloat(Dispatch::q_set_require_grad(tensor, require_grad))
1194        }
1195        _ => panic!("Should be Float primitive kind"),
1196    }
1197}
1198
1199fn relu_impl(p: BridgeTensor) -> BridgeTensor {
1200    BridgeTensor::float(Dispatch::relu(p.into_float()))
1201}
1202
1203fn quantize_impl(
1204    p: BridgeTensor,
1205    scheme: &QuantScheme,
1206    scales: BridgeTensor,
1207    global: Option<BridgeTensor>,
1208) -> BridgeTensor {
1209    BridgeTensor::qfloat(Dispatch::quantize(
1210        p.into_float(),
1211        scheme,
1212        QuantizationParametersPrimitive {
1213            scales: scales.into_float(),
1214            global: global.map(|global| global.into_float()),
1215        },
1216    ))
1217}
1218
1219fn quantize_dynamic_impl(p: BridgeTensor, scheme: &QuantScheme) -> BridgeTensor {
1220    BridgeTensor::qfloat(Dispatch::quantize_dynamic(p.into_float(), scheme))
1221}
1222
1223fn dequantize_impl(p: BridgeTensor) -> BridgeTensor {
1224    BridgeTensor::float(p.into_float())
1225}
1226
1227fn is_nan_impl(p: BridgeTensor) -> BridgeTensor {
1228    let bool_dtype = p.device_settings().bool_dtype;
1229    BridgeTensor::bool(Dispatch::float_is_nan(p.into_float(), bool_dtype))
1230}
1231
1232fn is_inf_impl(p: BridgeTensor) -> BridgeTensor {
1233    let bool_dtype = p.device_settings().bool_dtype;
1234    BridgeTensor::bool(Dispatch::float_is_inf(p.into_float(), bool_dtype))
1235}
1236
1237fn grid_sample_2d_impl(
1238    p: BridgeTensor,
1239    grid: BridgeTensor,
1240    options: GridSampleOptions,
1241) -> BridgeTensor {
1242    BridgeTensor::float(Dispatch::float_grid_sample_2d(
1243        p.into_float(),
1244        grid.into_float(),
1245        options,
1246    ))
1247}
1248
1249fn cross_impl(p: BridgeTensor, other: BridgeTensor, dim: usize) -> BridgeTensor {
1250    BridgeTensor::float(Dispatch::float_cross(
1251        p.into_float(),
1252        other.into_float(),
1253        dim,
1254    ))
1255}
1256
1257fn powf_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
1258    let (lkind, lhs) = lhs.into_parts();
1259    let (rkind, rhs) = rhs.into_parts();
1260    match (lkind, rkind) {
1261        (BridgeKind::Float, BridgeKind::Float) => {
1262            BridgeTensor::float(Dispatch::float_powf(lhs, rhs))
1263        }
1264        (BridgeKind::QFloat, BridgeKind::QFloat) => match Dispatch::q_powf(lhs, rhs) {
1265            TensorPrimitive::Float(out) => BridgeTensor::float(out),
1266            TensorPrimitive::QFloat(out) => BridgeTensor::qfloat(out),
1267        },
1268        (BridgeKind::QFloat, BridgeKind::Float) => {
1269            let dtype = rhs.dtype();
1270            BridgeTensor::float(Dispatch::float_powf(
1271                Dispatch::dequantize(lhs, dtype.into()),
1272                rhs,
1273            ))
1274        }
1275        (BridgeKind::Float, BridgeKind::QFloat) => {
1276            let dtype = lhs.dtype();
1277            BridgeTensor::float(Dispatch::float_powf(
1278                lhs,
1279                Dispatch::dequantize(rhs, dtype.into()),
1280            ))
1281        }
1282        _ => panic!("Should be Float primitive kind"),
1283    }
1284}
1285
1286fn powf_scalar_impl(p: BridgeTensor, rhs: Scalar) -> BridgeTensor {
1287    let (kind, lhs) = p.into_parts();
1288    match kind {
1289        BridgeKind::Float => BridgeTensor::float(Dispatch::float_powf_scalar(lhs, rhs)),
1290        BridgeKind::QFloat => match Dispatch::q_powf_scalar(lhs, rhs) {
1291            TensorPrimitive::Float(out) => BridgeTensor::float(out),
1292            TensorPrimitive::QFloat(out) => BridgeTensor::qfloat(out),
1293        },
1294        _ => panic!("Should be Float primitive kind"),
1295    }
1296}
1297
1298#[cfg(feature = "std")]
1299fn is_distributed_impl(p: &BridgeTensor) -> bool {
1300    let (kind, tensor) = p.as_parts();
1301    match kind {
1302        BridgeKind::Float => Dispatch::is_distributed(tensor),
1303        BridgeKind::QFloat => unimplemented!(),
1304        _ => panic!("Should be Float primitive kind"),
1305    }
1306}
1307
1308#[cfg(feature = "std")]
1309fn set_distributed_impl(p: BridgeTensor, param_id: DistributedParamId) -> BridgeTensor {
1310    let (kind, tensor) = p.into_parts();
1311    match kind {
1312        BridgeKind::Float => {
1313            BridgeTensor::float(Dispatch::set_distributed_params(tensor, param_id))
1314        }
1315        BridgeKind::QFloat => unimplemented!(),
1316        _ => panic!("Should be Float primitive kind"),
1317    }
1318}