1use std::cmp::Ordering;
2use std::fmt;
3use std::ops::{Add, Div, Mul, Neg, Sub};
4
5use anyhow::{anyhow, Result};
6use num_complex::{Complex32, Complex64};
7use num_traits::{One, Zero};
8use tensor4all_tensorbackend::AnyScalar as BackendScalar;
9
10use crate::defaults::tensordynlen::TensorDynLen;
11use crate::TensorElement;
12use tensor4all_tensorbackend::{Storage, SumFromStorage};
13
14#[derive(Clone, Copy, Debug, PartialEq)]
15enum ScalarValue {
16 F32(f32),
17 F64(f64),
18 C32(Complex32),
19 C64(Complex64),
20}
21
22impl ScalarValue {
23 fn real(self) -> f64 {
24 match self {
25 Self::F32(value) => value as f64,
26 Self::F64(value) => value,
27 Self::C32(value) => value.re as f64,
28 Self::C64(value) => value.re,
29 }
30 }
31
32 fn imag(self) -> f64 {
33 match self {
34 Self::F32(_) | Self::F64(_) => 0.0,
35 Self::C32(value) => value.im as f64,
36 Self::C64(value) => value.im,
37 }
38 }
39
40 fn abs(self) -> f64 {
41 match self {
42 Self::F32(value) => value.abs() as f64,
43 Self::F64(value) => value.abs(),
44 Self::C32(value) => value.norm() as f64,
45 Self::C64(value) => value.norm(),
46 }
47 }
48
49 fn is_complex(self) -> bool {
50 matches!(self, Self::C32(_) | Self::C64(_))
51 }
52
53 fn is_zero(self) -> bool {
54 match self {
55 Self::F32(value) => value == 0.0,
56 Self::F64(value) => value == 0.0,
57 Self::C32(value) => value == Complex32::new(0.0, 0.0),
58 Self::C64(value) => value == Complex64::new(0.0, 0.0),
59 }
60 }
61
62 fn into_complex(self) -> Complex64 {
63 match self {
64 Self::F32(value) => Complex64::new(value as f64, 0.0),
65 Self::F64(value) => Complex64::new(value, 0.0),
66 Self::C32(value) => Complex64::new(value.re as f64, value.im as f64),
67 Self::C64(value) => value,
68 }
69 }
70}
71
72trait ScalarTensorElement: TensorElement {
73 fn scalar_value(value: Self) -> ScalarValue;
74}
75
76impl ScalarTensorElement for f32 {
77 fn scalar_value(value: Self) -> ScalarValue {
78 ScalarValue::F32(value)
79 }
80}
81
82impl ScalarTensorElement for f64 {
83 fn scalar_value(value: Self) -> ScalarValue {
84 ScalarValue::F64(value)
85 }
86}
87
88impl ScalarTensorElement for Complex32 {
89 fn scalar_value(value: Self) -> ScalarValue {
90 ScalarValue::C32(value)
91 }
92}
93
94impl ScalarTensorElement for Complex64 {
95 fn scalar_value(value: Self) -> ScalarValue {
96 ScalarValue::C64(value)
97 }
98}
99
100#[derive(Clone)]
106pub struct AnyScalar {
107 tensor: Option<TensorDynLen>,
108 value: ScalarValue,
109}
110
111impl AnyScalar {
112 fn wrap_tensor(tensor: TensorDynLen) -> Result<Self> {
113 let dims = tensor.dims();
114 anyhow::ensure!(
115 dims.is_empty(),
116 "AnyScalar requires a rank-0 tensor, got dims {:?}",
117 dims
118 );
119 let value = Self::scalar_value_from_tensor(&tensor)?;
120 Ok(Self {
121 tensor: Some(tensor),
122 value,
123 })
124 }
125
126 fn from_tensor_result(tensor: Result<TensorDynLen>, op: &'static str) -> Result<Self> {
127 Self::wrap_tensor(
128 tensor.map_err(|e| anyhow!("AnyScalar::{op} returned invalid scalar tensor: {e}"))?,
129 )
130 .map_err(|e| anyhow!("AnyScalar::{op} returned non-scalar tensor: {e}"))
131 }
132
133 fn from_eager_binary<E>(
134 lhs: &Self,
135 rhs: &Self,
136 op: &'static str,
137 f: impl FnOnce(
138 &tenferro::EagerTensor<tenferro::CpuBackend>,
139 &tenferro::EagerTensor<tenferro::CpuBackend>,
140 ) -> std::result::Result<tenferro::EagerTensor<tenferro::CpuBackend>, E>,
141 ) -> Result<Self>
142 where
143 E: fmt::Display,
144 {
145 let result = f(lhs.as_tensor()?.as_inner()?, rhs.as_tensor()?.as_inner()?)
146 .map_err(|e| anyhow!("AnyScalar::{op} failed: {e}"))?;
147 Self::from_tensor_result(TensorDynLen::from_inner(vec![], result), op)
148 }
149
150 fn from_eager_unary<E>(
151 input: &Self,
152 op: &'static str,
153 f: impl FnOnce(
154 &tenferro::EagerTensor<tenferro::CpuBackend>,
155 ) -> std::result::Result<tenferro::EagerTensor<tenferro::CpuBackend>, E>,
156 ) -> Result<Self>
157 where
158 E: fmt::Display,
159 {
160 let result = f(input.as_tensor()?.as_inner()?)
161 .map_err(|e| anyhow!("AnyScalar::{op} failed: {e}"))?;
162 Self::from_tensor_result(TensorDynLen::from_inner(vec![], result), op)
163 }
164
165 fn scalar_value_from_tensor(tensor: &TensorDynLen) -> Result<ScalarValue> {
166 let storage = tensor.storage();
167 if storage.is_c64() {
168 let values = storage
169 .payload_c64_col_major_vec()
170 .map_err(|e| anyhow!("failed to read c64 scalar storage: {e}"))?;
171 values
172 .first()
173 .copied()
174 .map(ScalarValue::C64)
175 .ok_or_else(|| anyhow!("rank-0 c64 scalar storage is empty"))
176 } else {
177 let values = storage
178 .payload_f64_col_major_vec()
179 .map_err(|e| anyhow!("failed to read f64 scalar storage: {e}"))?;
180 values
181 .first()
182 .copied()
183 .map(ScalarValue::F64)
184 .ok_or_else(|| anyhow!("rank-0 f64 scalar storage is empty"))
185 }
186 }
187
188 fn value(&self) -> ScalarValue {
189 self.value
190 }
191
192 fn from_backend_scalar(value: BackendScalar) -> Self {
193 if let Some(value) = value.as_c64() {
194 Self::from_value(value)
195 } else {
196 Self::from_value(value.real())
197 }
198 }
199
200 pub(crate) fn from_tensor(tensor: TensorDynLen) -> Result<Self> {
201 Self::wrap_tensor(tensor)
202 }
203
204 pub(crate) fn as_tensor(&self) -> Result<&TensorDynLen> {
205 self.tensor
206 .as_ref()
207 .ok_or_else(|| anyhow!("AnyScalar has no backend tensor representation"))
208 }
209
210 #[allow(private_bounds)]
233 pub fn from_value<T: ScalarTensorElement>(value: T) -> Self {
234 Self {
235 tensor: TensorDynLen::scalar(value).ok(),
236 value: T::scalar_value(value),
237 }
238 }
239
240 pub fn from_real(x: f64) -> Self {
262 Self::from_value(x)
263 }
264
265 pub fn from_complex(re: f64, im: f64) -> Self {
288 Self::from_value(Complex64::new(re, im))
289 }
290
291 pub fn new_real(x: f64) -> Self {
313 Self::from_real(x)
314 }
315
316 pub fn new_complex(re: f64, im: f64) -> Self {
339 Self::from_complex(re, im)
340 }
341
342 pub fn primal(&self) -> Result<Self> {
360 self.detach()
361 }
362
363 pub fn enable_grad(self) -> Result<Self> {
378 let tensor = self
379 .tensor
380 .ok_or_else(|| anyhow!("AnyScalar has no backend tensor representation"))?;
381 Self::from_tensor(tensor.enable_grad()?)
382 }
383
384 pub fn tracks_grad(&self) -> bool {
400 self.tensor.as_ref().is_some_and(TensorDynLen::tracks_grad)
401 }
402
403 pub fn grad(&self) -> Result<Option<Self>> {
428 self.as_tensor()?
429 .grad()
430 .and_then(|maybe_grad| maybe_grad.map(Self::from_tensor).transpose())
431 }
432
433 pub fn clear_grad(&self) -> Result<()> {
457 self.as_tensor()?.clear_grad()
458 }
459
460 pub fn backward(&self) -> Result<()> {
483 self.as_tensor()?.backward()
484 }
485
486 pub fn detach(&self) -> Result<Self> {
506 Self::from_tensor(self.as_tensor()?.detach()?)
507 }
508
509 pub fn real(&self) -> f64 {
525 self.value().real()
526 }
527
528 pub fn imag(&self) -> f64 {
543 self.value().imag()
544 }
545
546 pub fn abs(&self) -> f64 {
562 self.value().abs()
563 }
564
565 pub fn is_complex(&self) -> bool {
580 self.value().is_complex()
581 }
582
583 pub fn is_real(&self) -> bool {
598 !self.is_complex()
599 }
600
601 pub fn is_zero(&self) -> bool {
616 self.value().is_zero()
617 }
618
619 pub fn as_f64(&self) -> Option<f64> {
635 match self.value() {
636 ScalarValue::F32(value) => Some(value as f64),
637 ScalarValue::F64(value) => Some(value),
638 ScalarValue::C32(_) | ScalarValue::C64(_) => None,
639 }
640 }
641
642 pub fn as_c64(&self) -> Option<Complex64> {
659 match self.value() {
660 ScalarValue::F32(_) | ScalarValue::F64(_) => None,
661 ScalarValue::C32(value) => Some(Complex64::new(value.re as f64, value.im as f64)),
662 ScalarValue::C64(value) => Some(value),
663 }
664 }
665
666 pub fn try_conj(&self) -> Result<Self> {
681 if !self.tracks_grad() {
682 return Ok(Self::from_backend_scalar(self.to_backend_scalar().conj()));
683 }
684 Self::from_eager_unary(self, "conj", |tensor| tensor.conj())
685 }
686
687 pub fn conj(&self) -> Self {
689 self.try_conj()
690 .unwrap_or_else(|_| Self::from_backend_scalar(self.to_backend_scalar().conj()))
691 }
692
693 pub fn real_part(&self) -> Self {
709 Self::from_real(self.real())
710 }
711
712 pub fn imag_part(&self) -> Self {
728 Self::from_real(self.imag())
729 }
730
731 pub fn compose_complex(real: Self, imag: Self) -> Result<Self> {
760 if !real.is_real() || !imag.is_real() {
761 return Err(anyhow!("compose_complex requires real-valued inputs"));
762 }
763 let imag_term = imag.try_mul(&Self::new_complex(0.0, 1.0))?;
764 real.try_add(&imag_term)
765 }
766
767 pub fn sqrt(&self) -> Self {
784 if !self.tracks_grad() || self.is_complex() || self.real() < 0.0 {
785 Self::from_backend_scalar(self.to_backend_scalar().sqrt())
786 } else {
787 Self::from_eager_unary(self, "sqrt", |tensor| tensor.sqrt())
788 .unwrap_or_else(|_| Self::from_backend_scalar(self.to_backend_scalar().sqrt()))
789 }
790 }
791
792 pub fn powf(&self, exponent: f64) -> Self {
811 Self::from_backend_scalar(self.to_backend_scalar().powf(exponent))
812 }
813
814 pub fn powi(&self, exponent: i32) -> Self {
834 if exponent == 0 {
835 return Self::one();
836 }
837
838 let mut base = self.clone();
839 let mut power = exponent.unsigned_abs();
840 let mut acc = Self::one();
841
842 while power > 0 {
843 if power % 2 == 1 {
844 acc = acc.try_mul(&base).unwrap_or_else(|_| {
845 Self::from_backend_scalar(acc.to_backend_scalar() * base.to_backend_scalar())
846 });
847 }
848 power /= 2;
849 if power > 0 {
850 base = base.try_mul(&base).unwrap_or_else(|_| {
851 Self::from_backend_scalar(base.to_backend_scalar() * base.to_backend_scalar())
852 });
853 }
854 }
855
856 if exponent < 0 {
857 Self::one().try_div(&acc).unwrap_or_else(|_| {
858 Self::from_backend_scalar(Self::one().to_backend_scalar() / acc.to_backend_scalar())
859 })
860 } else {
861 acc
862 }
863 }
864
865 pub(crate) fn to_backend_scalar(&self) -> BackendScalar {
866 match self.value() {
867 ScalarValue::F32(value) => BackendScalar::from_value(value),
868 ScalarValue::F64(value) => BackendScalar::from_value(value),
869 ScalarValue::C32(value) => BackendScalar::from_value(value),
870 ScalarValue::C64(value) => BackendScalar::from_value(value),
871 }
872 }
873
874 pub(crate) fn try_add(&self, rhs: &Self) -> Result<Self> {
875 if !self.tracks_grad() && !rhs.tracks_grad() {
876 return Ok(Self::from_backend_scalar(
877 self.to_backend_scalar() + rhs.to_backend_scalar(),
878 ));
879 }
880 Self::from_eager_binary(self, rhs, "add", |lhs, rhs| lhs.add(rhs))
881 }
882
883 pub(crate) fn try_mul(&self, rhs: &Self) -> Result<Self> {
884 if !self.tracks_grad() && !rhs.tracks_grad() {
885 return Ok(Self::from_backend_scalar(
886 self.to_backend_scalar() * rhs.to_backend_scalar(),
887 ));
888 }
889 Self::from_eager_binary(self, rhs, "mul", |lhs, rhs| lhs.mul(rhs))
890 }
891
892 pub(crate) fn try_div(&self, rhs: &Self) -> Result<Self> {
893 if !self.tracks_grad() && !rhs.tracks_grad() {
894 return Ok(Self::from_backend_scalar(
895 self.to_backend_scalar() / rhs.to_backend_scalar(),
896 ));
897 }
898 if self.as_tensor()?.as_native()?.dtype() == rhs.as_tensor()?.as_native()?.dtype() {
899 Self::from_eager_binary(self, rhs, "div", |lhs, rhs| lhs.div(rhs))
900 } else {
901 Ok(Self::from_backend_scalar(
902 self.to_backend_scalar() / rhs.to_backend_scalar(),
903 ))
904 }
905 }
906
907 pub(crate) fn try_neg(&self) -> Result<Self> {
908 if !self.tracks_grad() {
909 return Ok(Self::from_backend_scalar(-self.to_backend_scalar()));
910 }
911 Self::from_eager_unary(self, "neg", |tensor| tensor.neg())
912 }
913}
914
915impl SumFromStorage for AnyScalar {
916 fn sum_from_storage(storage: &Storage) -> Self {
917 Self::from_backend_scalar(BackendScalar::sum_from_storage(storage))
918 }
919}
920
921impl From<f32> for AnyScalar {
922 fn from(value: f32) -> Self {
923 Self::from_value(value)
924 }
925}
926
927impl From<f64> for AnyScalar {
928 fn from(value: f64) -> Self {
929 Self::from_value(value)
930 }
931}
932
933impl From<Complex32> for AnyScalar {
934 fn from(value: Complex32) -> Self {
935 Self::from_value(value)
936 }
937}
938
939impl From<Complex64> for AnyScalar {
940 fn from(value: Complex64) -> Self {
941 Self::from_value(value)
942 }
943}
944
945impl TryFrom<AnyScalar> for f64 {
946 type Error = &'static str;
947
948 fn try_from(value: AnyScalar) -> std::result::Result<Self, Self::Error> {
949 value.as_f64().ok_or("cannot convert complex scalar to f64")
950 }
951}
952
953impl From<AnyScalar> for Complex64 {
954 fn from(value: AnyScalar) -> Self {
955 value.value().into_complex()
956 }
957}
958
959impl Add<&AnyScalar> for &AnyScalar {
960 type Output = AnyScalar;
961
962 fn add(self, rhs: &AnyScalar) -> Self::Output {
963 self.try_add(rhs).unwrap_or_else(|_| {
964 AnyScalar::from_backend_scalar(self.to_backend_scalar() + rhs.to_backend_scalar())
965 })
966 }
967}
968
969impl Add<AnyScalar> for AnyScalar {
970 type Output = AnyScalar;
971
972 fn add(self, rhs: AnyScalar) -> Self::Output {
973 Add::add(&self, &rhs)
974 }
975}
976
977impl Add<AnyScalar> for &AnyScalar {
978 type Output = AnyScalar;
979
980 fn add(self, rhs: AnyScalar) -> Self::Output {
981 Add::add(self, &rhs)
982 }
983}
984
985impl Add<&AnyScalar> for AnyScalar {
986 type Output = AnyScalar;
987
988 fn add(self, rhs: &AnyScalar) -> Self::Output {
989 Add::add(&self, rhs)
990 }
991}
992
993impl Sub<&AnyScalar> for &AnyScalar {
994 type Output = AnyScalar;
995
996 fn sub(self, rhs: &AnyScalar) -> Self::Output {
997 Add::add(self, &Neg::neg(rhs))
998 }
999}
1000
1001impl Sub<AnyScalar> for AnyScalar {
1002 type Output = AnyScalar;
1003
1004 fn sub(self, rhs: AnyScalar) -> Self::Output {
1005 Sub::sub(&self, &rhs)
1006 }
1007}
1008
1009impl Sub<AnyScalar> for &AnyScalar {
1010 type Output = AnyScalar;
1011
1012 fn sub(self, rhs: AnyScalar) -> Self::Output {
1013 Sub::sub(self, &rhs)
1014 }
1015}
1016
1017impl Sub<&AnyScalar> for AnyScalar {
1018 type Output = AnyScalar;
1019
1020 fn sub(self, rhs: &AnyScalar) -> Self::Output {
1021 Sub::sub(&self, rhs)
1022 }
1023}
1024
1025impl Mul<&AnyScalar> for &AnyScalar {
1026 type Output = AnyScalar;
1027
1028 fn mul(self, rhs: &AnyScalar) -> Self::Output {
1029 self.try_mul(rhs).unwrap_or_else(|_| {
1030 AnyScalar::from_backend_scalar(self.to_backend_scalar() * rhs.to_backend_scalar())
1031 })
1032 }
1033}
1034
1035impl Mul<AnyScalar> for AnyScalar {
1036 type Output = AnyScalar;
1037
1038 fn mul(self, rhs: AnyScalar) -> Self::Output {
1039 Mul::mul(&self, &rhs)
1040 }
1041}
1042
1043impl Mul<AnyScalar> for &AnyScalar {
1044 type Output = AnyScalar;
1045
1046 fn mul(self, rhs: AnyScalar) -> Self::Output {
1047 Mul::mul(self, &rhs)
1048 }
1049}
1050
1051impl Mul<&AnyScalar> for AnyScalar {
1052 type Output = AnyScalar;
1053
1054 fn mul(self, rhs: &AnyScalar) -> Self::Output {
1055 Mul::mul(&self, rhs)
1056 }
1057}
1058
1059impl Div<&AnyScalar> for &AnyScalar {
1060 type Output = AnyScalar;
1061
1062 fn div(self, rhs: &AnyScalar) -> Self::Output {
1063 self.try_div(rhs).unwrap_or_else(|_| {
1064 AnyScalar::from_backend_scalar(self.to_backend_scalar() / rhs.to_backend_scalar())
1065 })
1066 }
1067}
1068
1069impl Div<AnyScalar> for AnyScalar {
1070 type Output = AnyScalar;
1071
1072 fn div(self, rhs: AnyScalar) -> Self::Output {
1073 Div::div(&self, &rhs)
1074 }
1075}
1076
1077impl Div<AnyScalar> for &AnyScalar {
1078 type Output = AnyScalar;
1079
1080 fn div(self, rhs: AnyScalar) -> Self::Output {
1081 Div::div(self, &rhs)
1082 }
1083}
1084
1085impl Div<&AnyScalar> for AnyScalar {
1086 type Output = AnyScalar;
1087
1088 fn div(self, rhs: &AnyScalar) -> Self::Output {
1089 Div::div(&self, rhs)
1090 }
1091}
1092
1093impl Neg for &AnyScalar {
1094 type Output = AnyScalar;
1095
1096 fn neg(self) -> Self::Output {
1097 self.try_neg()
1098 .unwrap_or_else(|_| AnyScalar::from_backend_scalar(-self.to_backend_scalar()))
1099 }
1100}
1101
1102impl Neg for AnyScalar {
1103 type Output = AnyScalar;
1104
1105 fn neg(self) -> Self::Output {
1106 Neg::neg(&self)
1107 }
1108}
1109
1110impl Mul<AnyScalar> for f64 {
1111 type Output = AnyScalar;
1112
1113 fn mul(self, rhs: AnyScalar) -> Self::Output {
1114 AnyScalar::from_real(self) * rhs
1115 }
1116}
1117
1118impl Mul<AnyScalar> for Complex64 {
1119 type Output = AnyScalar;
1120
1121 fn mul(self, rhs: AnyScalar) -> Self::Output {
1122 AnyScalar::from(self) * rhs
1123 }
1124}
1125
1126impl Div<AnyScalar> for Complex64 {
1127 type Output = AnyScalar;
1128
1129 fn div(self, rhs: AnyScalar) -> Self::Output {
1130 AnyScalar::from(self) / rhs
1131 }
1132}
1133
1134impl Default for AnyScalar {
1135 fn default() -> Self {
1136 Self::zero()
1137 }
1138}
1139
1140impl Zero for AnyScalar {
1141 fn zero() -> Self {
1142 Self::from_real(0.0)
1143 }
1144
1145 fn is_zero(&self) -> bool {
1146 AnyScalar::is_zero(self)
1147 }
1148}
1149
1150impl One for AnyScalar {
1151 fn one() -> Self {
1152 Self::from_real(1.0)
1153 }
1154}
1155
1156impl PartialEq for AnyScalar {
1157 fn eq(&self, other: &Self) -> bool {
1158 self.value() == other.value()
1159 }
1160}
1161
1162impl PartialOrd for AnyScalar {
1163 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1164 match (self.value(), other.value()) {
1165 (ScalarValue::F32(lhs), ScalarValue::F32(rhs)) => lhs.partial_cmp(&rhs),
1166 (ScalarValue::F32(lhs), ScalarValue::F64(rhs)) => (lhs as f64).partial_cmp(&rhs),
1167 (ScalarValue::F64(lhs), ScalarValue::F32(rhs)) => lhs.partial_cmp(&(rhs as f64)),
1168 (ScalarValue::F64(lhs), ScalarValue::F64(rhs)) => lhs.partial_cmp(&rhs),
1169 _ => None,
1170 }
1171 }
1172}
1173
1174impl fmt::Display for AnyScalar {
1175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1176 match self.value() {
1177 ScalarValue::F32(value) => value.fmt(f),
1178 ScalarValue::F64(value) => value.fmt(f),
1179 ScalarValue::C32(value) => value.fmt(f),
1180 ScalarValue::C64(value) => value.fmt(f),
1181 }
1182 }
1183}
1184
1185impl fmt::Debug for AnyScalar {
1186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1187 let dtype = match self.value {
1188 ScalarValue::F32(_) => "f32",
1189 ScalarValue::F64(_) => "f64",
1190 ScalarValue::C32(_) => "c32",
1191 ScalarValue::C64(_) => "c64",
1192 };
1193 f.debug_struct("AnyScalar")
1194 .field("dtype", &dtype)
1195 .field("value", &self.value())
1196 .field("tracks_grad", &self.tracks_grad())
1197 .finish()
1198 }
1199}
1200
1201#[cfg(test)]
1202mod tests {
1203 use super::*;
1204
1205 #[test]
1206 fn non_grad_scalar_arithmetic_uses_plain_values() {
1207 let a = AnyScalar::new_real(3.0);
1208 let b = AnyScalar::new_real(4.0);
1209
1210 let value = ((a.clone() + b.clone()) * b.clone() - AnyScalar::new_real(8.0))
1211 / AnyScalar::new_real(2.0);
1212
1213 assert_eq!(value.as_f64(), Some(10.0));
1214 assert!(!value.tracks_grad());
1215 assert!(value.as_tensor().is_ok());
1216 }
1217
1218 #[test]
1219 fn tracked_scalar_arithmetic_preserves_autodiff() {
1220 let x = AnyScalar::new_real(2.0).enable_grad().unwrap();
1221 let y = &x * &x;
1222
1223 assert!(y.tracks_grad());
1224 y.backward().unwrap();
1225
1226 let grad = x.grad().unwrap().unwrap();
1227 assert_eq!(grad.as_f64(), Some(4.0));
1228 }
1229}