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
110fn 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 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 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
343pub 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 #[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 pub fn from_real(x: f64) -> Self {
457 Self::from_value(x)
458 }
459
460 pub fn from_complex(re: f64, im: f64) -> Self {
473 Self::from_value(Complex64::new(re, im))
474 }
475
476 pub fn new_real(x: f64) -> Self {
487 Self::from_real(x)
488 }
489
490 pub fn new_complex(re: f64, im: f64) -> Self {
501 Self::from_complex(re, im)
502 }
503
504 pub fn primal(&self) -> Self {
516 self.clone()
517 }
518
519 pub fn real(&self) -> f64 {
530 self.value().real()
531 }
532
533 pub fn imag(&self) -> f64 {
549 self.value().imag()
550 }
551
552 pub fn abs(&self) -> f64 {
568 self.value().abs()
569 }
570
571 pub fn is_complex(&self) -> bool {
582 self.value().is_complex()
583 }
584
585 pub fn is_real(&self) -> bool {
596 !self.is_complex()
597 }
598
599 pub fn is_zero(&self) -> bool {
610 self.value().is_zero()
611 }
612
613 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 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 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 pub fn real_part(&self) -> Self {
711 Self::from_real(self.real())
712 }
713
714 pub fn imag_part(&self) -> Self {
730 Self::from_real(self.imag())
731 }
732
733 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 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 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 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;