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#[derive(Debug, thiserror::Error)]
171#[error("AnyScalar eager-tensor operation failed: {source}")]
172pub struct AnyScalarError {
173 #[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#[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 #[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 pub fn from_real(x: f64) -> Self {
439 Self::from_value(x)
440 }
441
442 pub fn from_complex(re: f64, im: f64) -> Self {
465 Self::from_value(Complex64::new(re, im))
466 }
467
468 pub fn new_real(x: f64) -> Self {
490 Self::from_real(x)
491 }
492
493 pub fn new_complex(re: f64, im: f64) -> Self {
516 Self::from_complex(re, im)
517 }
518
519 pub fn primal(&self) -> std::result::Result<Self, AnyScalarError> {
542 self.detach()
543 }
544
545 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 pub fn tracks_grad(&self) -> bool {
587 self.tracks_grad || self.tensor.as_ref().is_ok_and(IdxTensor::tracks_grad)
588 }
589
590 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 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 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 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 pub fn real(&self) -> f64 {
728 self.value().real()
729 }
730
731 pub fn imag(&self) -> f64 {
746 self.value().imag()
747 }
748
749 pub fn abs(&self) -> f64 {
765 self.value().abs()
766 }
767
768 pub fn is_complex(&self) -> bool {
783 self.value().is_complex()
784 }
785
786 pub fn is_real(&self) -> bool {
801 !self.is_complex()
802 }
803
804 pub fn is_zero(&self) -> bool {
819 self.value().is_zero()
820 }
821
822 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 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 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 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 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 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 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 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 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 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 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}