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