1use crate::curves::Point2D;
7use crate::error::SurfaceError;
8use crate::geometrics::HasX;
9use num_traits::FromPrimitive;
10use optionstratlib_core::model::decimal::decimal_to_f64;
11use optionstratlib_core::model::is_positive;
12use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14use std::cmp::Ordering;
15use std::fmt::Display;
16use std::hash::{Hash, Hasher};
17
18#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
48#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
49#[serde(deny_unknown_fields)]
50pub struct Point3D {
51 pub x: Decimal,
53 pub y: Decimal,
55 pub z: Decimal,
57}
58
59impl Display for Point3D {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "(x: {}, y: {}, z: {})", self.x, self.y, self.z)
62 }
63}
64
65impl PartialEq for Point3D {
78 fn eq(&self, other: &Self) -> bool {
79 self.x == other.x && self.y == other.y && self.z == other.z
80 }
81}
82
83impl Eq for Point3D {}
84
85impl Hash for Point3D {
86 fn hash<H: Hasher>(&self, state: &mut H) {
87 self.x.hash(state);
90 self.y.hash(state);
91 self.z.hash(state);
92 }
93}
94
95impl PartialOrd for Point3D {
96 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
97 Some(self.cmp(other))
98 }
99}
100
101impl Ord for Point3D {
105 fn cmp(&self, other: &Self) -> Ordering {
106 match self.x.cmp(&other.x) {
107 Ordering::Equal => match self.y.cmp(&other.y) {
108 Ordering::Equal => self.z.cmp(&other.z),
109 y_ordering => y_ordering,
110 },
111 x_ordering => x_ordering,
112 }
113 }
114}
115
116impl Point3D {
117 pub fn new<T: Into<Decimal>, U: Into<Decimal>, V: Into<Decimal>>(x: T, y: U, z: V) -> Self {
124 Self {
125 x: x.into(),
126 y: y.into(),
127 z: z.into(),
128 }
129 }
130
131 pub fn to_tuple<
150 T: TryFrom<Decimal> + 'static,
151 U: TryFrom<Decimal> + 'static,
152 V: TryFrom<Decimal> + 'static,
153 >(
154 &self,
155 ) -> Result<(T, U, V), SurfaceError> {
156 if is_positive::<T>() && self.x <= Decimal::ZERO {
157 return Err(SurfaceError::Point3DError {
158 reason: "x must be positive for type T",
159 });
160 }
161
162 if is_positive::<U>() && self.y <= Decimal::ZERO {
163 return Err(SurfaceError::Point3DError {
164 reason: "y must be positive for type U",
165 });
166 }
167
168 if is_positive::<V>() && self.z <= Decimal::ZERO {
169 return Err(SurfaceError::Point3DError {
170 reason: "z must be positive for type V",
171 });
172 }
173
174 let x = T::try_from(self.x).map_err(|_| SurfaceError::Point3DError {
175 reason: "failed to convert x",
176 })?;
177 let y = U::try_from(self.y).map_err(|_| SurfaceError::Point3DError {
178 reason: "failed to convert y",
179 })?;
180 let z = V::try_from(self.z).map_err(|_| SurfaceError::Point3DError {
181 reason: "failed to convert z",
182 })?;
183
184 Ok((x, y, z))
185 }
186
187 pub fn from_tuple<T: Into<Decimal>, U: Into<Decimal>, V: Into<Decimal>>(
197 x: T,
198 y: U,
199 z: V,
200 ) -> Result<Self, SurfaceError> {
201 Ok(Self::new(x, y, z))
202 }
203
204 pub fn to_f64_tuple(&self) -> Result<(f64, f64, f64), SurfaceError> {
212 let x = decimal_to_f64(self.x).ok();
213 let y = decimal_to_f64(self.y).ok();
214 let z = decimal_to_f64(self.z).ok();
215
216 match (x, y, z) {
217 (Some(x), Some(y), Some(z)) => Ok((x, y, z)),
218 _ => Err(SurfaceError::Point3DError {
219 reason: "Error converting Decimal to f64",
220 }),
221 }
222 }
223
224 pub fn from_f64_tuple(x: f64, y: f64, z: f64) -> Result<Self, SurfaceError> {
232 let x = Decimal::from_f64(x);
233 let y = Decimal::from_f64(y);
234 let z = Decimal::from_f64(z);
235
236 match (x, y, z) {
237 (Some(x), Some(y), Some(z)) => Ok(Self::new(x, y, z)),
238 _ => Err(SurfaceError::Point3DError {
239 reason: "Error converting f64 to Decimal",
240 }),
241 }
242 }
243
244 #[must_use]
272 pub fn point2d(&self) -> Box<Point2D> {
273 Box::new(Point2D::new(self.x, self.y))
274 }
275}
276
277impl From<&Point3D> for Point3D {
278 fn from(point: &Point3D) -> Self {
279 *point
280 }
281}
282
283impl HasX for Point3D {
284 fn get_x(&self) -> Decimal {
285 self.x
286 }
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
299#[repr(u8)]
300pub enum Axis {
301 X,
302 Y,
303 Z,
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 use crate::surfaces::Surface;
311 use optionstratlib_core::{model::Positive, pos_or_panic};
312 use rust_decimal_macros::dec;
313 use std::collections::BTreeSet;
314
315 #[test]
316 fn test_point3d_new() {
317 let point = Point3D::new(dec!(1.0), dec!(2.0), dec!(3.0));
318 assert_eq!(point.x, dec!(1.0));
319 assert_eq!(point.y, dec!(2.0));
320 assert_eq!(point.z, dec!(3.0));
321
322 let display = format!("{point}");
323 assert_eq!(display, "(x: 1.0, y: 2.0, z: 3.0)");
324
325 let point2d = point.point2d();
326 assert_eq!(point2d.x, dec!(1.0));
327 assert_eq!(point2d.y, dec!(2.0));
328
329 let has_x = point.get_x();
330 assert_eq!(has_x, dec!(1.0));
331 }
332
333 #[test]
334 fn test_point3d_negative() {
335 let point = Point3D::new(dec!(-1.0), dec!(-2.0), dec!(-3.0));
336 assert_eq!(point.x, dec!(-1.0));
337 assert_eq!(point.y, dec!(-2.0));
338 assert_eq!(point.z, dec!(-3.0));
339
340 let display = format!("{point}");
341 assert_eq!(display, "(x: -1.0, y: -2.0, z: -3.0)");
342
343 let tuple = point.to_tuple::<Positive, Decimal, Decimal>();
344 assert!(tuple.is_err());
345 let tuple = point.to_tuple::<Decimal, Positive, Decimal>();
346 assert!(tuple.is_err());
347 let tuple = point.to_tuple::<Decimal, Decimal, Positive>();
348 assert!(tuple.is_err());
349 }
350
351 #[test]
352 fn test_point3d_to_tuple() {
353 let result = Point3D::from_f64_tuple(1.0, 2.0, 3.0);
354 assert!(result.is_ok());
355 let point = result.unwrap();
356 let result: Result<(Decimal, Decimal, Decimal), _> = point.to_tuple();
357 assert!(result.is_ok());
358 let (x, y, z) = result.unwrap();
359 assert_eq!(x, dec!(1.0));
360 assert_eq!(y, dec!(2.0));
361 assert_eq!(z, dec!(3.0));
362 }
363
364 #[test]
365 fn test_point3d_from_tuple() {
366 let result = Point3D::from_tuple(dec!(1.0), Positive::TWO, pos_or_panic!(3.0));
367 assert!(result.is_ok());
368 let point = result.unwrap();
369 assert_eq!(point.x, dec!(1.0));
370 assert_eq!(point.y, dec!(2.0));
371 assert_eq!(point.z, dec!(3.0));
372 }
373
374 #[test]
375 fn test_point3d_to_f64_tuple() {
376 let result = Point3D::from_f64_tuple(1.0, 2.0, 3.0);
377 assert!(result.is_ok());
378 let point = result.unwrap();
379 let result = point.to_f64_tuple();
380 assert!(result.is_ok());
381 let (x, y, z) = result.unwrap();
382 assert_eq!(x, 1.0);
383 assert_eq!(y, 2.0);
384 assert_eq!(z, 3.0);
385 }
386
387 #[test]
388 fn test_point3d_from_f64_tuple() {
389 let result = Point3D::from_f64_tuple(1.0, 2.0, 3.0);
390 assert!(result.is_ok());
391 let point = result.unwrap();
392 assert_eq!(point.x, dec!(1.0));
393 assert_eq!(point.y, dec!(2.0));
394 assert_eq!(point.z, dec!(3.0));
395 }
396
397 #[test]
403 fn test_point3d_ordering() {
404 let p1 = Point3D::from_f64_tuple(1.0, 2.0, 3.0).unwrap();
405 let p2 = Point3D::from_f64_tuple(1.0, 2.0, 4.0).unwrap();
406 let p3 = Point3D::from_f64_tuple(1.0, 3.0, 1.0).unwrap();
407 let p4 = Point3D::from_f64_tuple(2.0, 1.0, 1.0).unwrap();
408
409 assert!(p1 < p2); assert!(p1 < p3); assert!(p1 < p4); assert_ne!(p1, p2);
416 assert_ne!(p1, p3);
417 assert_ne!(p1, p4);
418 }
419
420 #[test]
421 fn test_surface_new() {
422 let points = BTreeSet::from_iter(vec![
423 Point3D::from_f64_tuple(0.0, 0.0, 0.0).unwrap(),
424 Point3D::from_f64_tuple(1.0, 1.0, 1.0).unwrap(),
425 ]);
426 let surface = Surface::new(points.clone());
427
428 assert_eq!(surface.points, points);
429 }
430
431 #[test]
432 fn test_error_handling_invalid_f64() {
433 let result = Point3D::from_f64_tuple(f64::INFINITY, 2.0, 3.0);
434 assert!(result.is_err());
435 match result {
436 Err(SurfaceError::Point3DError { reason }) => {
437 assert_eq!(reason, "Error converting f64 to Decimal");
438 }
439 _ => panic!("Unexpected error type"),
440 }
441 }
442
443 #[test]
451 fn test_equal() {
452 let p1 = Point3D::from_f64_tuple(1.0, 2.0, 3.0).unwrap();
453 let p2 = Point3D::from_f64_tuple(1.0, 2.0, 3.0).unwrap();
454 let p3 = Point3D::from_f64_tuple(1.0, 2.0, 4.0).unwrap();
455 let p4 = Point3D::from_f64_tuple(1.0, 3.0, 3.0).unwrap();
456 let p5 = Point3D::from_f64_tuple(2.0, 2.0, 3.0).unwrap();
457
458 assert_eq!(p1, p2);
460 assert_ne!(p1, p3);
462 assert_ne!(p1, p4);
463 assert_ne!(p1, p5);
464 }
465
466 #[test]
468 fn test_eq_agrees_with_ord() {
469 let p1 = Point3D::from_f64_tuple(1.0, 2.0, 3.0).unwrap();
470 let p2 = Point3D::from_f64_tuple(1.0, 2.0, 3.0).unwrap();
471 let p3 = Point3D::from_f64_tuple(1.0, 2.0, 4.0).unwrap();
472
473 assert_eq!(p1.cmp(&p2), Ordering::Equal);
474 assert_eq!(p1 == p2, p1.cmp(&p2) == Ordering::Equal);
475 assert_eq!(p1 == p3, p1.cmp(&p3) == Ordering::Equal);
476 }
477
478 #[test]
482 fn test_hash_agrees_with_eq() {
483 use std::collections::HashSet;
484
485 let p1 = Point3D::new(dec!(1.0), dec!(2.0), dec!(3.0));
486 let scaled = Point3D::new(dec!(1.00), dec!(2.000), dec!(3.0000));
487 let other_z = Point3D::new(dec!(1.0), dec!(2.0), dec!(4.0));
488
489 assert_eq!(p1, scaled);
490 assert_ne!(p1, other_z);
491
492 let set: HashSet<Point3D> = [p1, scaled, other_z].into_iter().collect();
493 assert_eq!(set.len(), 2);
495 assert!(set.contains(&p1));
496 assert!(set.contains(&other_z));
497 }
498}
499
500#[cfg(test)]
501mod tests_point3d_serde {
502 use super::*;
503 use rust_decimal_macros::dec;
504 use serde_json::Value;
505
506 #[test]
507 fn test_basic_serialization() {
508 let point = Point3D {
509 x: dec!(1.5),
510 y: dec!(2.5),
511 z: dec!(3.5),
512 };
513
514 let serialized = serde_json::to_string(&point).unwrap();
515 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
516
517 assert_eq!(point.x, deserialized.x);
518 assert_eq!(point.y, deserialized.y);
519 assert_eq!(point.z, deserialized.z);
520 }
521
522 #[test]
523 fn test_zero_values() {
524 let point = Point3D {
525 x: dec!(0.0),
526 y: dec!(0.0),
527 z: dec!(0.0),
528 };
529
530 let serialized = serde_json::to_string(&point).unwrap();
531 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
532
533 assert_eq!(point, deserialized);
534 }
535
536 #[test]
537 fn test_negative_values() {
538 let point = Point3D {
539 x: dec!(-1.5),
540 y: dec!(-2.5),
541 z: dec!(-3.5),
542 };
543
544 let serialized = serde_json::to_string(&point).unwrap();
545 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
546
547 assert_eq!(point, deserialized);
548 }
549
550 #[test]
551 fn test_high_precision_values() {
552 let point = Point3D {
553 x: dec!(1.12345678901234567890),
554 y: dec!(2.12345678901234567890),
555 z: dec!(3.12345678901234567890),
556 };
557
558 let serialized = serde_json::to_string(&point).unwrap();
559 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
560
561 assert_eq!(point.x, deserialized.x);
562 assert_eq!(point.y, deserialized.y);
563 assert_eq!(point.z, deserialized.z);
564 }
565
566 #[test]
567 fn test_json_structure() {
568 let point = Point3D {
569 x: dec!(1.5),
570 y: dec!(2.5),
571 z: dec!(3.5),
572 };
573
574 let serialized = serde_json::to_string(&point).unwrap();
575 let json_value: Value = serde_json::from_str(&serialized).unwrap();
576
577 assert!(json_value.is_object());
579 assert_eq!(json_value.as_object().unwrap().len(), 3);
580 assert!(json_value.get("x").is_some());
581 assert!(json_value.get("y").is_some());
582 assert!(json_value.get("z").is_some());
583 }
584
585 #[test]
586 fn test_pretty_print() {
587 let point = Point3D {
588 x: dec!(1.5),
589 y: dec!(2.5),
590 z: dec!(3.5),
591 };
592
593 let serialized = serde_json::to_string_pretty(&point).unwrap();
594
595 assert!(serialized.contains('\n'));
597 assert!(serialized.contains(" "));
598
599 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
601 assert_eq!(point, deserialized);
602 }
603
604 #[test]
605 fn test_deserialize_from_integers() {
606 let json_str = r#"{"x": 1, "y": 2, "z": 3}"#;
607 let point: Point3D = serde_json::from_str(json_str).unwrap();
608
609 assert_eq!(point.x, dec!(1.0));
610 assert_eq!(point.y, dec!(2.0));
611 assert_eq!(point.z, dec!(3.0));
612 }
613
614 #[test]
615 fn test_deserialize_from_strings() {
616 let json_str = r#"{"x": "1.5", "y": "2.5", "z": "3.5"}"#;
617 let point: Point3D = serde_json::from_str(json_str).unwrap();
618
619 assert_eq!(point.x, dec!(1.5));
620 assert_eq!(point.y, dec!(2.5));
621 assert_eq!(point.z, dec!(3.5));
622 }
623
624 #[test]
625 fn test_invalid_json() {
626 let json_str = r#"{"x": 1.5, "y": 2.5}"#;
628 let result = serde_json::from_str::<Point3D>(json_str);
629 assert!(result.is_err());
630
631 let json_str = r#"{"x": "invalid", "y": 2.5, "z": 3.5}"#;
633 let result = serde_json::from_str::<Point3D>(json_str);
634 assert!(result.is_err());
635
636 let json_str = r#"{"x": true, "y": 2.5, "z": 3.5}"#;
638 let result = serde_json::from_str::<Point3D>(json_str);
639 assert!(result.is_err());
640 }
641
642 #[test]
643 fn test_max_values() {
644 let point = Point3D {
645 x: Decimal::MAX,
646 y: Decimal::MAX,
647 z: Decimal::MAX,
648 };
649
650 let serialized = serde_json::to_string(&point).unwrap();
651 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
652
653 assert_eq!(point, deserialized);
654 }
655
656 #[test]
657 fn test_min_values() {
658 let point = Point3D {
659 x: Decimal::MIN,
660 y: Decimal::MIN,
661 z: Decimal::MIN,
662 };
663
664 let serialized = serde_json::to_string(&point).unwrap();
665 let deserialized: Point3D = serde_json::from_str(&serialized).unwrap();
666
667 assert_eq!(point, deserialized);
668 }
669
670 #[test]
671 fn test_json_to_vec() {
672 let points = vec![
673 Point3D {
674 x: dec!(1.0),
675 y: dec!(2.0),
676 z: dec!(3.0),
677 },
678 Point3D {
679 x: dec!(4.0),
680 y: dec!(5.0),
681 z: dec!(6.0),
682 },
683 ];
684
685 let serialized = serde_json::to_string(&points).unwrap();
686 let deserialized: Vec<Point3D> = serde_json::from_str(&serialized).unwrap();
687
688 assert_eq!(points, deserialized);
689 }
690
691 #[test]
692 fn test_array() {
693 let json_str = "[1.5, 2.5, 3.5]";
694 let result = serde_json::from_str::<Point3D>(json_str);
695 assert!(result.is_ok());
696 }
697}