Skip to main content

ruda_tensor/api/
float.rs

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