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