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;