optionstratlib_math/geometrics/
utils.rs1use crate::geometrics::ConstructionMethod;
7use optionstratlib_core::error::DecimalError;
8use rust_decimal::{Decimal, MathematicalOps};
9use std::collections::BTreeSet;
10
11pub(crate) fn powu_checked(
23 base: Decimal,
24 exp: u64,
25 op: &'static str,
26) -> Result<Decimal, DecimalError> {
27 base.checked_powu(exp)
28 .ok_or_else(|| DecimalError::overflow(op, base, Decimal::from(exp)))
29}
30
31pub trait GeometricObject<Point: Clone, Input> {
34 type Error;
36
37 fn get_points(&self) -> BTreeSet<&Point>;
40
41 fn vector(&self) -> Vec<&Point> {
44 self.get_points().into_iter().collect()
45 }
46
47 fn from_vector<T>(points: Vec<T>) -> Self
53 where
54 Self: Sized,
55 T: Into<Point> + Clone;
56
57 fn construct<T>(method: T) -> Result<Self, Self::Error>
72 where
73 Self: Sized,
74 T: Into<ConstructionMethod<Point, Input, Self::Error>>;
75
76 fn to_vector(&self) -> Vec<&Point> {
79 self.vector()
80 }
81
82 fn calculate_range<I>(iter: I) -> (Decimal, Decimal)
86 where
87 I: Iterator<Item = Decimal>,
88 {
89 iter.fold((Decimal::MAX, Decimal::MIN), |(min, max), val| {
90 (min.min(val), max.max(val))
91 })
92 }
93}
94
95#[cfg(test)]
96mod tests_geometric_object {
97 use super::*;
98 use optionstratlib_core::utils::Len;
99 use rust_decimal_macros::dec;
100
101 #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
103 struct TestPoint {
104 x: Decimal,
105 y: Decimal,
106 }
107
108 impl From<(Decimal, Decimal)> for TestPoint {
109 fn from(tuple: (Decimal, Decimal)) -> Self {
110 TestPoint {
111 x: tuple.0,
112 y: tuple.1,
113 }
114 }
115 }
116
117 #[derive(Debug, Clone)]
119 struct TestGeometricObject {
120 points: BTreeSet<TestPoint>,
121 }
122
123 impl GeometricObject<TestPoint, Decimal> for TestGeometricObject {
124 type Error = String;
125
126 fn get_points(&self) -> BTreeSet<&TestPoint> {
127 self.points.iter().collect()
128 }
129
130 fn from_vector<T>(points: Vec<T>) -> Self
131 where
132 T: Into<TestPoint> + Clone,
133 {
134 let points_set: BTreeSet<TestPoint> = points.into_iter().map(|p| p.into()).collect();
135 TestGeometricObject { points: points_set }
136 }
137
138 fn construct<T>(method: T) -> Result<Self, Self::Error>
139 where
140 Self: Sized,
141 T: Into<ConstructionMethod<TestPoint, Decimal, Self::Error>>,
142 {
143 let method = method.into();
144 match method {
145 ConstructionMethod::FromData { points } => {
146 if points.is_empty() {
147 Err("Cannot create object from empty points".to_string())
148 } else {
149 Ok(TestGeometricObject { points })
150 }
151 }
152 _ => Err("Unsupported construction method".to_string()),
153 }
154 }
155 }
156
157 impl Len for TestGeometricObject {
158 fn len(&self) -> usize {
159 self.points.len()
160 }
161 }
162
163 #[test]
164 fn test_get_points() {
165 let points = BTreeSet::from([
166 TestPoint {
167 x: dec!(1.0),
168 y: dec!(2.0),
169 },
170 TestPoint {
171 x: dec!(3.0),
172 y: dec!(4.0),
173 },
174 ]);
175
176 let obj = TestGeometricObject {
177 points: points.clone(),
178 };
179 let retrieved_points = obj.get_points();
180
181 assert_eq!(retrieved_points.len(), points.len());
182 for point in &points {
183 assert!(retrieved_points.contains(point));
184 }
185 }
186
187 #[test]
188 fn test_from_vector() {
189 let points = vec![(dec!(1.0), dec!(2.0)), (dec!(3.0), dec!(4.0))];
190 let obj = TestGeometricObject::from_vector(points);
191
192 assert_eq!(obj.points.len(), 2);
193 assert!(obj.points.contains(&TestPoint {
194 x: dec!(1.0),
195 y: dec!(2.0)
196 }));
197 assert!(obj.points.contains(&TestPoint {
198 x: dec!(3.0),
199 y: dec!(4.0)
200 }));
201 }
202
203 #[test]
204 fn test_construct_from_data() {
205 let points = BTreeSet::from([
206 TestPoint {
207 x: dec!(1.0),
208 y: dec!(2.0),
209 },
210 TestPoint {
211 x: dec!(3.0),
212 y: dec!(4.0),
213 },
214 ]);
215
216 let result = TestGeometricObject::construct(ConstructionMethod::FromData {
217 points: points.clone(),
218 });
219
220 assert!(result.is_ok());
221 let obj = result.unwrap();
222 assert_eq!(obj.points, points);
223 }
224
225 #[test]
226 fn test_construct_empty_points_fails() {
227 let points: BTreeSet<TestPoint> = BTreeSet::new();
228 let result = TestGeometricObject::construct(ConstructionMethod::FromData { points });
229
230 assert!(result.is_err());
231 }
232
233 #[test]
234 fn test_to_vector() {
235 let points = BTreeSet::from([
236 TestPoint {
237 x: dec!(1.0),
238 y: dec!(2.0),
239 },
240 TestPoint {
241 x: dec!(3.0),
242 y: dec!(4.0),
243 },
244 ]);
245
246 let obj = TestGeometricObject {
247 points: points.clone(),
248 };
249 let vector = obj.to_vector();
250
251 assert_eq!(vector.len(), points.len());
252 for point in &points {
253 assert!(vector.contains(&point));
254 }
255 }
256
257 #[test]
258 fn test_calculate_range() {
259 let values = vec![dec!(1.0), dec!(5.0), dec!(3.0), dec!(-2.0), dec!(10.0)];
260
261 let (min, max) = TestGeometricObject::calculate_range(values.into_iter());
262
263 assert_eq!(min, dec!(-2.0));
264 assert_eq!(max, dec!(10.0));
265 }
266
267 #[test]
268 fn test_len_trait() {
269 let points = BTreeSet::from([
270 TestPoint {
271 x: dec!(1.0),
272 y: dec!(2.0),
273 },
274 TestPoint {
275 x: dec!(3.0),
276 y: dec!(4.0),
277 },
278 ]);
279
280 let obj = TestGeometricObject { points };
281 assert_eq!(obj.len(), 2);
282 assert!(!obj.is_empty());
283 }
284
285 #[test]
286 fn test_len_empty() {
287 let points: BTreeSet<TestPoint> = BTreeSet::new();
288
289 let obj = TestGeometricObject { points };
290 assert_eq!(obj.len(), 0);
291 assert!(obj.is_empty());
292 }
293}