Skip to main content

tensor4all_core/
any_scalar.rs

1#[cfg(test)]
2use std::cell::Cell;
3use std::cmp::Ordering;
4use std::fmt;
5use std::ops::{Add, Div, Mul, Neg, Sub};
6use std::sync::Arc;
7
8use anyhow::{anyhow, Result};
9use num_complex::{Complex32, Complex64};
10use num_traits::{One, Zero};
11use tenferro::DType;
12use tensor4all_tensorbackend::BackendScalar;
13
14use crate::defaults::idx_tensor::IdxTensor;
15use crate::TensorElement;
16use tensor4all_tensorbackend::{Storage, SumFromStorage};
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19enum ScalarValue {
20    F32(f32),
21    F64(f64),
22    C32(Complex32),
23    C64(Complex64),
24}
25
26impl ScalarValue {
27    fn real(self) -> f64 {
28        match self {
29            Self::F32(value) => value as f64,
30            Self::F64(value) => value,
31            Self::C32(value) => value.re as f64,
32            Self::C64(value) => value.re,
33        }
34    }
35
36    fn imag(self) -> f64 {
37        match self {
38            Self::F32(_) | Self::F64(_) => 0.0,
39            Self::C32(value) => value.im as f64,
40            Self::C64(value) => value.im,
41        }
42    }
43
44    fn abs(self) -> f64 {
45        match self {
46            Self::F32(value) => value.abs() as f64,
47            Self::F64(value) => value.abs(),
48            Self::C32(value) => {
49                if value.re.is_nan() || value.im.is_nan() {
50                    f64::NAN
51                } else {
52                    f64::from(value.re).hypot(f64::from(value.im))
53                }
54            }
55            Self::C64(value) => {
56                if value.re.is_nan() || value.im.is_nan() {
57                    f64::NAN
58                } else {
59                    value.re.hypot(value.im)
60                }
61            }
62        }
63    }
64
65    fn is_complex(self) -> bool {
66        matches!(self, Self::C32(_) | Self::C64(_))
67    }
68
69    fn is_zero(self) -> bool {
70        match self {
71            Self::F32(value) => value == 0.0,
72            Self::F64(value) => value == 0.0,
73            Self::C32(value) => value == Complex32::new(0.0, 0.0),
74            Self::C64(value) => value == Complex64::new(0.0, 0.0),
75        }
76    }
77
78    fn into_complex(self) -> Complex64 {
79        match self {
80            Self::F32(value) => Complex64::new(value as f64, 0.0),
81            Self::F64(value) => Complex64::new(value, 0.0),
82            Self::C32(value) => Complex64::new(value.re as f64, value.im as f64),
83            Self::C64(value) => value,
84        }
85    }
86}
87
88trait ScalarTensorElement: TensorElement {
89    fn scalar_value(value: Self) -> ScalarValue;
90}
91
92impl ScalarTensorElement for f32 {
93    fn scalar_value(value: Self) -> ScalarValue {
94        ScalarValue::F32(value)
95    }
96}
97
98impl ScalarTensorElement for f64 {
99    fn scalar_value(value: Self) -> ScalarValue {
100        ScalarValue::F64(value)
101    }
102}
103
104impl ScalarTensorElement for Complex32 {
105    fn scalar_value(value: Self) -> ScalarValue {
106        ScalarValue::C32(value)
107    }
108}
109
110impl ScalarTensorElement for Complex64 {
111    fn scalar_value(value: Self) -> ScalarValue {
112        ScalarValue::C64(value)
113    }
114}
115
116#[cfg(test)]
117thread_local! {
118    static FORCE_ANY_SCALAR_TENSOR_INITIALIZATION_FAILURE: Cell<bool> = const { Cell::new(false) };
119}
120
121#[derive(Debug, Clone, thiserror::Error)]
122enum AnyScalarTensorError {
123    #[error("AnyScalar tensor initialization failed: {source}")]
124    Initialization {
125        #[source]
126        source: Arc<dyn std::error::Error + Send + Sync + 'static>,
127    },
128    #[error("AnyScalar::{op} failed: {source}")]
129    Operation {
130        op: &'static str,
131        #[source]
132        source: Arc<dyn std::error::Error + Send + Sync + 'static>,
133    },
134}
135
136fn initialize_tensor<T: ScalarTensorElement>(
137    value: T,
138) -> std::result::Result<IdxTensor, AnyScalarTensorError> {
139    #[cfg(test)]
140    if FORCE_ANY_SCALAR_TENSOR_INITIALIZATION_FAILURE.with(Cell::get) {
141        return Err(AnyScalarTensorError::Initialization {
142            source: Arc::new(std::io::Error::other(
143                "forced AnyScalar eager initialization failure",
144            )),
145        });
146    }
147
148    IdxTensor::scalar(value).map_err(|source| AnyScalarTensorError::Initialization {
149        source: Arc::from(anyhow::Error::new(source).into_boxed_dyn_error()),
150    })
151}
152
153/// Error returned by eager-tensor `AnyScalar` operations (autodiff, conjugation,
154/// and complex composition).
155///
156/// The full original diagnostic is preserved in [`AnyScalarError::source`], so
157/// callers can inspect the underlying tensor, AD-runtime, or configuration
158/// failure without losing context.
159///
160/// # Examples
161///
162/// ```
163/// use tensor4all_core::{AnyScalar, AnyScalarError};
164///
165/// let result: Result<AnyScalar, AnyScalarError> =
166///     AnyScalar::compose_complex(AnyScalar::new_real(1.0), AnyScalar::new_complex(0.0, 1.0));
167/// let err = result.unwrap_err();
168/// assert!(err.source.to_string().contains("real-valued"));
169/// ```
170#[derive(Debug, thiserror::Error)]
171#[error("AnyScalar eager-tensor operation failed: {source}")]
172pub struct AnyScalarError {
173    /// Original tensor, AD-runtime, or configuration diagnostic, including any
174    /// operation-specific context added by the failing call.
175    #[source]
176    pub source: anyhow::Error,
177}
178
179impl From<anyhow::Error> for AnyScalarError {
180    fn from(source: anyhow::Error) -> Self {
181        Self { source }
182    }
183}
184
185fn operation_error<E>(op: &'static str, source: E) -> anyhow::Error
186where
187    E: std::error::Error + Send + Sync + 'static,
188{
189    anyhow::Error::new(AnyScalarTensorError::Operation {
190        op,
191        source: Arc::new(source),
192    })
193}
194
195fn operation_error_from_anyhow(op: &'static str, source: anyhow::Error) -> anyhow::Error {
196    match source.downcast::<AnyScalarTensorError>() {
197        Ok(source) => anyhow::Error::new(source),
198        Err(source) => anyhow::Error::new(AnyScalarTensorError::Operation {
199            op,
200            source: Arc::from(source.into_boxed_dyn_error()),
201        }),
202    }
203}
204
205/// Dynamic scalar compatibility wrapper for tensor4all-core.
206/// This owns a rank-0 [`IdxTensor`] so that scalar values can participate in
207/// the same eager autodiff graph as tensors while preserving the existing
208/// dynamic scalar API shape. The infallible scalar constructors retain a
209/// tensor-initialization failure for later fallible tensor or AD operations.
210/// Infallible arithmetic also retains typed backend diagnostics and the
211/// tracked-state marker when an eager operation fails.
212#[derive(Clone)]
213pub struct AnyScalar {
214    tensor: std::result::Result<IdxTensor, AnyScalarTensorError>,
215    value: ScalarValue,
216    tracks_grad: bool,
217}
218
219impl AnyScalar {
220    fn wrap_tensor(tensor: IdxTensor) -> Result<Self> {
221        let dims = tensor.dims();
222        anyhow::ensure!(
223            dims.is_empty(),
224            "AnyScalar requires a rank-0 tensor, got dims {:?}",
225            dims
226        );
227        let value = Self::scalar_value_from_tensor(&tensor)?;
228        let tracks_grad = tensor.tracks_grad();
229        Ok(Self {
230            tensor: Ok(tensor),
231            value,
232            tracks_grad,
233        })
234    }
235
236    fn from_tensor_result(tensor: Result<IdxTensor>, op: &'static str) -> Result<Self> {
237        let tensor = tensor.map_err(|error| operation_error_from_anyhow(op, error))?;
238        Self::wrap_tensor(tensor).map_err(|error| operation_error_from_anyhow(op, error))
239    }
240
241    fn fallback_result(
242        result: Result<Self>,
243        op: &'static str,
244        fallback: impl FnOnce() -> ScalarValue,
245        tracks_grad: bool,
246    ) -> Self {
247        match result {
248            Ok(result) => result,
249            Err(error) => {
250                let error = match error.downcast::<AnyScalarTensorError>() {
251                    Ok(error) => error,
252                    Err(error) => AnyScalarTensorError::Operation {
253                        op,
254                        source: Arc::from(error.into_boxed_dyn_error()),
255                    },
256                };
257                Self {
258                    tensor: Err(error),
259                    value: fallback(),
260                    tracks_grad,
261                }
262            }
263        }
264    }
265
266    fn scalar_value_from_backend(value: BackendScalar) -> ScalarValue {
267        value
268            .as_c64()
269            .map(ScalarValue::C64)
270            .unwrap_or_else(|| ScalarValue::F64(value.real()))
271    }
272
273    fn zero_like(&self) -> Self {
274        match self.value() {
275            ScalarValue::F32(_) => Self::from_value(0.0_f32),
276            ScalarValue::F64(_) => Self::from_value(0.0_f64),
277            ScalarValue::C32(_) => Self::from_value(Complex32::new(0.0, 0.0)),
278            ScalarValue::C64(_) => Self::from_value(Complex64::new(0.0, 0.0)),
279        }
280    }
281
282    fn one_like(&self) -> Self {
283        match self.value() {
284            ScalarValue::F32(_) => Self::from_value(1.0_f32),
285            ScalarValue::F64(_) => Self::from_value(1.0_f64),
286            ScalarValue::C32(_) => Self::from_value(Complex32::new(1.0, 0.0)),
287            ScalarValue::C64(_) => Self::from_value(Complex64::new(1.0, 0.0)),
288        }
289    }
290
291    fn from_eager_binary<E>(
292        lhs: &Self,
293        rhs: &Self,
294        op: &'static str,
295        f: impl FnOnce(
296            &tenferro_ad::EagerTensor,
297            &tenferro_ad::EagerTensor,
298        ) -> std::result::Result<tenferro_ad::EagerTensor, E>,
299    ) -> Result<Self>
300    where
301        E: std::error::Error + Send + Sync + 'static,
302    {
303        let result = f(lhs.as_tensor()?.as_inner()?, rhs.as_tensor()?.as_inner()?)
304            .map_err(|error| operation_error(op, error))?;
305        Self::from_tensor_result(IdxTensor::from_inner(vec![], result), op)
306    }
307
308    fn from_eager_unary<E>(
309        input: &Self,
310        op: &'static str,
311        f: impl FnOnce(&tenferro_ad::EagerTensor) -> std::result::Result<tenferro_ad::EagerTensor, E>,
312    ) -> Result<Self>
313    where
314        E: std::error::Error + Send + Sync + 'static,
315    {
316        let result =
317            f(input.as_tensor()?.as_inner()?).map_err(|error| operation_error(op, error))?;
318        Self::from_tensor_result(IdxTensor::from_inner(vec![], result), op)
319    }
320
321    fn scalar_value_from_tensor(tensor: &IdxTensor) -> Result<ScalarValue> {
322        let inner = tensor.as_inner()?;
323        match inner.dtype() {
324            DType::F32 => inner
325                .value()?
326                .as_slice::<f32>()?
327                .first()
328                .copied()
329                .map(ScalarValue::F32)
330                .ok_or_else(|| anyhow!("rank-0 f32 scalar tensor is empty")),
331            DType::F64 => inner
332                .value()?
333                .as_slice::<f64>()?
334                .first()
335                .copied()
336                .map(ScalarValue::F64)
337                .ok_or_else(|| anyhow!("rank-0 f64 scalar tensor is empty")),
338            DType::C32 => inner
339                .value()?
340                .as_slice::<Complex32>()?
341                .first()
342                .copied()
343                .map(ScalarValue::C32)
344                .ok_or_else(|| anyhow!("rank-0 c32 scalar tensor is empty")),
345            DType::C64 => inner
346                .value()?
347                .as_slice::<Complex64>()?
348                .first()
349                .copied()
350                .map(ScalarValue::C64)
351                .ok_or_else(|| anyhow!("rank-0 c64 scalar tensor is empty")),
352            dtype => Err(anyhow!("unsupported scalar tensor dtype {dtype:?}")),
353        }
354    }
355
356    fn value(&self) -> ScalarValue {
357        self.value
358    }
359
360    fn from_backend_scalar(value: BackendScalar) -> Self {
361        match Self::scalar_value_from_backend(value) {
362            ScalarValue::F32(value) => Self::from_value(value),
363            ScalarValue::F64(value) => Self::from_value(value),
364            ScalarValue::C32(value) => Self::from_value(value),
365            ScalarValue::C64(value) => Self::from_value(value),
366        }
367    }
368
369    pub(crate) fn from_tensor(tensor: IdxTensor) -> Result<Self> {
370        Self::wrap_tensor(tensor)
371    }
372
373    pub(crate) fn as_tensor(&self) -> Result<&IdxTensor> {
374        self.tensor
375            .as_ref()
376            .map_err(|error| anyhow::Error::new(error.clone()))
377    }
378
379    /// Creates an `AnyScalar` from a tensor element.
380    ///
381    /// Use this when you already have a scalar value that implements
382    /// [`TensorElement`] and want to lift it into the dynamic scalar wrapper.
383    ///
384    /// # Arguments
385    ///
386    /// * `value` - The scalar value to wrap.
387    ///
388    /// # Returns
389    ///
390    /// A rank-0 `AnyScalar` containing `value`. The supported scalar types are
391    /// `f32`, `f64`, `Complex32`, and `Complex64`; the dtype is retained without
392    /// promotion.
393    ///
394    /// Tensor initialization is attempted eagerly. Because this constructor is
395    /// infallible, an initialization failure is retained and returned by later
396    /// tensor- or AD-dependent operations such as [`AnyScalar::enable_grad`].
397    /// Value-only accessors and non-AD arithmetic remain available.
398    ///
399    /// # Examples
400    ///
401    /// ```
402    /// use tensor4all_core::AnyScalar;
403    ///
404    /// let scalar = AnyScalar::from_value(3.0f64);
405    /// assert_eq!(scalar.real(), 3.0);
406    /// assert!(scalar.is_real());
407    /// ```
408    #[allow(private_bounds)]
409    pub fn from_value<T: ScalarTensorElement>(value: T) -> Self {
410        Self {
411            tensor: initialize_tensor(value),
412            value: T::scalar_value(value),
413            tracks_grad: false,
414        }
415    }
416
417    /// Creates a real-valued `AnyScalar`.
418    ///
419    /// This is a convenience wrapper around [`AnyScalar::from_value`].
420    ///
421    /// # Arguments
422    ///
423    /// * `x` - The real scalar value to wrap.
424    ///
425    /// # Returns
426    ///
427    /// A rank-0 `AnyScalar` with real dtype.
428    ///
429    /// # Examples
430    ///
431    /// ```
432    /// use tensor4all_core::AnyScalar;
433    ///
434    /// let scalar = AnyScalar::from_real(1.25);
435    /// assert_eq!(scalar.as_f64(), Some(1.25));
436    /// assert!(scalar.is_real());
437    /// ```
438    pub fn from_real(x: f64) -> Self {
439        Self::from_value(x)
440    }
441
442    /// Creates a complex-valued `AnyScalar`.
443    ///
444    /// This is a convenience wrapper around [`AnyScalar::from_value`].
445    ///
446    /// # Arguments
447    ///
448    /// * `re` - The real part of the complex value.
449    /// * `im` - The imaginary part of the complex value.
450    ///
451    /// # Returns
452    ///
453    /// A rank-0 `AnyScalar` containing the requested complex number.
454    ///
455    /// # Examples
456    ///
457    /// ```
458    /// use tensor4all_core::AnyScalar;
459    ///
460    /// let scalar = AnyScalar::from_complex(1.0, -2.0);
461    /// assert_eq!(scalar.as_c64().map(|z| (z.re, z.im)), Some((1.0, -2.0)));
462    /// assert!(scalar.is_complex());
463    /// ```
464    pub fn from_complex(re: f64, im: f64) -> Self {
465        Self::from_value(Complex64::new(re, im))
466    }
467
468    /// Creates a real-valued `AnyScalar`.
469    ///
470    /// This is an alias for [`AnyScalar::from_real`].
471    ///
472    /// # Arguments
473    ///
474    /// * `x` - The real scalar value to wrap.
475    ///
476    /// # Returns
477    ///
478    /// A rank-0 `AnyScalar` with real dtype.
479    ///
480    /// # Examples
481    ///
482    /// ```
483    /// use tensor4all_core::AnyScalar;
484    ///
485    /// let scalar = AnyScalar::new_real(2.5);
486    /// assert_eq!(scalar.real(), 2.5);
487    /// assert!(scalar.is_real());
488    /// ```
489    pub fn new_real(x: f64) -> Self {
490        Self::from_real(x)
491    }
492
493    /// Creates a complex-valued `AnyScalar`.
494    ///
495    /// This is an alias for [`AnyScalar::from_complex`].
496    ///
497    /// # Arguments
498    ///
499    /// * `re` - The real part of the complex value.
500    /// * `im` - The imaginary part of the complex value.
501    ///
502    /// # Returns
503    ///
504    /// A rank-0 `AnyScalar` containing the requested complex number.
505    ///
506    /// # Examples
507    ///
508    /// ```
509    /// use tensor4all_core::AnyScalar;
510    ///
511    /// let scalar = AnyScalar::new_complex(2.0, 3.0);
512    /// assert_eq!(scalar.as_c64().map(|z| (z.re, z.im)), Some((2.0, 3.0)));
513    /// assert!(scalar.is_complex());
514    /// ```
515    pub fn new_complex(re: f64, im: f64) -> Self {
516        Self::from_complex(re, im)
517    }
518
519    /// Returns the detached primal value of this scalar.
520    ///
521    /// This is an alias for [`AnyScalar::detach`].
522    ///
523    /// # Returns
524    ///
525    /// A scalar with the same value and no gradient tracking.
526    ///
527    /// # Errors
528    ///
529    /// Returns an error when the scalar is not a tracked leaf (a missing-graph
530    /// /// failure).
531    ///
532    /// # Examples
533    ///
534    /// ```
535    /// use tensor4all_core::AnyScalar;
536    ///
537    /// let primal = AnyScalar::new_real(5.0).enable_grad().unwrap().primal().unwrap();
538    /// assert_eq!(primal.real(), 5.0);
539    /// assert!(!primal.tracks_grad());
540    /// ```
541    pub fn primal(&self) -> std::result::Result<Self, AnyScalarError> {
542        self.detach()
543    }
544
545    /// Enables gradient tracking for this scalar.
546    ///
547    /// # Returns
548    ///
549    /// A new scalar that shares the same value but participates in autodiff.
550    ///
551    /// # Errors
552    ///
553    /// Returns the original tensor-initialization diagnostic if this scalar's
554    /// eager backend tensor could not be created, or propagates an AD runtime
555    /// failure while enabling gradients.
556    ///
557    /// # Examples
558    ///
559    /// ```
560    /// use tensor4all_core::AnyScalar;
561    ///
562    /// let scalar = AnyScalar::new_real(2.0).enable_grad().unwrap();
563    /// assert!(scalar.tracks_grad());
564    /// ```
565    pub fn enable_grad(self) -> std::result::Result<Self, AnyScalarError> {
566        let tensor = self.tensor.map_err(anyhow::Error::new)?;
567        Self::from_tensor(tensor.enable_grad().map_err(anyhow::Error::from)?)
568            .map_err(AnyScalarError::from)
569    }
570
571    /// Returns whether this scalar tracks gradients.
572    ///
573    /// # Returns
574    ///
575    /// `true` when the scalar participates in autodiff or retains a failed
576    /// tracked operation, otherwise `false`.
577    ///
578    /// # Examples
579    ///
580    /// ```
581    /// use tensor4all_core::AnyScalar;
582    ///
583    /// let scalar = AnyScalar::new_real(1.0);
584    /// assert!(!scalar.tracks_grad());
585    /// ```
586    pub fn tracks_grad(&self) -> bool {
587        self.tracks_grad || self.tensor.as_ref().is_ok_and(IdxTensor::tracks_grad)
588    }
589
590    /// Returns the stored gradient, if any.
591    ///
592    /// # Returns
593    ///
594    /// `Ok(Some(_))` when a gradient is available, `Ok(None)` when no gradient
595    /// has been recorded, or an error if the backend cannot read it.
596    ///
597    /// # Errors
598    ///
599    /// Returns an error when the scalar is not a tracked leaf or the gradient is
600    /// /// unavailable (a missing-graph or dtype mismatch failure).
601    ///
602    /// # Examples
603    ///
604    /// ```
605    /// use tensor4all_core::AnyScalar;
606    ///
607    /// let x = AnyScalar::new_real(2.0).enable_grad().unwrap();
608    /// let y = &x * &x;
609    /// y.backward().unwrap();
610    ///
611    /// let grad = x.grad().unwrap().unwrap();
612    /// assert_eq!(grad.real(), 4.0);
613    /// ```
614    pub fn grad(&self) -> std::result::Result<Option<Self>, AnyScalarError> {
615        self.as_tensor()?
616            .grad()
617            .map_err(anyhow::Error::from)
618            .and_then(|maybe_grad| maybe_grad.map(Self::from_tensor).transpose())
619            .map_err(AnyScalarError::from)
620    }
621
622    /// Clears the stored gradient for this scalar.
623    ///
624    /// # Returns
625    ///
626    /// `Ok(())` when the gradient buffer was cleared successfully.
627    ///
628    /// # Errors
629    ///
630    /// Returns an error when the scalar is not a tracked leaf (a missing-graph
631    /// /// failure).
632    ///
633    /// # Examples
634    ///
635    /// ```
636    /// use tensor4all_core::AnyScalar;
637    ///
638    /// let x = AnyScalar::new_real(2.0).enable_grad().unwrap();
639    /// let y = &x * &x;
640    /// y.backward().unwrap();
641    /// assert!(x.grad().unwrap().is_some());
642    ///
643    /// x.clear_grad().unwrap();
644    /// assert!(x.grad().unwrap().is_none());
645    /// ```
646    pub fn clear_grad(&self) -> std::result::Result<(), AnyScalarError> {
647        self.as_tensor()?
648            .clear_grad()
649            .map_err(anyhow::Error::from)
650            .map_err(AnyScalarError::from)
651    }
652
653    /// Runs reverse-mode autodiff starting from this scalar.
654    ///
655    /// # Returns
656    ///
657    /// `Ok(())` when gradients were accumulated successfully.
658    ///
659    /// # Errors
660    ///
661    /// Returns an error when the scalar is not a scalar-valued leaf or the reverse
662    /// /// pass fails (a graph failure).
663    ///
664    /// # Examples
665    ///
666    /// ```
667    /// use tensor4all_core::AnyScalar;
668    ///
669    /// let x = AnyScalar::new_real(2.0).enable_grad().unwrap();
670    /// let y = &x * &x;
671    /// y.backward().unwrap();
672    ///
673    /// let grad = x.grad().unwrap().unwrap();
674    /// assert_eq!(grad.real(), 4.0);
675    /// ```
676    pub fn backward(&self) -> std::result::Result<(), AnyScalarError> {
677        self.as_tensor()?
678            .backward()
679            .map_err(anyhow::Error::from)
680            .map_err(AnyScalarError::from)
681    }
682
683    /// Returns a detached copy of this scalar.
684    ///
685    /// # Returns
686    ///
687    /// A scalar with the same value but without gradient tracking.
688    ///
689    /// # Errors
690    ///
691    /// Returns an error when the scalar is not a tracked leaf (a missing-graph
692    /// /// failure).
693    ///
694    /// # Examples
695    ///
696    /// ```
697    /// use tensor4all_core::AnyScalar;
698    ///
699    /// let detached = AnyScalar::new_real(7.0)
700    ///     .enable_grad()
701    ///     .unwrap()
702    ///     .detach()
703    ///     .unwrap();
704    /// assert_eq!(detached.real(), 7.0);
705    /// assert!(!detached.tracks_grad());
706    /// ```
707    pub fn detach(&self) -> std::result::Result<Self, AnyScalarError> {
708        Self::from_tensor(self.as_tensor()?.detach().map_err(anyhow::Error::from)?)
709            .map_err(AnyScalarError::from)
710    }
711
712    /// Returns the real part of this scalar.
713    ///
714    /// # Returns
715    ///
716    /// The real component as an `f64`, regardless of the underlying storage
717    /// type.
718    ///
719    /// # Examples
720    ///
721    /// ```
722    /// use tensor4all_core::AnyScalar;
723    ///
724    /// let scalar = AnyScalar::new_complex(3.0, -4.0);
725    /// assert_eq!(scalar.real(), 3.0);
726    /// ```
727    pub fn real(&self) -> f64 {
728        self.value().real()
729    }
730
731    /// Returns the imaginary part of this scalar.
732    ///
733    /// # Returns
734    ///
735    /// The imaginary component as an `f64`. Real-valued scalars return `0.0`.
736    ///
737    /// # Examples
738    ///
739    /// ```
740    /// use tensor4all_core::AnyScalar;
741    ///
742    /// let scalar = AnyScalar::new_complex(3.0, -4.0);
743    /// assert_eq!(scalar.imag(), -4.0);
744    /// ```
745    pub fn imag(&self) -> f64 {
746        self.value().imag()
747    }
748
749    /// Returns the magnitude of this scalar.
750    ///
751    /// # Returns
752    ///
753    /// The absolute value for real scalars or the complex norm for complex
754    /// scalars.
755    ///
756    /// # Examples
757    ///
758    /// ```
759    /// use tensor4all_core::AnyScalar;
760    ///
761    /// let scalar = AnyScalar::new_complex(3.0, -4.0);
762    /// assert_eq!(scalar.abs(), 5.0);
763    /// ```
764    pub fn abs(&self) -> f64 {
765        self.value().abs()
766    }
767
768    /// Returns whether this scalar is complex-valued.
769    ///
770    /// # Returns
771    ///
772    /// `true` for complex dtypes and `false` for real or integer dtypes.
773    ///
774    /// # Examples
775    ///
776    /// ```
777    /// use tensor4all_core::AnyScalar;
778    ///
779    /// assert!(AnyScalar::new_complex(1.0, 2.0).is_complex());
780    /// assert!(!AnyScalar::new_real(1.0).is_complex());
781    /// ```
782    pub fn is_complex(&self) -> bool {
783        self.value().is_complex()
784    }
785
786    /// Returns whether this scalar is real-valued.
787    ///
788    /// # Returns
789    ///
790    /// `true` when the scalar is not complex-valued.
791    ///
792    /// # Examples
793    ///
794    /// ```
795    /// use tensor4all_core::AnyScalar;
796    ///
797    /// assert!(AnyScalar::new_real(1.0).is_real());
798    /// assert!(!AnyScalar::new_complex(1.0, 2.0).is_real());
799    /// ```
800    pub fn is_real(&self) -> bool {
801        !self.is_complex()
802    }
803
804    /// Returns whether this scalar is exactly zero.
805    ///
806    /// # Returns
807    ///
808    /// `true` for exact zeros and `false` for any nonzero value.
809    ///
810    /// # Examples
811    ///
812    /// ```
813    /// use tensor4all_core::AnyScalar;
814    ///
815    /// assert!(AnyScalar::new_real(0.0).is_zero());
816    /// assert!(!AnyScalar::new_complex(0.0, 1.0).is_zero());
817    /// ```
818    pub fn is_zero(&self) -> bool {
819        self.value().is_zero()
820    }
821
822    /// Returns this scalar as an `f64` when it is real-valued.
823    ///
824    /// # Returns
825    ///
826    /// `Some(value)` for real and integer scalars, or `None` for complex
827    /// scalars.
828    ///
829    /// # Examples
830    ///
831    /// ```
832    /// use tensor4all_core::AnyScalar;
833    ///
834    /// assert_eq!(AnyScalar::new_real(2.5).as_f64(), Some(2.5));
835    /// assert_eq!(AnyScalar::new_complex(2.5, 1.0).as_f64(), None);
836    /// ```
837    pub fn as_f64(&self) -> Option<f64> {
838        match self.value() {
839            ScalarValue::F32(value) => Some(value as f64),
840            ScalarValue::F64(value) => Some(value),
841            ScalarValue::C32(_) | ScalarValue::C64(_) => None,
842        }
843    }
844
845    /// Returns this scalar as a `Complex64` when it is complex-valued.
846    ///
847    /// # Returns
848    ///
849    /// `Some(value)` for complex scalars or `None` for real and integer
850    /// scalars.
851    ///
852    /// # Examples
853    ///
854    /// ```
855    /// use tensor4all_core::AnyScalar;
856    ///
857    /// let scalar = AnyScalar::new_complex(2.5, 1.0);
858    /// assert_eq!(scalar.as_c64().map(|z| (z.re, z.im)), Some((2.5, 1.0)));
859    /// assert_eq!(AnyScalar::new_real(2.5).as_c64(), None);
860    /// ```
861    pub fn as_c64(&self) -> Option<Complex64> {
862        match self.value() {
863            ScalarValue::F32(_) | ScalarValue::F64(_) => None,
864            ScalarValue::C32(value) => Some(Complex64::new(value.re as f64, value.im as f64)),
865            ScalarValue::C64(value) => Some(value),
866        }
867    }
868
869    /// Returns the complex conjugate of this scalar.
870    ///
871    /// # Returns
872    ///
873    /// The conjugated scalar. Real-valued inputs are returned unchanged.
874    ///
875    /// # Errors
876    ///
877    /// Returns an error when the conjugation fails (a dtype mismatch or backend
878    /// /// failure).
879    ///
880    /// # Examples
881    ///
882    /// ```
883    /// use tensor4all_core::AnyScalar;
884    ///
885    /// let scalar = AnyScalar::new_complex(3.0, -4.0).conj();
886    /// assert_eq!(scalar.as_c64().map(|z| (z.re, z.im)), Some((3.0, 4.0)));
887    /// ```
888    pub fn try_conj(&self) -> std::result::Result<Self, AnyScalarError> {
889        self.as_tensor()?;
890        if !self.tracks_grad() {
891            return Ok(Self::from_backend_scalar(self.to_backend_scalar().conj()));
892        }
893        Self::from_eager_unary(self, "conj", |tensor| tensor.conj()).map_err(AnyScalarError::from)
894    }
895
896    /// Returns the complex conjugate of this scalar.
897    pub fn conj(&self) -> Self {
898        Self::fallback_result(
899            self.try_conj().map_err(|error| error.source),
900            "conj",
901            || Self::scalar_value_from_backend(self.to_backend_scalar().conj()),
902            self.tracks_grad(),
903        )
904    }
905
906    /// Returns the real part as a real-valued scalar.
907    ///
908    /// # Returns
909    ///
910    /// A real-valued scalar containing the real component of `self`.
911    ///
912    /// # Examples
913    ///
914    /// ```
915    /// use tensor4all_core::AnyScalar;
916    ///
917    /// let scalar = AnyScalar::new_complex(3.0, -4.0).real_part();
918    /// assert_eq!(scalar.real(), 3.0);
919    /// assert!(scalar.is_real());
920    /// ```
921    pub fn real_part(&self) -> Self {
922        Self::fallback_result(
923            self.try_real_part(),
924            "real_part",
925            || Self::from_real(self.real()).value(),
926            self.tracks_grad(),
927        )
928    }
929
930    /// Returns the imaginary part as a real-valued scalar.
931    ///
932    /// # Returns
933    ///
934    /// A real-valued scalar containing the imaginary component of `self`.
935    ///
936    /// # Examples
937    ///
938    /// ```
939    /// use tensor4all_core::AnyScalar;
940    ///
941    /// let scalar = AnyScalar::new_complex(3.0, -4.0).imag_part();
942    /// assert_eq!(scalar.real(), -4.0);
943    /// assert!(scalar.is_real());
944    /// ```
945    pub fn imag_part(&self) -> Self {
946        Self::fallback_result(
947            self.try_imag_part(),
948            "imag_part",
949            || Self::from_real(self.imag()).value(),
950            self.tracks_grad(),
951        )
952    }
953
954    /// Combines two real-valued scalars into a complex scalar.
955    ///
956    /// # Arguments
957    ///
958    /// * `real` - The real component.
959    /// * `imag` - The imaginary component.
960    ///
961    /// # Returns
962    ///
963    /// A complex `AnyScalar` whose real and imaginary parts come from the
964    /// inputs.
965    ///
966    /// # Errors
967    ///
968    /// Returns an error when the components cannot be composed (a dtype mismatch
969    /// or a backend failure).
970    ///
971    /// # Examples
972    ///
973    /// ```
974    /// use tensor4all_core::AnyScalar;
975    ///
976    /// let scalar = AnyScalar::compose_complex(
977    ///     AnyScalar::new_real(3.0),
978    ///     AnyScalar::new_real(-4.0),
979    /// )
980    /// .unwrap();
981    /// assert_eq!(scalar.as_c64().map(|z| (z.re, z.im)), Some((3.0, -4.0)));
982    /// ```
983    pub fn compose_complex(real: Self, imag: Self) -> std::result::Result<Self, AnyScalarError> {
984        if !real.is_real() || !imag.is_real() {
985            return Err(anyhow!("compose_complex requires real-valued inputs").into());
986        }
987        let imag_term = imag.try_mul(&Self::new_complex(0.0, 1.0))?;
988        real.try_add(&imag_term).map_err(AnyScalarError::from)
989    }
990
991    /// Returns the square root of this scalar.
992    ///
993    /// # Returns
994    ///
995    /// The principal square root. Negative real inputs and complex inputs use
996    /// complex arithmetic.
997    ///
998    /// # Examples
999    ///
1000    /// ```
1001    /// use tensor4all_core::AnyScalar;
1002    ///
1003    /// let scalar = AnyScalar::new_real(9.0).sqrt();
1004    /// assert_eq!(scalar.real(), 3.0);
1005    /// assert!(scalar.is_real());
1006    /// ```
1007    pub fn sqrt(&self) -> Self {
1008        Self::fallback_result(
1009            self.try_sqrt(),
1010            "sqrt",
1011            || Self::scalar_value_from_backend(self.to_backend_scalar().sqrt()),
1012            self.tracks_grad(),
1013        )
1014    }
1015
1016    /// Raises this scalar to a floating-point power.
1017    ///
1018    /// # Arguments
1019    ///
1020    /// * `exponent` - The exponent to apply.
1021    ///
1022    /// # Returns
1023    ///
1024    /// The value of `self^exponent`.
1025    ///
1026    /// # Examples
1027    ///
1028    /// ```
1029    /// use tensor4all_core::AnyScalar;
1030    ///
1031    /// let scalar = AnyScalar::new_real(2.0).powf(3.0);
1032    /// assert_eq!(scalar.real(), 8.0);
1033    /// ```
1034    pub fn powf(&self, exponent: f64) -> Self {
1035        Self::fallback_result(
1036            self.try_powf(exponent),
1037            "powf",
1038            || Self::scalar_value_from_backend(self.to_backend_scalar().powf(exponent)),
1039            self.tracks_grad(),
1040        )
1041    }
1042
1043    /// Raises this scalar to an integer power.
1044    ///
1045    /// # Arguments
1046    ///
1047    /// * `exponent` - The integer exponent to apply. Negative exponents return
1048    ///
1049    ///   the reciprocal power.
1050    ///
1051    /// # Returns
1052    ///
1053    /// The value of `self^exponent`. Zero exponents return `1`.
1054    ///
1055    /// # Examples
1056    ///
1057    /// ```
1058    /// use tensor4all_core::AnyScalar;
1059    ///
1060    /// assert_eq!(AnyScalar::new_real(2.0).powi(3).real(), 8.0);
1061    /// assert_eq!(AnyScalar::new_real(2.0).powi(-1).real(), 0.5);
1062    /// ```
1063    pub fn powi(&self, exponent: i32) -> Self {
1064        Self::fallback_result(
1065            self.try_powi(exponent),
1066            "powi",
1067            || Self::scalar_value_from_backend(self.to_backend_scalar().powi(exponent)),
1068            self.tracks_grad(),
1069        )
1070    }
1071
1072    pub(crate) fn to_backend_scalar(&self) -> BackendScalar {
1073        match self.value() {
1074            ScalarValue::F32(value) => BackendScalar::from_value(value),
1075            ScalarValue::F64(value) => BackendScalar::from_value(value),
1076            ScalarValue::C32(value) => BackendScalar::from_value(value),
1077            ScalarValue::C64(value) => BackendScalar::from_value(value),
1078        }
1079    }
1080
1081    pub(crate) fn try_add(&self, rhs: &Self) -> Result<Self> {
1082        self.as_tensor()?;
1083        rhs.as_tensor()?;
1084        if !self.tracks_grad() && !rhs.tracks_grad() {
1085            return Ok(Self::from_backend_scalar(
1086                self.to_backend_scalar() + rhs.to_backend_scalar(),
1087            ));
1088        }
1089        Self::from_eager_binary(self, rhs, "add", |lhs, rhs| lhs.add(rhs))
1090    }
1091
1092    pub(crate) fn try_mul(&self, rhs: &Self) -> Result<Self> {
1093        self.as_tensor()?;
1094        rhs.as_tensor()?;
1095        if !self.tracks_grad() && !rhs.tracks_grad() {
1096            return Ok(Self::from_backend_scalar(
1097                self.to_backend_scalar() * rhs.to_backend_scalar(),
1098            ));
1099        }
1100        Self::from_eager_binary(self, rhs, "mul", |lhs, rhs| lhs.mul(rhs))
1101    }
1102
1103    pub(crate) fn try_div(&self, rhs: &Self) -> Result<Self> {
1104        self.as_tensor()?;
1105        rhs.as_tensor()?;
1106        if !self.tracks_grad() && !rhs.tracks_grad() {
1107            return Ok(Self::from_backend_scalar(
1108                self.to_backend_scalar() / rhs.to_backend_scalar(),
1109            ));
1110        }
1111        Self::from_eager_binary(self, rhs, "div", |lhs, rhs| lhs.div(rhs))
1112    }
1113
1114    pub(crate) fn try_neg(&self) -> Result<Self> {
1115        self.as_tensor()?;
1116        if !self.tracks_grad() {
1117            return Ok(Self::from_backend_scalar(-self.to_backend_scalar()));
1118        }
1119        Self::from_eager_unary(self, "neg", |tensor| tensor.neg())
1120    }
1121
1122    fn try_real_part(&self) -> Result<Self> {
1123        self.as_tensor()?;
1124        if !self.tracks_grad() {
1125            return Ok(Self::from_real(self.real()));
1126        }
1127        if self.is_complex() {
1128            Self::from_eager_unary(self, "real_part", |tensor| tensor.cast(DType::F64))
1129        } else {
1130            self.try_mul(&Self::new_real(1.0))
1131        }
1132    }
1133
1134    fn try_imag_part(&self) -> Result<Self> {
1135        self.as_tensor()?;
1136        if !self.tracks_grad() {
1137            return Ok(Self::from_real(self.imag()));
1138        }
1139        if self.is_complex() {
1140            let factor = Self::new_complex(0.0, -1.0);
1141            let imaginary =
1142                Self::from_eager_binary(self, &factor, "imag_part", |value, factor| {
1143                    value.mul(factor)
1144                })?;
1145            Self::from_eager_unary(&imaginary, "imag_part", |tensor| tensor.cast(DType::F64))
1146        } else {
1147            self.try_mul(&Self::new_real(0.0))
1148        }
1149    }
1150
1151    fn try_sqrt(&self) -> Result<Self> {
1152        self.as_tensor()?;
1153        if !self.tracks_grad() {
1154            return Ok(Self::from_backend_scalar(self.to_backend_scalar().sqrt()));
1155        }
1156        if self.is_real() && self.real() < 0.0 {
1157            let magnitude_input = Self::from_eager_unary(self, "sqrt", |tensor| tensor.neg())?;
1158            let magnitude = magnitude_input.try_sqrt()?;
1159            let factor = Self::new_complex(0.0, 1.0);
1160            return Self::from_eager_binary(&magnitude, &factor, "sqrt", |value, factor| {
1161                value.mul(factor)
1162            });
1163        }
1164        Self::from_eager_unary(self, "sqrt", |tensor| tensor.sqrt())
1165    }
1166
1167    fn try_powf(&self, exponent: f64) -> Result<Self> {
1168        self.as_tensor()?;
1169        if !self.tracks_grad() {
1170            return Ok(Self::from_backend_scalar(
1171                self.to_backend_scalar().powf(exponent),
1172            ));
1173        }
1174        if self.is_real() && self.real() < 0.0 && exponent.fract() != 0.0 {
1175            let magnitude_input = Self::from_eager_unary(self, "powf", |tensor| tensor.neg())?;
1176            let magnitude = magnitude_input.try_powf(exponent)?;
1177            let phase = std::f64::consts::PI * exponent;
1178            let factor = Self::new_complex(phase.cos(), phase.sin());
1179            return Self::from_eager_binary(&magnitude, &factor, "powf", |value, factor| {
1180                value.mul(factor)
1181            });
1182        }
1183        let exponent = if self.is_complex() {
1184            Self::new_complex(exponent, 0.0)
1185        } else {
1186            Self::new_real(exponent)
1187        };
1188        Self::from_eager_binary(self, &exponent, "powf", |base, exponent| base.pow(exponent))
1189    }
1190
1191    fn try_powi(&self, exponent: i32) -> Result<Self> {
1192        self.as_tensor()?;
1193        if exponent == 0 {
1194            if self.tracks_grad() {
1195                // Build 1 as `self * 0 + 1`, rather than evaluating x^0.
1196                // This keeps the result in the graph and has an exact zero
1197                // derivative even when the input is zero.
1198                let zeroed = self.try_mul(&self.zero_like())?;
1199                return zeroed.try_add(&self.one_like());
1200            }
1201            return Ok(Self::one());
1202        }
1203        if self.tracks_grad() {
1204            return self.try_powf(exponent as f64);
1205        }
1206        Ok(Self::from_backend_scalar(
1207            self.to_backend_scalar().powi(exponent),
1208        ))
1209    }
1210}
1211
1212impl SumFromStorage for AnyScalar {
1213    fn sum_from_storage(storage: &Storage) -> Self {
1214        Self::from_backend_scalar(BackendScalar::sum_from_storage(storage))
1215    }
1216}
1217
1218impl From<f32> for AnyScalar {
1219    fn from(value: f32) -> Self {
1220        Self::from_value(value)
1221    }
1222}
1223
1224impl From<f64> for AnyScalar {
1225    fn from(value: f64) -> Self {
1226        Self::from_value(value)
1227    }
1228}
1229
1230impl From<Complex32> for AnyScalar {
1231    fn from(value: Complex32) -> Self {
1232        Self::from_value(value)
1233    }
1234}
1235
1236impl From<Complex64> for AnyScalar {
1237    fn from(value: Complex64) -> Self {
1238        Self::from_value(value)
1239    }
1240}
1241
1242impl TryFrom<AnyScalar> for f64 {
1243    type Error = &'static str;
1244
1245    fn try_from(value: AnyScalar) -> std::result::Result<Self, Self::Error> {
1246        value.as_f64().ok_or("cannot convert complex scalar to f64")
1247    }
1248}
1249
1250impl From<AnyScalar> for Complex64 {
1251    fn from(value: AnyScalar) -> Self {
1252        value.value().into_complex()
1253    }
1254}
1255
1256impl Add<&AnyScalar> for &AnyScalar {
1257    type Output = AnyScalar;
1258
1259    fn add(self, rhs: &AnyScalar) -> Self::Output {
1260        AnyScalar::fallback_result(
1261            self.try_add(rhs),
1262            "add",
1263            || {
1264                AnyScalar::scalar_value_from_backend(
1265                    self.to_backend_scalar() + rhs.to_backend_scalar(),
1266                )
1267            },
1268            self.tracks_grad() || rhs.tracks_grad(),
1269        )
1270    }
1271}
1272
1273impl Add<AnyScalar> for AnyScalar {
1274    type Output = AnyScalar;
1275
1276    fn add(self, rhs: AnyScalar) -> Self::Output {
1277        Add::add(&self, &rhs)
1278    }
1279}
1280
1281impl Add<AnyScalar> for &AnyScalar {
1282    type Output = AnyScalar;
1283
1284    fn add(self, rhs: AnyScalar) -> Self::Output {
1285        Add::add(self, &rhs)
1286    }
1287}
1288
1289impl Add<&AnyScalar> for AnyScalar {
1290    type Output = AnyScalar;
1291
1292    fn add(self, rhs: &AnyScalar) -> Self::Output {
1293        Add::add(&self, rhs)
1294    }
1295}
1296
1297impl Sub<&AnyScalar> for &AnyScalar {
1298    type Output = AnyScalar;
1299
1300    fn sub(self, rhs: &AnyScalar) -> Self::Output {
1301        Add::add(self, &Neg::neg(rhs))
1302    }
1303}
1304
1305impl Sub<AnyScalar> for AnyScalar {
1306    type Output = AnyScalar;
1307
1308    fn sub(self, rhs: AnyScalar) -> Self::Output {
1309        Sub::sub(&self, &rhs)
1310    }
1311}
1312
1313impl Sub<AnyScalar> for &AnyScalar {
1314    type Output = AnyScalar;
1315
1316    fn sub(self, rhs: AnyScalar) -> Self::Output {
1317        Sub::sub(self, &rhs)
1318    }
1319}
1320
1321impl Sub<&AnyScalar> for AnyScalar {
1322    type Output = AnyScalar;
1323
1324    fn sub(self, rhs: &AnyScalar) -> Self::Output {
1325        Sub::sub(&self, rhs)
1326    }
1327}
1328
1329impl Mul<&AnyScalar> for &AnyScalar {
1330    type Output = AnyScalar;
1331
1332    fn mul(self, rhs: &AnyScalar) -> Self::Output {
1333        AnyScalar::fallback_result(
1334            self.try_mul(rhs),
1335            "mul",
1336            || {
1337                AnyScalar::scalar_value_from_backend(
1338                    self.to_backend_scalar() * rhs.to_backend_scalar(),
1339                )
1340            },
1341            self.tracks_grad() || rhs.tracks_grad(),
1342        )
1343    }
1344}
1345
1346impl Mul<AnyScalar> for AnyScalar {
1347    type Output = AnyScalar;
1348
1349    fn mul(self, rhs: AnyScalar) -> Self::Output {
1350        Mul::mul(&self, &rhs)
1351    }
1352}
1353
1354impl Mul<AnyScalar> for &AnyScalar {
1355    type Output = AnyScalar;
1356
1357    fn mul(self, rhs: AnyScalar) -> Self::Output {
1358        Mul::mul(self, &rhs)
1359    }
1360}
1361
1362impl Mul<&AnyScalar> for AnyScalar {
1363    type Output = AnyScalar;
1364
1365    fn mul(self, rhs: &AnyScalar) -> Self::Output {
1366        Mul::mul(&self, rhs)
1367    }
1368}
1369
1370impl Div<&AnyScalar> for &AnyScalar {
1371    type Output = AnyScalar;
1372
1373    fn div(self, rhs: &AnyScalar) -> Self::Output {
1374        AnyScalar::fallback_result(
1375            self.try_div(rhs),
1376            "div",
1377            || {
1378                AnyScalar::scalar_value_from_backend(
1379                    self.to_backend_scalar() / rhs.to_backend_scalar(),
1380                )
1381            },
1382            self.tracks_grad() || rhs.tracks_grad(),
1383        )
1384    }
1385}
1386
1387impl Div<AnyScalar> for AnyScalar {
1388    type Output = AnyScalar;
1389
1390    fn div(self, rhs: AnyScalar) -> Self::Output {
1391        Div::div(&self, &rhs)
1392    }
1393}
1394
1395impl Div<AnyScalar> for &AnyScalar {
1396    type Output = AnyScalar;
1397
1398    fn div(self, rhs: AnyScalar) -> Self::Output {
1399        Div::div(self, &rhs)
1400    }
1401}
1402
1403impl Div<&AnyScalar> for AnyScalar {
1404    type Output = AnyScalar;
1405
1406    fn div(self, rhs: &AnyScalar) -> Self::Output {
1407        Div::div(&self, rhs)
1408    }
1409}
1410
1411impl Neg for &AnyScalar {
1412    type Output = AnyScalar;
1413
1414    fn neg(self) -> Self::Output {
1415        AnyScalar::fallback_result(
1416            self.try_neg(),
1417            "neg",
1418            || AnyScalar::scalar_value_from_backend(-self.to_backend_scalar()),
1419            self.tracks_grad(),
1420        )
1421    }
1422}
1423
1424impl Neg for AnyScalar {
1425    type Output = AnyScalar;
1426
1427    fn neg(self) -> Self::Output {
1428        Neg::neg(&self)
1429    }
1430}
1431
1432impl Mul<AnyScalar> for f64 {
1433    type Output = AnyScalar;
1434
1435    fn mul(self, rhs: AnyScalar) -> Self::Output {
1436        AnyScalar::from_real(self) * rhs
1437    }
1438}
1439
1440impl Mul<AnyScalar> for Complex64 {
1441    type Output = AnyScalar;
1442
1443    fn mul(self, rhs: AnyScalar) -> Self::Output {
1444        AnyScalar::from(self) * rhs
1445    }
1446}
1447
1448impl Div<AnyScalar> for Complex64 {
1449    type Output = AnyScalar;
1450
1451    fn div(self, rhs: AnyScalar) -> Self::Output {
1452        AnyScalar::from(self) / rhs
1453    }
1454}
1455
1456impl Default for AnyScalar {
1457    fn default() -> Self {
1458        Self::zero()
1459    }
1460}
1461
1462impl Zero for AnyScalar {
1463    fn zero() -> Self {
1464        Self::from_real(0.0)
1465    }
1466
1467    fn is_zero(&self) -> bool {
1468        AnyScalar::is_zero(self)
1469    }
1470}
1471
1472impl One for AnyScalar {
1473    fn one() -> Self {
1474        Self::from_real(1.0)
1475    }
1476}
1477
1478impl PartialEq for AnyScalar {
1479    fn eq(&self, other: &Self) -> bool {
1480        self.value() == other.value()
1481    }
1482}
1483
1484impl PartialOrd for AnyScalar {
1485    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1486        match (self.value(), other.value()) {
1487            (ScalarValue::F32(lhs), ScalarValue::F32(rhs)) => lhs.partial_cmp(&rhs),
1488            (ScalarValue::F32(lhs), ScalarValue::F64(rhs)) => (lhs as f64).partial_cmp(&rhs),
1489            (ScalarValue::F64(lhs), ScalarValue::F32(rhs)) => lhs.partial_cmp(&(rhs as f64)),
1490            (ScalarValue::F64(lhs), ScalarValue::F64(rhs)) => lhs.partial_cmp(&rhs),
1491            _ => None,
1492        }
1493    }
1494}
1495
1496impl fmt::Display for AnyScalar {
1497    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1498        match self.value() {
1499            ScalarValue::F32(value) => value.fmt(f),
1500            ScalarValue::F64(value) => value.fmt(f),
1501            ScalarValue::C32(value) => value.fmt(f),
1502            ScalarValue::C64(value) => value.fmt(f),
1503        }
1504    }
1505}
1506
1507impl fmt::Debug for AnyScalar {
1508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1509        let dtype = match self.value {
1510            ScalarValue::F32(_) => "f32",
1511            ScalarValue::F64(_) => "f64",
1512            ScalarValue::C32(_) => "c32",
1513            ScalarValue::C64(_) => "c64",
1514        };
1515        f.debug_struct("AnyScalar")
1516            .field("dtype", &dtype)
1517            .field("value", &self.value())
1518            .field("tracks_grad", &self.tracks_grad())
1519            .finish()
1520    }
1521}
1522
1523#[cfg(test)]
1524mod tests {
1525    use super::*;
1526
1527    fn with_forced_tensor_initialization_failure<T>(f: impl FnOnce() -> T) -> T {
1528        let previous =
1529            FORCE_ANY_SCALAR_TENSOR_INITIALIZATION_FAILURE.with(|failure| failure.replace(true));
1530        let result = f();
1531        FORCE_ANY_SCALAR_TENSOR_INITIALIZATION_FAILURE.with(|failure| failure.set(previous));
1532        result
1533    }
1534
1535    #[test]
1536    fn compact_sum_preserves_f32_and_c32_dtype_with_and_without_ad() {
1537        let indices = || vec![crate::DynIndex::new_dyn(2), crate::DynIndex::new_dyn(2)];
1538        for tensor in [
1539            IdxTensor::from_diag(indices(), vec![1.0_f32, 2.0_f32])
1540                .unwrap()
1541                .sum()
1542                .unwrap(),
1543            IdxTensor::from_diag(indices(), vec![1.0_f32, 2.0_f32])
1544                .unwrap()
1545                .enable_grad()
1546                .unwrap()
1547                .sum()
1548                .unwrap(),
1549        ] {
1550            assert!(matches!(tensor.value(), ScalarValue::F32(3.0)));
1551        }
1552        for tensor in [
1553            IdxTensor::from_diag(
1554                indices(),
1555                vec![Complex32::new(1.0, 2.0), Complex32::new(3.0, 4.0)],
1556            )
1557            .unwrap()
1558            .sum()
1559            .unwrap(),
1560            IdxTensor::from_diag(
1561                indices(),
1562                vec![Complex32::new(1.0, 2.0), Complex32::new(3.0, 4.0)],
1563            )
1564            .unwrap()
1565            .enable_grad()
1566            .unwrap()
1567            .sum()
1568            .unwrap(),
1569        ] {
1570            assert!(
1571                matches!(tensor.value(), ScalarValue::C32(value) if value == Complex32::new(4.0, 6.0))
1572            );
1573        }
1574    }
1575
1576    #[test]
1577    fn non_grad_scalar_arithmetic_uses_plain_values() {
1578        let a = AnyScalar::new_real(3.0);
1579        let b = AnyScalar::new_real(4.0);
1580
1581        let value = ((a.clone() + b.clone()) * b.clone() - AnyScalar::new_real(8.0))
1582            / AnyScalar::new_real(2.0);
1583
1584        assert_eq!(value.as_f64(), Some(10.0));
1585        assert!(!value.tracks_grad());
1586        assert!(value.as_tensor().is_ok());
1587    }
1588
1589    #[test]
1590    fn tracked_scalar_arithmetic_preserves_autodiff() {
1591        let x = AnyScalar::new_real(2.0).enable_grad().unwrap();
1592        let y = &x * &x;
1593
1594        assert!(y.tracks_grad());
1595        y.backward().unwrap();
1596
1597        let grad = x.grad().unwrap().unwrap();
1598        assert_eq!(grad.as_f64(), Some(4.0));
1599    }
1600
1601    #[test]
1602    fn scalar_tensor_initialization_failure_is_retained_for_tensor_operations() {
1603        let scalar = with_forced_tensor_initialization_failure(|| AnyScalar::new_real(2.0));
1604        assert_eq!(scalar.real(), 2.0);
1605        assert!(!scalar.tracks_grad());
1606
1607        let error = scalar.as_tensor().unwrap_err();
1608        assert!(error.downcast_ref::<AnyScalarTensorError>().is_some());
1609        assert!(error
1610            .to_string()
1611            .contains("AnyScalar tensor initialization failed"));
1612        assert!(error
1613            .chain()
1614            .any(|cause| cause.to_string() == "forced AnyScalar eager initialization failure"));
1615
1616        let error = scalar.clone().enable_grad().unwrap_err();
1617        assert!(error
1618            .source
1619            .downcast_ref::<AnyScalarTensorError>()
1620            .is_some());
1621        assert!(error
1622            .source
1623            .chain()
1624            .any(|cause| cause.to_string() == "forced AnyScalar eager initialization failure"));
1625    }
1626
1627    #[test]
1628    fn tracked_scalar_operation_failure_retains_error_and_graph_state() {
1629        let scalar = AnyScalar::new_real(2.0).enable_grad().unwrap();
1630        let result = with_forced_tensor_initialization_failure(|| scalar.powf(2.0));
1631
1632        assert!(result.tracks_grad());
1633        assert_eq!(result.real(), 4.0);
1634
1635        let error = result.as_tensor().unwrap_err();
1636        assert!(error.downcast_ref::<AnyScalarTensorError>().is_some());
1637        assert!(error
1638            .to_string()
1639            .contains("AnyScalar tensor initialization failed"));
1640        assert!(error
1641            .chain()
1642            .any(|cause| cause.to_string() == "forced AnyScalar eager initialization failure"));
1643
1644        let error = result.clone().enable_grad().unwrap_err();
1645        assert!(error
1646            .source
1647            .downcast_ref::<AnyScalarTensorError>()
1648            .is_some());
1649        assert!(error
1650            .source
1651            .chain()
1652            .any(|cause| cause.to_string() == "forced AnyScalar eager initialization failure"));
1653    }
1654
1655    #[test]
1656    fn tracked_backend_failure_preserves_typed_diagnostic_through_fallback() {
1657        let lhs = AnyScalar::new_real(2.0).enable_grad().unwrap();
1658        let rhs = AnyScalar::new_real(3.0).enable_grad().unwrap();
1659        let operation = AnyScalar::from_eager_binary(&lhs, &rhs, "add", |_lhs, _rhs| {
1660            Err(tenferro_tensor::Error::backend_failure(
1661                "forced_add",
1662                "forced tracked backend failure",
1663            ))
1664        });
1665        let result = AnyScalar::fallback_result(operation, "add", || ScalarValue::F64(5.0), true);
1666
1667        assert!(result.tracks_grad());
1668        assert_eq!(result.real(), 5.0);
1669        let error = result.as_tensor().unwrap_err();
1670        assert!(error.downcast_ref::<AnyScalarTensorError>().is_some());
1671        let stored = error.downcast_ref::<AnyScalarTensorError>().unwrap();
1672        match stored {
1673            AnyScalarTensorError::Operation { source, .. } => {
1674                assert!(source
1675                    .downcast_ref::<tenferro_tensor::Error>()
1676                    .is_some_and(|error| error
1677                        .to_string()
1678                        .contains("forced tracked backend failure")));
1679            }
1680            AnyScalarTensorError::Initialization { .. } => {
1681                panic!("operation failure was converted to initialization failure")
1682            }
1683        }
1684        let error = result.enable_grad().unwrap_err();
1685        assert!(error.to_string().contains("forced tracked backend failure"));
1686    }
1687
1688    #[test]
1689    fn every_infallible_scalar_fallback_retains_a_tracked_error() {
1690        let failed = AnyScalar {
1691            tensor: Err(AnyScalarTensorError::Operation {
1692                op: "seed",
1693                source: Arc::new(std::io::Error::other("forced tracked scalar failure")),
1694            }),
1695            value: ScalarValue::F64(2.0),
1696            tracks_grad: true,
1697        };
1698        let one = AnyScalar::new_real(1.0);
1699
1700        let results = [
1701            &failed + &one,
1702            &failed * &one,
1703            &failed / &one,
1704            -&failed,
1705            failed.conj(),
1706            failed.real_part(),
1707            failed.imag_part(),
1708            failed.sqrt(),
1709            failed.powf(2.0),
1710            failed.powi(2),
1711        ];
1712        for result in results {
1713            assert!(result.tracks_grad());
1714            let error = result.as_tensor().unwrap_err();
1715            assert!(error.downcast_ref::<AnyScalarTensorError>().is_some());
1716            assert!(error
1717                .chain()
1718                .any(|cause| cause.to_string() == "forced tracked scalar failure"));
1719        }
1720    }
1721
1722    #[test]
1723    fn every_infallible_scalar_operation_retains_an_initialization_error() {
1724        let failed = with_forced_tensor_initialization_failure(|| AnyScalar::new_real(2.0));
1725        let one = AnyScalar::new_real(1.0);
1726
1727        let results = [
1728            &failed + &one,
1729            &failed * &one,
1730            &failed / &one,
1731            -&failed,
1732            failed.conj(),
1733            failed.real_part(),
1734            failed.imag_part(),
1735            failed.sqrt(),
1736            failed.powf(2.0),
1737            failed.powi(0),
1738        ];
1739        for result in results {
1740            let error = result.as_tensor().unwrap_err();
1741            assert!(error.downcast_ref::<AnyScalarTensorError>().is_some());
1742            assert!(error
1743                .chain()
1744                .any(|cause| cause.to_string() == "forced AnyScalar eager initialization failure"));
1745        }
1746    }
1747}