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}