Skip to main content

tensor4all_tensorbackend/
any_scalar.rs

1use std::cmp::Ordering;
2use std::fmt;
3use std::ops::{Add, Div, Mul, Neg, Sub};
4
5use anyhow::{anyhow, ensure, Result};
6use num_complex::{Complex32, Complex64};
7use num_traits::{One, Zero};
8use tenferro::{DType, Tensor as NativeTensor, TensorScalar};
9
10use crate::storage::{Storage, SumFromStorage};
11use crate::tensor_element::TensorElement;
12
13#[derive(Clone, Copy, Debug, PartialEq)]
14enum ScalarValue {
15    F32(f32),
16    F64(f64),
17    I32(i32),
18    I64(i64),
19    Bool(bool),
20    C32(Complex32),
21    C64(Complex64),
22}
23
24impl ScalarValue {
25    fn real(self) -> f64 {
26        match self {
27            Self::F32(value) => value as f64,
28            Self::F64(value) => value,
29            Self::I32(value) => value as f64,
30            Self::I64(value) => value as f64,
31            Self::Bool(value) => {
32                if value {
33                    1.0
34                } else {
35                    0.0
36                }
37            }
38            Self::C32(value) => value.re as f64,
39            Self::C64(value) => value.re,
40        }
41    }
42
43    fn imag(self) -> f64 {
44        match self {
45            Self::F32(_) | Self::F64(_) | Self::I32(_) | Self::I64(_) | Self::Bool(_) => 0.0,
46            Self::C32(value) => value.im as f64,
47            Self::C64(value) => value.im,
48        }
49    }
50
51    fn abs(self) -> f64 {
52        match self {
53            Self::F32(value) => value.abs() as f64,
54            Self::F64(value) => value.abs(),
55            Self::I32(value) => value.abs() as f64,
56            Self::I64(value) => value.abs() as f64,
57            Self::Bool(value) => {
58                if value {
59                    1.0
60                } else {
61                    0.0
62                }
63            }
64            Self::C32(value) => {
65                if value.re.is_nan() || value.im.is_nan() {
66                    f64::NAN
67                } else {
68                    value.norm() as f64
69                }
70            }
71            Self::C64(value) => {
72                if value.re.is_nan() || value.im.is_nan() {
73                    f64::NAN
74                } else {
75                    value.norm()
76                }
77            }
78        }
79    }
80
81    fn is_complex(self) -> bool {
82        matches!(self, Self::C32(_) | Self::C64(_))
83    }
84
85    fn is_zero(self) -> bool {
86        match self {
87            Self::F32(value) => value == 0.0,
88            Self::F64(value) => value == 0.0,
89            Self::I32(value) => value == 0,
90            Self::I64(value) => value == 0,
91            Self::Bool(value) => !value,
92            Self::C32(value) => value == Complex32::new(0.0, 0.0),
93            Self::C64(value) => value == Complex64::new(0.0, 0.0),
94        }
95    }
96
97    fn into_complex(self) -> Complex64 {
98        match self {
99            Self::F32(value) => Complex64::new(value as f64, 0.0),
100            Self::F64(value) => Complex64::new(value, 0.0),
101            Self::I32(value) => Complex64::new(value as f64, 0.0),
102            Self::I64(value) => Complex64::new(value as f64, 0.0),
103            Self::Bool(value) => Complex64::new(if value { 1.0 } else { 0.0 }, 0.0),
104            Self::C32(value) => Complex64::new(value.re as f64, value.im as f64),
105            Self::C64(value) => value,
106        }
107    }
108}
109
110/// Snapshot a compact storage reduction as the dynamic scalar kind it was stored as.
111///
112/// The dispatch follows the storage's own dtype instead of assuming `f64` for every real payload: a
113/// narrow `f32` storage yields an `f32` scalar and a `c32` storage a `c32` scalar, exactly as
114/// [`SumFromStorage`] defines them, so a reduction never changes the scalar kind.
115fn scalar_value_from_storage(storage: &Storage) -> ScalarValue {
116    match storage.dtype() {
117        DType::F32 => ScalarValue::F32(f32::sum_from_storage(storage)),
118        DType::F64 => ScalarValue::F64(f64::sum_from_storage(storage)),
119        DType::C32 => ScalarValue::C32(Complex32::sum_from_storage(storage)),
120        DType::C64 => ScalarValue::C64(Complex64::sum_from_storage(storage)),
121        // `Storage` wraps exactly the four floating payloads above, so no other dtype can be stored.
122        other => crate::invariant_violated(
123            "compact storage reduction",
124            format!("compact storage carries no {other:?} payload"),
125        ),
126    }
127}
128
129fn scalar_value_from_native(native: &NativeTensor) -> Result<ScalarValue> {
130    ensure!(
131        native.shape().is_empty(),
132        "expected rank-0 scalar tensor, got shape {:?}",
133        native.shape()
134    );
135
136    match native.dtype() {
137        DType::F32 => native
138            .as_slice::<f32>()
139            .map_err(anyhow::Error::new)?
140            .first()
141            .copied()
142            .map(ScalarValue::F32)
143            .ok_or_else(|| anyhow!("failed to read f32 scalar tensor value")),
144        DType::F64 => native
145            .as_slice::<f64>()
146            .map_err(anyhow::Error::new)?
147            .first()
148            .copied()
149            .map(ScalarValue::F64)
150            .ok_or_else(|| anyhow!("failed to read f64 scalar tensor value")),
151        DType::I32 => native
152            .as_slice::<i32>()
153            .map_err(anyhow::Error::new)?
154            .first()
155            .copied()
156            .map(ScalarValue::I32)
157            .ok_or_else(|| anyhow!("failed to read i32 scalar tensor value")),
158        DType::I64 => native
159            .as_slice::<i64>()
160            .map_err(anyhow::Error::new)?
161            .first()
162            .copied()
163            .map(ScalarValue::I64)
164            .ok_or_else(|| anyhow!("failed to read i64 scalar tensor value")),
165        DType::Bool => native
166            .as_slice::<bool>()
167            .map_err(anyhow::Error::new)?
168            .first()
169            .copied()
170            .map(ScalarValue::Bool)
171            .ok_or_else(|| anyhow!("failed to read bool scalar tensor value")),
172        DType::C32 => native
173            .as_slice::<Complex32>()
174            .map_err(anyhow::Error::new)?
175            .first()
176            .copied()
177            .map(ScalarValue::C32)
178            .ok_or_else(|| anyhow!("failed to read c32 scalar tensor value")),
179        // An external scalar kind is a caller-registered Rust type; it has no
180        // logical scalar value in this crate.
181        DType::External(_) => Err(anyhow::Error::new(tenferro_tensor::Error::unsupported(
182            "scalar snapshot",
183            format!(
184                "external scalar kind {:?} is not a supported scalar tensor",
185                native.dtype()
186            ),
187        ))),
188        DType::C64 => native
189            .as_slice::<Complex64>()
190            .map_err(anyhow::Error::new)?
191            .first()
192            .copied()
193            .map(ScalarValue::C64)
194            .ok_or_else(|| anyhow!("failed to read c64 scalar tensor value")),
195    }
196}
197
198trait ScalarTensorElement: TensorElement {
199    fn scalar_value(value: Self) -> ScalarValue;
200}
201
202fn scalar_native<T: TensorScalar>(value: T) -> NativeTensor {
203    NativeTensor::from_vec_col_major(vec![], vec![value]).unwrap_or_else(|error| {
204        crate::invariant_violated("rank-0 scalar construction failed", error)
205    })
206}
207
208impl ScalarTensorElement for f32 {
209    fn scalar_value(value: Self) -> ScalarValue {
210        ScalarValue::F32(value)
211    }
212}
213
214impl ScalarTensorElement for f64 {
215    fn scalar_value(value: Self) -> ScalarValue {
216        ScalarValue::F64(value)
217    }
218}
219
220impl ScalarTensorElement for Complex32 {
221    fn scalar_value(value: Self) -> ScalarValue {
222        ScalarValue::C32(value)
223    }
224}
225
226impl ScalarTensorElement for Complex64 {
227    fn scalar_value(value: Self) -> ScalarValue {
228        ScalarValue::C64(value)
229    }
230}
231
232pub(crate) fn promote_scalar_native(native: &NativeTensor, target: DType) -> Result<NativeTensor> {
233    let promoted = match (scalar_value_from_native(native)?, target) {
234        (ScalarValue::F32(value), DType::F32) => BackendScalar::from_value(value),
235        (ScalarValue::F32(value), DType::F64) => BackendScalar::from_value(value as f64),
236        (ScalarValue::F32(value), DType::C32) => {
237            BackendScalar::from_value(Complex32::new(value, 0.0))
238        }
239        (ScalarValue::F32(value), DType::C64) => {
240            BackendScalar::from_value(Complex64::new(value as f64, 0.0))
241        }
242        (ScalarValue::F32(_), DType::I64) => {
243            return Err(anyhow!(
244                "cannot promote f32 scalar to i64 without truncation"
245            ));
246        }
247        (ScalarValue::F32(_), DType::I32 | DType::Bool) => {
248            return Err(anyhow!(
249                "cannot promote f32 scalar to integer/bool without truncation"
250            ));
251        }
252        (ScalarValue::F64(value), DType::F32) => BackendScalar::from_value(value as f32),
253        (ScalarValue::F64(value), DType::F64) => BackendScalar::from_value(value),
254        (ScalarValue::F64(_), DType::I64) => {
255            return Err(anyhow!(
256                "cannot promote f64 scalar to i64 without truncation"
257            ));
258        }
259        (ScalarValue::F64(_), DType::I32 | DType::Bool) => {
260            return Err(anyhow!(
261                "cannot promote f64 scalar to integer/bool without truncation"
262            ));
263        }
264        (ScalarValue::F64(value), DType::C32) => {
265            BackendScalar::from_value(Complex32::new(value as f32, 0.0))
266        }
267        (ScalarValue::F64(value), DType::C64) => {
268            BackendScalar::from_value(Complex64::new(value, 0.0))
269        }
270        (ScalarValue::I32(value), DType::F32) => BackendScalar::from_value(value as f32),
271        (ScalarValue::I32(value), DType::F64) => BackendScalar::from_value(value as f64),
272        (ScalarValue::I32(value), DType::I32) => return Ok(scalar_native(value)),
273        (ScalarValue::I32(value), DType::I64) => BackendScalar::from_i64(value as i64),
274        (ScalarValue::I32(value), DType::C32) => {
275            BackendScalar::from_value(Complex32::new(value as f32, 0.0))
276        }
277        (ScalarValue::I32(value), DType::C64) => {
278            BackendScalar::from_value(Complex64::new(value as f64, 0.0))
279        }
280        (ScalarValue::I32(_), DType::Bool) => {
281            return Err(anyhow!("cannot promote i32 scalar to bool"));
282        }
283        (ScalarValue::I64(value), DType::F32) => BackendScalar::from_value(value as f32),
284        (ScalarValue::I64(value), DType::F64) => BackendScalar::from_value(value as f64),
285        (ScalarValue::I64(_), DType::I32 | DType::Bool) => {
286            return Err(anyhow!("cannot promote i64 scalar to i32/bool"));
287        }
288        (ScalarValue::I64(value), DType::I64) => BackendScalar::from_i64(value),
289        (ScalarValue::I64(value), DType::C32) => {
290            BackendScalar::from_value(Complex32::new(value as f32, 0.0))
291        }
292        (ScalarValue::I64(value), DType::C64) => {
293            BackendScalar::from_value(Complex64::new(value as f64, 0.0))
294        }
295        (ScalarValue::Bool(value), DType::F32) => {
296            BackendScalar::from_value(if value { 1.0_f32 } else { 0.0_f32 })
297        }
298        (ScalarValue::Bool(value), DType::F64) => {
299            BackendScalar::from_value(if value { 1.0 } else { 0.0 })
300        }
301        (ScalarValue::Bool(value), DType::I32) => {
302            return Ok(scalar_native(if value { 1 } else { 0 }));
303        }
304        (ScalarValue::Bool(value), DType::I64) => {
305            BackendScalar::from_i64(if value { 1 } else { 0 })
306        }
307        (ScalarValue::Bool(value), DType::Bool) => return Ok(scalar_native(value)),
308        (ScalarValue::Bool(value), DType::C32) => {
309            BackendScalar::from_value(Complex32::new(if value { 1.0 } else { 0.0 }, 0.0))
310        }
311        (ScalarValue::Bool(value), DType::C64) => {
312            BackendScalar::from_value(Complex64::new(if value { 1.0 } else { 0.0 }, 0.0))
313        }
314        (ScalarValue::C32(value), DType::F32) => BackendScalar::from_value(value.re),
315        (ScalarValue::C32(value), DType::F64) => BackendScalar::from_value(value.re as f64),
316        (ScalarValue::C32(_), DType::I32 | DType::I64 | DType::Bool) => {
317            return Err(anyhow!("cannot promote c32 scalar to integer/bool"));
318        }
319        (ScalarValue::C32(value), DType::C32) => BackendScalar::from_value(value),
320        (ScalarValue::C32(value), DType::C64) => {
321            BackendScalar::from_value(Complex64::new(value.re as f64, value.im as f64))
322        }
323        (ScalarValue::C64(value), DType::F32) => BackendScalar::from_value(value.re as f32),
324        (ScalarValue::C64(value), DType::F64) => BackendScalar::from_value(value.re),
325        (ScalarValue::C64(_), DType::I32 | DType::I64 | DType::Bool) => {
326            return Err(anyhow!("cannot promote c64 scalar to integer/bool"));
327        }
328        (ScalarValue::C64(value), DType::C32) => {
329            BackendScalar::from_value(Complex32::new(value.re as f32, value.im as f32))
330        }
331        (ScalarValue::C64(value), DType::C64) => BackendScalar::from_value(value),
332
333        (_, DType::External(_)) => {
334            return Err(anyhow::Error::new(tenferro_tensor::Error::unsupported(
335                "promotion",
336                "external scalar kinds are not promotable dtypes",
337            )));
338        }
339    };
340    Ok(promoted.native)
341}
342
343/// Dynamic scalar used across tensor4all backends.
344///
345/// This is a tensor4all-owned rank-0 wrapper over tenferro's dynamic tensor.
346///
347/// # Examples
348///
349/// ```
350/// use tensor4all_tensorbackend::BackendScalar;
351///
352/// // Real scalar
353/// let a = BackendScalar::new_real(3.14);
354/// assert!((a.real() - 3.14).abs() < 1e-10);
355/// assert_eq!(a.imag(), 0.0);
356/// assert!(a.is_real());
357/// assert!(!a.is_complex());
358///
359/// // Complex scalar
360/// let b = BackendScalar::new_complex(1.0, 2.0);
361/// assert!((b.real() - 1.0).abs() < 1e-10);
362/// assert!((b.imag() - 2.0).abs() < 1e-10);
363/// assert!(b.is_complex());
364///
365/// // Arithmetic
366/// let c = BackendScalar::new_real(2.0);
367/// let d = a + c;
368/// assert!((d.real() - 5.14).abs() < 1e-10);
369/// ```
370pub struct BackendScalar {
371    native: NativeTensor,
372    value: ScalarValue,
373}
374
375impl BackendScalar {
376    fn wrap_native(native: NativeTensor) -> Result<Self> {
377        if native.shape().is_empty() {
378            let value = scalar_value_from_native(&native)?;
379            Ok(Self { native, value })
380        } else {
381            Err(anyhow!(
382                "BackendScalar requires a rank-0 tensor, got shape {:?}",
383                native.shape()
384            ))
385        }
386    }
387
388    fn value(&self) -> ScalarValue {
389        self.value
390    }
391
392    fn from_i64(value: i64) -> Self {
393        Self {
394            native: scalar_native(value),
395            value: ScalarValue::I64(value),
396        }
397    }
398
399    fn from_i32(value: i32) -> Self {
400        Self {
401            native: scalar_native(value),
402            value: ScalarValue::I32(value),
403        }
404    }
405
406    fn from_bool(value: bool) -> Self {
407        Self {
408            native: scalar_native(value),
409            value: ScalarValue::Bool(value),
410        }
411    }
412
413    pub(crate) fn from_native(value: NativeTensor) -> Result<Self> {
414        Self::wrap_native(value)
415    }
416
417    pub(crate) fn as_native(&self) -> &NativeTensor {
418        &self.native
419    }
420
421    /// Creates a scalar from any supported public tensor element type.
422    ///
423    /// # Examples
424    ///
425    /// ```
426    /// use tensor4all_tensorbackend::BackendScalar;
427    ///
428    /// let s = BackendScalar::from_value(3.14_f64);
429    /// assert!((s.real() - 3.14).abs() < 1e-10);
430    ///
431    /// use num_complex::Complex64;
432    /// let z = BackendScalar::from_value(Complex64::new(1.0, 2.0));
433    /// assert!(z.is_complex());
434    /// assert!((z.real() - 1.0).abs() < 1e-10);
435    /// assert!((z.imag() - 2.0).abs() < 1e-10);
436    /// ```
437    #[allow(private_bounds)]
438    pub fn from_value<T: ScalarTensorElement>(value: T) -> Self {
439        Self {
440            native: scalar_native(value),
441            value: T::scalar_value(value),
442        }
443    }
444
445    /// Creates a real scalar from an `f64` value.
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// use tensor4all_tensorbackend::BackendScalar;
451    ///
452    /// let s = BackendScalar::from_real(2.5);
453    /// assert!((s.real() - 2.5).abs() < 1e-10);
454    /// assert!(s.is_real());
455    /// ```
456    pub fn from_real(x: f64) -> Self {
457        Self::from_value(x)
458    }
459
460    /// Creates a complex scalar from real and imaginary parts.
461    ///
462    /// # Examples
463    ///
464    /// ```
465    /// use tensor4all_tensorbackend::BackendScalar;
466    ///
467    /// let s = BackendScalar::from_complex(1.0, -1.0);
468    /// assert!((s.real() - 1.0).abs() < 1e-10);
469    /// assert!((s.imag() - (-1.0)).abs() < 1e-10);
470    /// assert!(s.is_complex());
471    /// ```
472    pub fn from_complex(re: f64, im: f64) -> Self {
473        Self::from_value(Complex64::new(re, im))
474    }
475
476    /// Backward-compatible constructor for a real scalar.
477    ///
478    /// # Examples
479    ///
480    /// ```
481    /// use tensor4all_tensorbackend::BackendScalar;
482    ///
483    /// let s = BackendScalar::new_real(42.0);
484    /// assert!((s.real() - 42.0).abs() < 1e-10);
485    /// ```
486    pub fn new_real(x: f64) -> Self {
487        Self::from_real(x)
488    }
489
490    /// Backward-compatible constructor for a complex scalar.
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// use tensor4all_tensorbackend::BackendScalar;
496    ///
497    /// let s = BackendScalar::new_complex(3.0, 4.0);
498    /// assert!((s.abs() - 5.0).abs() < 1e-10); // |3 + 4i| = 5
499    /// ```
500    pub fn new_complex(re: f64, im: f64) -> Self {
501        Self::from_complex(re, im)
502    }
503
504    /// Returns the scalar's plain rank-0 tensor value.
505    ///
506    /// # Examples
507    ///
508    /// ```
509    /// use tensor4all_tensorbackend::BackendScalar;
510    ///
511    /// let s = BackendScalar::new_real(5.0);
512    /// let p = s.primal();
513    /// assert!((p.real() - 5.0).abs() < 1e-10);
514    /// ```
515    pub fn primal(&self) -> Self {
516        self.clone()
517    }
518
519    /// Returns the real part while intentionally dropping AD metadata.
520    ///
521    /// # Examples
522    ///
523    /// ```
524    /// use tensor4all_tensorbackend::BackendScalar;
525    ///
526    /// let s = BackendScalar::new_complex(3.0, 4.0);
527    /// assert!((s.real() - 3.0).abs() < 1e-10);
528    /// ```
529    pub fn real(&self) -> f64 {
530        self.value().real()
531    }
532
533    /// Returns the imaginary part while intentionally dropping AD metadata.
534    ///
535    /// Returns `0.0` for real scalars.
536    ///
537    /// # Examples
538    ///
539    /// ```
540    /// use tensor4all_tensorbackend::BackendScalar;
541    ///
542    /// let s = BackendScalar::new_complex(3.0, 4.0);
543    /// assert!((s.imag() - 4.0).abs() < 1e-10);
544    ///
545    /// let r = BackendScalar::new_real(5.0);
546    /// assert_eq!(r.imag(), 0.0);
547    /// ```
548    pub fn imag(&self) -> f64 {
549        self.value().imag()
550    }
551
552    /// Returns the absolute value while intentionally dropping AD metadata.
553    ///
554    /// For complex scalars, returns the complex modulus (`sqrt(re^2 + im^2)`).
555    ///
556    /// # Examples
557    ///
558    /// ```
559    /// use tensor4all_tensorbackend::BackendScalar;
560    ///
561    /// let s = BackendScalar::new_complex(3.0, 4.0);
562    /// assert!((s.abs() - 5.0).abs() < 1e-10);
563    ///
564    /// let r = BackendScalar::new_real(-7.0);
565    /// assert!((r.abs() - 7.0).abs() < 1e-10);
566    /// ```
567    pub fn abs(&self) -> f64 {
568        self.value().abs()
569    }
570
571    /// Returns true when the scalar is complex.
572    ///
573    /// # Examples
574    ///
575    /// ```
576    /// use tensor4all_tensorbackend::BackendScalar;
577    ///
578    /// assert!(BackendScalar::new_complex(1.0, 0.0).is_complex());
579    /// assert!(!BackendScalar::new_real(1.0).is_complex());
580    /// ```
581    pub fn is_complex(&self) -> bool {
582        self.value().is_complex()
583    }
584
585    /// Returns true when the scalar is real.
586    ///
587    /// # Examples
588    ///
589    /// ```
590    /// use tensor4all_tensorbackend::BackendScalar;
591    ///
592    /// assert!(BackendScalar::new_real(1.0).is_real());
593    /// assert!(!BackendScalar::new_complex(1.0, 2.0).is_real());
594    /// ```
595    pub fn is_real(&self) -> bool {
596        !self.is_complex()
597    }
598
599    /// Returns true when the scalar is zero.
600    ///
601    /// # Examples
602    ///
603    /// ```
604    /// use tensor4all_tensorbackend::BackendScalar;
605    ///
606    /// assert!(BackendScalar::new_real(0.0).is_zero());
607    /// assert!(!BackendScalar::new_real(1.0).is_zero());
608    /// ```
609    pub fn is_zero(&self) -> bool {
610        self.value().is_zero()
611    }
612
613    /// Returns the underlying value as `f64` when real.
614    ///
615    /// Returns `None` for complex scalars.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// use tensor4all_tensorbackend::BackendScalar;
621    ///
622    /// let r = BackendScalar::new_real(2.5);
623    /// assert_eq!(r.as_f64(), Some(2.5));
624    ///
625    /// let c = BackendScalar::new_complex(1.0, 1.0);
626    /// assert_eq!(c.as_f64(), None);
627    /// ```
628    pub fn as_f64(&self) -> Option<f64> {
629        match self.value() {
630            ScalarValue::F32(value) => Some(value as f64),
631            ScalarValue::F64(value) => Some(value),
632            ScalarValue::I32(value) => Some(value as f64),
633            ScalarValue::I64(value) => Some(value as f64),
634            ScalarValue::Bool(value) => Some(if value { 1.0 } else { 0.0 }),
635            ScalarValue::C32(_) | ScalarValue::C64(_) => None,
636        }
637    }
638
639    /// Returns the underlying value as `Complex64` when complex.
640    ///
641    /// Returns `None` for real scalars.
642    ///
643    /// # Examples
644    ///
645    /// ```
646    /// use tensor4all_tensorbackend::BackendScalar;
647    /// use num_complex::Complex64;
648    ///
649    /// let c = BackendScalar::new_complex(1.0, 2.0);
650    /// assert_eq!(c.as_c64(), Some(Complex64::new(1.0, 2.0)));
651    ///
652    /// let r = BackendScalar::new_real(1.0);
653    /// assert_eq!(r.as_c64(), None);
654    /// ```
655    pub fn as_c64(&self) -> Option<Complex64> {
656        match self.value() {
657            ScalarValue::F32(_)
658            | ScalarValue::F64(_)
659            | ScalarValue::I32(_)
660            | ScalarValue::I64(_)
661            | ScalarValue::Bool(_) => None,
662            ScalarValue::C32(value) => Some(Complex64::new(value.re as f64, value.im as f64)),
663            ScalarValue::C64(value) => Some(value),
664        }
665    }
666
667    /// Returns the complex conjugate.
668    ///
669    /// For real scalars, returns a copy (conjugate of a real number is itself).
670    ///
671    /// # Examples
672    ///
673    /// ```
674    /// use tensor4all_tensorbackend::BackendScalar;
675    ///
676    /// let c = BackendScalar::new_complex(3.0, 4.0);
677    /// let cc = c.conj();
678    /// assert!((cc.real() - 3.0).abs() < 1e-10);
679    /// assert!((cc.imag() - (-4.0)).abs() < 1e-10);
680    ///
681    /// let r = BackendScalar::new_real(5.0);
682    /// assert!((r.conj().real() - 5.0).abs() < 1e-10);
683    /// ```
684    pub fn conj(&self) -> Self {
685        match self.value() {
686            ScalarValue::F32(value) => Self::from_value(value),
687            ScalarValue::F64(value) => Self::from_value(value),
688            ScalarValue::I32(value) => Self::from_i32(value),
689            ScalarValue::I64(value) => Self::from_i64(value),
690            ScalarValue::Bool(value) => Self::from_bool(value),
691            ScalarValue::C32(value) => Self::from_value(value.conj()),
692            ScalarValue::C64(value) => Self::from_value(value.conj()),
693        }
694    }
695
696    /// Returns the real part as a scalar, preserving scalar semantics.
697    ///
698    /// Unlike [`real`](Self::real), this returns an `BackendScalar` rather than raw `f64`.
699    ///
700    /// # Examples
701    ///
702    /// ```
703    /// use tensor4all_tensorbackend::BackendScalar;
704    ///
705    /// let c = BackendScalar::new_complex(3.0, 4.0);
706    /// let re = c.real_part();
707    /// assert!(re.is_real());
708    /// assert!((re.real() - 3.0).abs() < 1e-10);
709    /// ```
710    pub fn real_part(&self) -> Self {
711        Self::from_real(self.real())
712    }
713
714    /// Returns the imaginary part as a scalar, preserving scalar semantics.
715    ///
716    /// Unlike [`imag`](Self::imag), this returns an `BackendScalar` rather than raw `f64`.
717    /// The result is always a real scalar.
718    ///
719    /// # Examples
720    ///
721    /// ```
722    /// use tensor4all_tensorbackend::BackendScalar;
723    ///
724    /// let c = BackendScalar::new_complex(3.0, 4.0);
725    /// let im = c.imag_part();
726    /// assert!(im.is_real());
727    /// assert!((im.real() - 4.0).abs() < 1e-10);
728    /// ```
729    pub fn imag_part(&self) -> Self {
730        Self::from_real(self.imag())
731    }
732
733    /// Compose a complex scalar from real-valued parts.
734    ///
735    /// # Errors
736    ///
737    /// Returns an error when `real` or `imag` is complex rather than a real
738    /// scalar (an invalid input).
739    ///
740    /// # Examples
741    ///
742    /// ```
743    /// use tensor4all_tensorbackend::BackendScalar;
744    ///
745    /// let re = BackendScalar::new_real(3.0);
746    /// let im = BackendScalar::new_real(4.0);
747    /// let c = BackendScalar::compose_complex(re, im).unwrap();
748    /// assert!(c.is_complex());
749    /// assert!((c.real() - 3.0).abs() < 1e-10);
750    /// assert!((c.imag() - 4.0).abs() < 1e-10);
751    /// ```
752    pub fn compose_complex(real: Self, imag: Self) -> Result<Self> {
753        if !real.is_real() || !imag.is_real() {
754            return Err(anyhow!(
755                "compose_complex requires real-valued inputs, got real={:?}, imag={:?}",
756                real.native.dtype(),
757                imag.native.dtype()
758            ));
759        }
760        Ok(Self::from_complex(real.real(), imag.real()))
761    }
762
763    /// Square root, preserving AD metadata.
764    ///
765    /// Automatically promotes to complex if the value is negative.
766    ///
767    /// # Examples
768    ///
769    /// ```
770    /// use tensor4all_tensorbackend::BackendScalar;
771    ///
772    /// let s = BackendScalar::new_real(9.0);
773    /// assert!((s.sqrt().real() - 3.0).abs() < 1e-10);
774    /// ```
775    pub fn sqrt(&self) -> Self {
776        if self.is_complex() || self.real() < 0.0 {
777            let value = self.value().into_complex().sqrt();
778            if value.im == 0.0 {
779                Self::from_real(value.re)
780            } else {
781                Self::from_value(value)
782            }
783        } else {
784            Self::from_real(self.real().sqrt())
785        }
786    }
787
788    /// Real exponent power, preserving AD metadata.
789    ///
790    /// Automatically promotes to complex when the base is negative and the
791    /// exponent is non-integer.
792    ///
793    /// # Examples
794    ///
795    /// ```
796    /// use tensor4all_tensorbackend::BackendScalar;
797    ///
798    /// let s = BackendScalar::new_real(2.0);
799    /// assert!((s.powf(3.0).real() - 8.0).abs() < 1e-10);
800    /// ```
801    pub fn powf(&self, exponent: f64) -> Self {
802        let needs_complex_promotion =
803            self.is_complex() || (self.real() < 0.0 && exponent.fract() != 0.0);
804        if needs_complex_promotion {
805            let value = self.value().into_complex().powf(exponent);
806            if value.im == 0.0 {
807                Self::from_real(value.re)
808            } else {
809                Self::from_value(value)
810            }
811        } else {
812            Self::from_real(self.real().powf(exponent))
813        }
814    }
815
816    /// Integer exponent power, preserving AD metadata.
817    ///
818    /// # Examples
819    ///
820    /// ```
821    /// use tensor4all_tensorbackend::BackendScalar;
822    ///
823    /// let s = BackendScalar::new_real(3.0);
824    /// assert!((s.powi(2).real() - 9.0).abs() < 1e-10);
825    /// ```
826    pub fn powi(&self, exponent: i32) -> Self {
827        self.powf(exponent as f64)
828    }
829}
830
831impl SumFromStorage for BackendScalar {
832    fn sum_from_storage(storage: &Storage) -> Self {
833        match scalar_value_from_storage(storage) {
834            ScalarValue::F32(value) => Self::from_value(value),
835            ScalarValue::F64(value) => Self::from_value(value),
836            ScalarValue::I32(value) => Self::from_i32(value),
837            ScalarValue::I64(value) => Self::from_i64(value),
838            ScalarValue::Bool(value) => Self::from_bool(value),
839            ScalarValue::C32(value) => Self::from_value(value),
840            ScalarValue::C64(value) => Self::from_value(value),
841        }
842    }
843}
844
845impl From<f32> for BackendScalar {
846    fn from(value: f32) -> Self {
847        Self::from_value(value)
848    }
849}
850
851impl From<f64> for BackendScalar {
852    fn from(value: f64) -> Self {
853        Self::from_value(value)
854    }
855}
856
857impl From<Complex32> for BackendScalar {
858    fn from(value: Complex32) -> Self {
859        Self::from_value(value)
860    }
861}
862
863impl From<Complex64> for BackendScalar {
864    fn from(value: Complex64) -> Self {
865        Self::from_value(value)
866    }
867}
868
869impl TryFrom<BackendScalar> for f64 {
870    type Error = &'static str;
871
872    fn try_from(value: BackendScalar) -> std::result::Result<Self, Self::Error> {
873        match value.value() {
874            ScalarValue::F32(real) => Ok(real as f64),
875            ScalarValue::F64(real) => Ok(real),
876            ScalarValue::I32(real) => Ok(real as f64),
877            ScalarValue::I64(real) => Ok(real as f64),
878            ScalarValue::Bool(real) => Ok(if real { 1.0 } else { 0.0 }),
879            ScalarValue::C32(_) | ScalarValue::C64(_) => {
880                Err("cannot convert complex scalar to f64")
881            }
882        }
883    }
884}
885
886impl From<BackendScalar> for Complex64 {
887    fn from(value: BackendScalar) -> Self {
888        value.value().into_complex()
889    }
890}
891
892impl Add for BackendScalar {
893    type Output = Self;
894
895    fn add(self, rhs: Self) -> Self::Output {
896        match (self.value(), rhs.value()) {
897            (ScalarValue::F32(lhs), ScalarValue::F32(rhs)) => Self::from_value(lhs + rhs),
898            (lhs, rhs) if lhs.is_complex() || rhs.is_complex() => {
899                Self::from_value(lhs.into_complex() + rhs.into_complex())
900            }
901            (lhs, rhs) => Self::from_real(lhs.real() + rhs.real()),
902        }
903    }
904}
905
906impl Sub for BackendScalar {
907    type Output = Self;
908
909    fn sub(self, rhs: Self) -> Self::Output {
910        self + (-rhs)
911    }
912}
913
914impl Mul for BackendScalar {
915    type Output = Self;
916
917    fn mul(self, rhs: Self) -> Self::Output {
918        match (self.value(), rhs.value()) {
919            (ScalarValue::F32(lhs), ScalarValue::F32(rhs)) => Self::from_value(lhs * rhs),
920            (lhs, rhs) if lhs.is_complex() || rhs.is_complex() => {
921                Self::from_value(lhs.into_complex() * rhs.into_complex())
922            }
923            (lhs, rhs) => Self::from_real(lhs.real() * rhs.real()),
924        }
925    }
926}
927
928impl Div for BackendScalar {
929    type Output = Self;
930
931    fn div(self, rhs: Self) -> Self::Output {
932        match (self.value(), rhs.value()) {
933            (ScalarValue::F32(lhs), ScalarValue::F32(rhs)) => Self::from_value(lhs / rhs),
934            (lhs, rhs) if lhs.is_complex() || rhs.is_complex() => {
935                Self::from_value(lhs.into_complex() / rhs.into_complex())
936            }
937            (lhs, rhs) => Self::from_real(lhs.real() / rhs.real()),
938        }
939    }
940}
941
942impl Neg for BackendScalar {
943    type Output = Self;
944
945    fn neg(self) -> Self::Output {
946        match self.value() {
947            ScalarValue::F32(value) => Self::from_value(-value),
948            ScalarValue::F64(value) => Self::from_value(-value),
949            ScalarValue::I32(value) => Self::from_i32(-value),
950            ScalarValue::I64(value) => Self::from_i64(-value),
951            ScalarValue::Bool(value) => Self::from_real(if value { -1.0 } else { 0.0 }),
952            ScalarValue::C32(value) => Self::from_value(-value),
953            ScalarValue::C64(value) => Self::from_value(-value),
954        }
955    }
956}
957
958impl Mul<BackendScalar> for f64 {
959    type Output = BackendScalar;
960
961    fn mul(self, rhs: BackendScalar) -> Self::Output {
962        BackendScalar::from_real(self) * rhs
963    }
964}
965
966impl Mul<BackendScalar> for Complex64 {
967    type Output = BackendScalar;
968
969    fn mul(self, rhs: BackendScalar) -> Self::Output {
970        BackendScalar::from(self) * rhs
971    }
972}
973
974impl Div<BackendScalar> for Complex64 {
975    type Output = BackendScalar;
976
977    fn div(self, rhs: BackendScalar) -> Self::Output {
978        BackendScalar::from(self) / rhs
979    }
980}
981
982impl Default for BackendScalar {
983    fn default() -> Self {
984        Self::zero()
985    }
986}
987
988impl Zero for BackendScalar {
989    fn zero() -> Self {
990        Self::from_real(0.0)
991    }
992
993    fn is_zero(&self) -> bool {
994        BackendScalar::is_zero(self)
995    }
996}
997
998impl One for BackendScalar {
999    fn one() -> Self {
1000        Self::from_real(1.0)
1001    }
1002}
1003
1004impl PartialEq for BackendScalar {
1005    fn eq(&self, other: &Self) -> bool {
1006        self.native.dtype() == other.native.dtype() && self.value() == other.value()
1007    }
1008}
1009
1010impl PartialOrd for BackendScalar {
1011    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1012        match (self.value(), other.value()) {
1013            (ScalarValue::F32(lhs), ScalarValue::F32(rhs)) => lhs.partial_cmp(&rhs),
1014            (ScalarValue::F32(lhs), ScalarValue::F64(rhs)) => (lhs as f64).partial_cmp(&rhs),
1015            (ScalarValue::F32(lhs), ScalarValue::I32(rhs)) => {
1016                (lhs as f64).partial_cmp(&(rhs as f64))
1017            }
1018            (ScalarValue::F32(lhs), ScalarValue::I64(rhs)) => {
1019                (lhs as f64).partial_cmp(&(rhs as f64))
1020            }
1021            (ScalarValue::F32(lhs), ScalarValue::Bool(rhs)) => {
1022                (lhs as f64).partial_cmp(&(if rhs { 1.0 } else { 0.0 }))
1023            }
1024            (ScalarValue::F64(lhs), ScalarValue::F32(rhs)) => lhs.partial_cmp(&(rhs as f64)),
1025            (ScalarValue::F64(lhs), ScalarValue::F64(rhs)) => lhs.partial_cmp(&rhs),
1026            (ScalarValue::F64(lhs), ScalarValue::I32(rhs)) => lhs.partial_cmp(&(rhs as f64)),
1027            (ScalarValue::F64(lhs), ScalarValue::I64(rhs)) => lhs.partial_cmp(&(rhs as f64)),
1028            (ScalarValue::F64(lhs), ScalarValue::Bool(rhs)) => {
1029                lhs.partial_cmp(&(if rhs { 1.0 } else { 0.0 }))
1030            }
1031            (ScalarValue::I32(lhs), ScalarValue::F32(rhs)) => {
1032                (lhs as f64).partial_cmp(&(rhs as f64))
1033            }
1034            (ScalarValue::I32(lhs), ScalarValue::F64(rhs)) => (lhs as f64).partial_cmp(&rhs),
1035            (ScalarValue::I32(lhs), ScalarValue::I32(rhs)) => lhs.partial_cmp(&rhs),
1036            (ScalarValue::I32(lhs), ScalarValue::I64(rhs)) => {
1037                (lhs as f64).partial_cmp(&(rhs as f64))
1038            }
1039            (ScalarValue::I32(lhs), ScalarValue::Bool(rhs)) => {
1040                (lhs as f64).partial_cmp(&(if rhs { 1.0 } else { 0.0 }))
1041            }
1042            (ScalarValue::I64(lhs), ScalarValue::F32(rhs)) => {
1043                (lhs as f64).partial_cmp(&(rhs as f64))
1044            }
1045            (ScalarValue::I64(lhs), ScalarValue::F64(rhs)) => (lhs as f64).partial_cmp(&rhs),
1046            (ScalarValue::I64(lhs), ScalarValue::I32(rhs)) => {
1047                (lhs as f64).partial_cmp(&(rhs as f64))
1048            }
1049            (ScalarValue::I64(lhs), ScalarValue::I64(rhs)) => lhs.partial_cmp(&rhs),
1050            (ScalarValue::I64(lhs), ScalarValue::Bool(rhs)) => {
1051                (lhs as f64).partial_cmp(&(if rhs { 1.0 } else { 0.0 }))
1052            }
1053            (ScalarValue::Bool(lhs), ScalarValue::F32(rhs)) => {
1054                (if lhs { 1.0 } else { 0.0 }).partial_cmp(&(rhs as f64))
1055            }
1056            (ScalarValue::Bool(lhs), ScalarValue::F64(rhs)) => {
1057                (if lhs { 1.0 } else { 0.0 }).partial_cmp(&rhs)
1058            }
1059            (ScalarValue::Bool(lhs), ScalarValue::I32(rhs)) => {
1060                (if lhs { 1.0 } else { 0.0 }).partial_cmp(&(rhs as f64))
1061            }
1062            (ScalarValue::Bool(lhs), ScalarValue::I64(rhs)) => {
1063                (if lhs { 1.0 } else { 0.0 }).partial_cmp(&(rhs as f64))
1064            }
1065            (ScalarValue::Bool(lhs), ScalarValue::Bool(rhs)) => lhs.partial_cmp(&rhs),
1066            _ => None,
1067        }
1068    }
1069}
1070
1071impl fmt::Display for BackendScalar {
1072    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1073        match self.value() {
1074            ScalarValue::F32(value) => value.fmt(f),
1075            ScalarValue::F64(value) => value.fmt(f),
1076            ScalarValue::I32(value) => value.fmt(f),
1077            ScalarValue::I64(value) => value.fmt(f),
1078            ScalarValue::Bool(value) => value.fmt(f),
1079            ScalarValue::C32(value) => value.fmt(f),
1080            ScalarValue::C64(value) => value.fmt(f),
1081        }
1082    }
1083}
1084
1085impl fmt::Debug for BackendScalar {
1086    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1087        f.debug_struct("BackendScalar")
1088            .field("dtype", &self.native.dtype())
1089            .field("value", &self.value())
1090            .finish()
1091    }
1092}
1093
1094impl Clone for BackendScalar {
1095    fn clone(&self) -> Self {
1096        match self.value {
1097            ScalarValue::F32(value) => Self::from_value(value),
1098            ScalarValue::F64(value) => Self::from_value(value),
1099            ScalarValue::I32(value) => Self::from_i32(value),
1100            ScalarValue::I64(value) => Self::from_i64(value),
1101            ScalarValue::Bool(value) => Self::from_bool(value),
1102            ScalarValue::C32(value) => Self::from_value(value),
1103            ScalarValue::C64(value) => Self::from_value(value),
1104        }
1105    }
1106}
1107
1108#[cfg(test)]
1109mod tests;