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