Skip to main content

tensor4all_core/
scalar.rs

1//! Common scalar trait for matrix and tensor operations.
2//!
3//! The [`Scalar`] trait provides a unified interface for numeric types used
4//! in matrix cross interpolation and tensor train operations. It is
5//! implemented for [`f64`], [`f32`], [`Complex64`], and [`Complex32`].
6//!
7//! The `scalar_tests!` macro generates dual `f64`/`Complex64` test
8//! variants from a single generic test function.
9
10use num_complex::{Complex32, Complex64};
11use num_traits::{Float, One, Zero};
12use tensor4all_tensorbackend::MatrixScalar;
13
14/// Common scalar trait for matrix and tensor operations.
15///
16/// Defines the minimal requirements for scalar types used in matrix cross
17/// interpolation and tensor train operations. Implemented for `f64`, `f32`,
18/// `Complex64`, and `Complex32`.
19///
20/// # Examples
21///
22/// ```
23/// use tensor4all_core::Scalar;
24///
25/// // f64
26/// let x = 3.0_f64;
27/// assert_eq!(x.abs_sq(), 9.0);
28/// assert_eq!(x.conj(), 3.0);
29///
30/// // Complex64
31/// use num_complex::Complex64;
32/// let z = Complex64::new(3.0, 4.0);
33/// assert!((z.abs_sq() - 25.0).abs() < 1e-10);
34/// assert_eq!(z.conj(), Complex64::new(3.0, -4.0));
35///
36/// // Construction from f64
37/// let val = f64::from_f64(2.5);
38/// assert_eq!(val, 2.5);
39/// ```
40pub trait Scalar:
41    Clone
42    + Copy
43    + Zero
44    + One
45    + std::ops::Add<Output = Self>
46    + std::ops::Sub<Output = Self>
47    + std::ops::Mul<Output = Self>
48    + std::ops::Div<Output = Self>
49    + std::ops::Neg<Output = Self>
50    + Default
51    + Send
52    + Sync
53    + MatrixScalar
54    + 'static
55{
56    /// Complex conjugate of the value.
57    fn conj(self) -> Self;
58
59    /// Square of the absolute value (for complex numbers, |z|^2).
60    fn abs_sq(self) -> f64;
61
62    /// Absolute value as Self type.
63    ///
64    /// For real types, this returns the absolute value.
65    /// For complex types, this returns a real-valued complex (re=|z|, im=0).
66    fn abs(self) -> Self;
67
68    /// Absolute value as f64.
69    fn abs_val(self) -> f64 {
70        self.abs_sq().sqrt()
71    }
72
73    /// Smallest positive normal value of the underlying real component type.
74    ///
75    /// This bounds squared-magnitude arithmetic; subnormal values remain valid
76    /// inputs. Returns `f64::MIN_POSITIVE` by default.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use tensor4all_core::Scalar;
82    /// assert_eq!(f32::min_positive(), f32::MIN_POSITIVE as f64);
83    /// assert_eq!(f64::min_positive(), f64::MIN_POSITIVE);
84    /// ```
85    fn min_positive() -> f64 {
86        f64::MIN_POSITIVE
87    }
88
89    /// Create from f64 value.
90    fn from_f64(val: f64) -> Self;
91
92    /// Divide by a real scalar without forming a complex squared norm.
93    ///
94    /// The divisor is converted to the underlying real component precision.
95    /// IEEE division semantics apply for zero or non-finite divisors.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use tensor4all_core::Scalar;
101    /// use num_complex::Complex64;
102    /// let value = Complex64::new(1e200, 5e199).div_real(1e200);
103    /// assert_eq!(value, Complex64::new(1.0, 0.5));
104    /// ```
105    fn div_real(self, divisor: f64) -> Self;
106
107    /// Check if value is NaN.
108    fn is_nan(self) -> bool;
109
110    /// Machine epsilon for numerical comparisons.
111    ///
112    /// Returns `f64::EPSILON` (~2.2e-16) by default. This is the smallest value
113    /// such that `1.0 + epsilon != 1.0`.
114    fn epsilon() -> f64 {
115        f64::EPSILON
116    }
117}
118
119impl Scalar for f64 {
120    #[inline]
121    fn div_real(self, divisor: f64) -> Self {
122        self / divisor
123    }
124    #[inline]
125    fn conj(self) -> Self {
126        self
127    }
128
129    #[inline]
130    fn abs_sq(self) -> f64 {
131        self * self
132    }
133
134    #[inline]
135    fn abs(self) -> Self {
136        Float::abs(self)
137    }
138
139    #[inline]
140    fn abs_val(self) -> f64 {
141        Float::abs(self)
142    }
143
144    #[inline]
145    fn from_f64(val: f64) -> Self {
146        val
147    }
148
149    #[inline]
150    fn is_nan(self) -> bool {
151        Float::is_nan(self)
152    }
153}
154
155impl Scalar for f32 {
156    #[inline]
157    fn div_real(self, divisor: f64) -> Self {
158        self / divisor as f32
159    }
160    #[inline]
161    fn conj(self) -> Self {
162        self
163    }
164
165    #[inline]
166    fn abs_sq(self) -> f64 {
167        (self * self) as f64
168    }
169
170    #[inline]
171    fn abs(self) -> Self {
172        Float::abs(self)
173    }
174
175    #[inline]
176    fn abs_val(self) -> f64 {
177        Float::abs(self) as f64
178    }
179
180    #[inline]
181    fn min_positive() -> f64 {
182        f32::MIN_POSITIVE as f64
183    }
184
185    #[inline]
186    fn from_f64(val: f64) -> Self {
187        val as f32
188    }
189
190    #[inline]
191    fn is_nan(self) -> bool {
192        Float::is_nan(self)
193    }
194}
195
196impl Scalar for Complex64 {
197    #[inline]
198    fn div_real(self, divisor: f64) -> Self {
199        self / divisor
200    }
201    #[inline]
202    fn conj(self) -> Self {
203        Complex64::conj(&self)
204    }
205
206    #[inline]
207    fn abs_sq(self) -> f64 {
208        self.norm_sqr()
209    }
210
211    #[inline]
212    fn abs(self) -> Self {
213        Complex64::new(self.norm(), 0.0)
214    }
215
216    #[inline]
217    fn abs_val(self) -> f64 {
218        self.norm()
219    }
220
221    #[inline]
222    fn from_f64(val: f64) -> Self {
223        Complex64::new(val, 0.0)
224    }
225
226    #[inline]
227    fn is_nan(self) -> bool {
228        self.re.is_nan() || self.im.is_nan()
229    }
230}
231
232impl Scalar for Complex32 {
233    #[inline]
234    fn div_real(self, divisor: f64) -> Self {
235        self / divisor as f32
236    }
237    #[inline]
238    fn conj(self) -> Self {
239        Complex32::conj(&self)
240    }
241
242    #[inline]
243    fn abs_sq(self) -> f64 {
244        self.norm_sqr() as f64
245    }
246
247    #[inline]
248    fn abs(self) -> Self {
249        Complex32::new(self.norm(), 0.0)
250    }
251
252    #[inline]
253    fn abs_val(self) -> f64 {
254        self.norm() as f64
255    }
256
257    #[inline]
258    fn min_positive() -> f64 {
259        f32::MIN_POSITIVE as f64
260    }
261
262    #[inline]
263    fn from_f64(val: f64) -> Self {
264        Complex32::new(val as f32, 0.0)
265    }
266
267    #[inline]
268    fn is_nan(self) -> bool {
269        self.re.is_nan() || self.im.is_nan()
270    }
271}
272
273/// Macro to generate f64 and Complex64 test variants from a generic test function.
274///
275/// # Example
276///
277/// ```
278/// fn test_operation_generic<T: tensor4all_core::Scalar>() {
279///     let value = T::from_f64(2.0);
280///     assert_eq!(value.abs_sq(), 4.0);
281/// }
282///
283/// # fn main() {}
284/// tensor4all_core::scalar_tests!(test_operation, test_operation_generic);
285/// ```
286#[macro_export]
287macro_rules! scalar_tests {
288    ($name:ident, $test_fn:ident) => {
289        paste::paste! {
290            #[test]
291            fn [<$name _f64>]() {
292                $test_fn::<f64>();
293            }
294
295            #[test]
296            fn [<$name _c64>]() {
297                $test_fn::<num_complex::Complex64>();
298            }
299        }
300    };
301}
302
303#[cfg(test)]
304mod tests;