1use crate::curves::Point2D;
7use crate::curves::traits::StatisticalCurve;
8use crate::curves::utils::{detect_peaks_and_valleys, grid_coordinate};
9use crate::error::{CurveError, InterpolationError, MetricsError};
10use crate::geometrics::{
11 Arithmetic, AxisOperations, BasicMetrics, BiLinearInterpolation, ConstructionMethod,
12 ConstructionParams, CubicInterpolation, GeometricObject, GeometricTransformations, Interpolate,
13 InterpolationType, LinearInterpolation, MergeAxisInterpolate, MergeOperation, MetricsExtractor,
14 RangeMetrics, RiskMetrics, ShapeMetrics, SplineInterpolation, TrendMetrics,
15 coefficient_of_variation, population_std_dev, powu_checked,
16};
17use optionstratlib_core::error::DecimalError;
18use optionstratlib_core::model::decimal::{
19 d_add, d_div, d_mul, d_product_iter, d_sqrt, d_sub, d_sum_iter,
20};
21use optionstratlib_core::utils::Len;
22use optionstratlib_core::utils::time::ReferenceCarrier;
23use rayon::iter::{IntoParallelIterator, ParallelIterator};
24use rayon::prelude::*;
25use rust_decimal::Decimal;
26use rust_decimal_macros::dec;
27use serde::{Deserialize, Serialize};
28use std::collections::{BTreeSet, btree_set};
29use std::fmt::{Display, Formatter};
30use std::ops::{Bound, Index};
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
71#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
72#[serde(deny_unknown_fields)]
73pub struct Curve {
74 pub points: BTreeSet<Point2D>,
83
84 pub x_range: (Decimal, Decimal),
88}
89
90impl Display for Curve {
91 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
92 for point in self.points.iter() {
93 writeln!(f, "{point}")?;
94 }
95 Ok(())
96 }
97}
98
99impl Default for Curve {
100 fn default() -> Self {
101 Curve {
102 points: BTreeSet::new(),
103 x_range: (Decimal::ZERO, Decimal::ZERO),
104 }
105 }
106}
107
108impl Curve {
109 #[must_use]
172 pub fn new(points: BTreeSet<Point2D>) -> Self {
173 let x_range = Self::calculate_range(points.iter().map(|p| p.x));
174 Curve { points, x_range }
175 }
176
177 fn exact_point_at(&self, x: Decimal) -> Result<Option<Point2D>, InterpolationError> {
187 let mut at_x = self.points_at(x);
188 let Some(point) = at_x.next() else {
189 return Ok(None);
190 };
191 if at_x.next().is_some() {
192 return Err(InterpolationError::DegenerateInterval);
193 }
194 Ok(Some(*point))
195 }
196
197 fn points_at(&self, x: Decimal) -> btree_set::Range<'_, Point2D> {
203 self.points
204 .range(Point2D { x, y: Decimal::MIN }..=Point2D { x, y: Decimal::MAX })
205 }
206
207 fn point_after(&self, point: &Point2D) -> Option<&Point2D> {
209 self.points
210 .range((Bound::Excluded(*point), Bound::Unbounded))
211 .next()
212 }
213
214 fn point_before(&self, point: &Point2D) -> Option<&Point2D> {
216 self.points.range(..*point).next_back()
217 }
218
219 fn bracket(&self, x: Decimal) -> Result<(&Point2D, &Point2D), InterpolationError> {
229 if self.points.len() < 2 {
230 return Err(InterpolationError::EmptyData);
231 }
232 let (Some(first), Some(last)) = (self.points.first(), self.points.last()) else {
233 return Err(InterpolationError::EmptyData);
234 };
235 if x < first.x || x > last.x {
236 return Err(InterpolationError::OutOfRange {
237 target: x.to_string(),
238 });
239 }
240 let floor = Point2D { x, y: Decimal::MIN };
241 let right = self
243 .points
244 .range(floor..)
245 .next()
246 .ok_or(InterpolationError::DegenerateInterval)?;
247 match self.points.range(..floor).next_back() {
248 Some(left) => Ok((left, right)),
249 None => self
252 .point_after(right)
253 .map(|second| (right, second))
254 .ok_or(InterpolationError::DegenerateInterval),
255 }
256 }
257
258 fn bilinear_cell(&self, x: Decimal) -> Result<[&Point2D; 4], InterpolationError> {
266 let (near_left, near_right) = self.bracket(x)?;
267 let mut beyond = self
268 .points
269 .range((Bound::Excluded(*near_right), Bound::Unbounded));
270 let far = match (beyond.next(), beyond.next()) {
271 (Some(left), Some(right)) => Some((left, right)),
272 _ => {
273 let mut tail = self.points.iter().rev();
274 match (tail.next(), tail.next()) {
275 (Some(right), Some(left)) => Some((left, right)),
276 _ => None,
277 }
278 }
279 };
280 let (far_left, far_right) = far.ok_or_else(|| {
281 InterpolationError::Bilinear(format!(
282 "the far edge needs two points, the curve has {}",
283 self.points.len()
284 ))
285 })?;
286 Ok([near_left, near_right, far_left, far_right])
287 }
288
289 fn cubic_window(&self, x: Decimal) -> Result<[&Point2D; 4], InterpolationError> {
298 let (left, right) = self.bracket(x)?;
299 let window = match (self.point_before(left), self.point_after(right)) {
300 (None, _) => {
302 let mut head = self.points.iter();
303 (head.next(), head.next(), head.next(), head.next())
304 }
305 (Some(_), None) => {
307 let mut tail = self.points.iter().rev();
308 let (p3, p2, p1, p0) = (tail.next(), tail.next(), tail.next(), tail.next());
309 (p0, p1, p2, p3)
310 }
311 (Some(before), Some(after)) => (Some(before), Some(left), Some(right), Some(after)),
312 };
313 match window {
314 (Some(p0), Some(p1), Some(p2), Some(p3)) => Ok([p0, p1, p2, p3]),
315 _ => Err(InterpolationError::Cubic(format!(
316 "the window needs four points, the curve has {}",
317 self.points.len()
318 ))),
319 }
320 }
321
322 fn spline_precheck(&self, x: Decimal) -> Result<Option<Point2D>, InterpolationError> {
327 let len = self.points.len();
328
329 if len < 3 {
331 return Err(InterpolationError::Spline(
332 "Need at least three points for spline interpolation".to_string(),
333 ));
334 }
335
336 let (Some(first), Some(last)) = (self.points.first(), self.points.last()) else {
338 return Err(InterpolationError::Spline(format!(
339 "point index 0 is out of bounds for a curve of {len} points"
340 )));
341 };
342 if x < first.x || x > last.x {
343 return Err(InterpolationError::Spline(
344 "x is outside the range of points".to_string(),
345 ));
346 }
347
348 self.exact_point_at(x)
352 }
353}
354
355#[derive(Debug, Clone)]
400pub struct CurveSpline<'a> {
401 curve: &'a Curve,
402 knots: Vec<&'a Point2D>,
403 moments: Option<Vec<Decimal>>,
406}
407
408impl<'a> CurveSpline<'a> {
409 #[must_use]
415 pub fn new(curve: &'a Curve) -> Self {
416 let knots: Vec<&'a Point2D> = curve.points.iter().collect();
417 let moments = if knots.len() < 3 {
418 None
419 } else {
420 spline_moments(&knots).ok()
421 };
422 CurveSpline {
423 curve,
424 knots,
425 moments,
426 }
427 }
428
429 pub fn interpolate(&self, x: Decimal) -> Result<Point2D, InterpolationError> {
443 if let Some(point) = self.curve.spline_precheck(x)? {
444 return Ok(point);
445 }
446 match &self.moments {
447 Some(moments) => spline_eval(&self.knots, moments, x),
448 None => self.unsolvable(),
449 }
450 }
451
452 #[cold]
456 fn unsolvable(&self) -> Result<Point2D, InterpolationError> {
457 spline_moments(&self.knots).and_then(|_| {
458 Err(InterpolationError::Spline(
459 "the spline system solved on retry after failing once".to_string(),
460 ))
461 })
462 }
463}
464
465fn construction_err(err: DecimalError) -> CurveError {
468 CurveError::ConstructionError(err.to_string())
469}
470
471fn analysis_err(err: DecimalError) -> CurveError {
473 CurveError::AnalysisError(err.to_string())
474}
475
476fn interp_err(
479 kind: fn(String) -> InterpolationError,
480) -> impl Fn(DecimalError) -> InterpolationError {
481 move |err| kind(err.to_string())
482}
483
484fn third_quartile_index(len: usize) -> Result<usize, MetricsError> {
488 len.checked_mul(3)
489 .map(|three_len| three_len / 4)
490 .ok_or_else(|| {
491 MetricsError::BasicError(format!("third quartile index of {len} samples overflowed"))
492 })
493}
494
495fn sample_at(
496 values: &[Decimal],
497 index: usize,
498 what: &'static str,
499) -> Result<Decimal, MetricsError> {
500 values.get(index).copied().ok_or_else(|| {
501 MetricsError::BasicError(format!(
502 "{what}: sample {index} is out of bounds for {} values",
503 values.len()
504 ))
505 })
506}
507
508fn mean_of(values: &[Decimal], op: &'static str) -> Result<Decimal, DecimalError> {
511 let sum = d_sum_iter(values.iter().copied(), op)?;
512 d_div(sum, Decimal::from(values.len()), op)
513}
514
515fn variance_of(
517 values: &[Decimal],
518 mean: Decimal,
519 op: &'static str,
520) -> Result<Decimal, DecimalError> {
521 let mut acc = Decimal::ZERO;
522 for &value in values {
523 let centered = d_sub(value, mean, op)?;
524 let squared = powu_checked(centered, 2, op)?;
525 acc = d_add(acc, squared, op)?;
526 }
527 d_div(acc, Decimal::from(values.len()), op)
528}
529
530fn standardized_moment(
534 centered: &[Decimal],
535 std_dev: Decimal,
536 order: u64,
537 op: &'static str,
538) -> Result<Decimal, DecimalError> {
539 let mut acc = Decimal::ZERO;
540 for &value in centered {
541 let z = d_div(value, std_dev, op)?;
542 let moment = powu_checked(z, order, op)?;
543 acc = d_add(acc, moment, op)?;
544 }
545 d_div(acc, Decimal::from(centered.len()), op)
546}
547
548fn shift_index(
554 index: usize,
555 by: isize,
556 kind: fn(String) -> InterpolationError,
557) -> Result<usize, InterpolationError> {
558 index.checked_add_signed(by).ok_or_else(|| {
559 kind(format!(
560 "index {index} shifted by {by} leaves the usize range"
561 ))
562 })
563}
564
565fn band_at(
566 band: &[Decimal],
567 index: usize,
568 name: &'static str,
569) -> Result<Decimal, InterpolationError> {
570 band.get(index).copied().ok_or_else(|| {
571 InterpolationError::Spline(format!(
572 "spline band `{name}` has no slot {index} (len {})",
573 band.len()
574 ))
575 })
576}
577
578fn band_at_mut<'a>(
580 band: &'a mut [Decimal],
581 index: usize,
582 name: &'static str,
583) -> Result<&'a mut Decimal, InterpolationError> {
584 let len = band.len();
585 band.get_mut(index).ok_or_else(|| {
586 InterpolationError::Spline(format!(
587 "spline band `{name}` has no slot {index} (len {len})"
588 ))
589 })
590}
591
592fn cube_scaled(
595 moment: Decimal,
596 d: Decimal,
597 six_h: Decimal,
598 op: &'static str,
599) -> Result<Decimal, InterpolationError> {
600 let scaled = d_mul(moment, d, op).map_err(interp_err(InterpolationError::Spline))?;
601 let scaled = d_mul(scaled, d, op).map_err(interp_err(InterpolationError::Spline))?;
602 let scaled = d_mul(scaled, d, op).map_err(interp_err(InterpolationError::Spline))?;
603 d_div(scaled, six_h, op).map_err(interp_err(InterpolationError::Spline))
604}
605
606fn linear_term(
609 y: Decimal,
610 moment: Decimal,
611 h: Decimal,
612 d: Decimal,
613 op: &'static str,
614) -> Result<Decimal, InterpolationError> {
615 let level = d_div(y, h, op).map_err(interp_err(InterpolationError::Spline))?;
616 let bend = d_mul(moment, h, op).map_err(interp_err(InterpolationError::Spline))?;
617 let bend = d_div(bend, dec!(6), op).map_err(interp_err(InterpolationError::Spline))?;
618 let slope = d_sub(level, bend, op).map_err(interp_err(InterpolationError::Spline))?;
619 d_mul(slope, d, op).map_err(interp_err(InterpolationError::Spline))
620}
621
622impl Len for Curve {
623 fn len(&self) -> usize {
624 self.points.len()
625 }
626
627 fn is_empty(&self) -> bool {
628 self.points.is_empty()
629 }
630}
631
632impl GeometricObject<Point2D, Decimal> for Curve {
633 type Error = CurveError;
634
635 fn get_points(&self) -> BTreeSet<&Point2D> {
636 self.points.iter().collect()
637 }
638
639 fn from_vector<T>(points: Vec<T>) -> Self
651 where
652 T: Into<Point2D> + Clone,
653 {
654 let points: BTreeSet<Point2D> = points.into_iter().map(|p| p.into()).collect();
655
656 let x_range = Self::calculate_range(points.iter().map(|p| p.x));
657 Curve { points, x_range }
658 }
659
660 fn construct<T>(method: T) -> Result<Self, Self::Error>
661 where
662 Self: Sized,
663 T: Into<ConstructionMethod<Point2D, Decimal, CurveError>>,
664 {
665 let method = method.into();
666 match method {
667 ConstructionMethod::FromData { points } => {
668 if points.is_empty() {
669 return Err(CurveError::Point2DError {
670 reason: "Empty points array",
671 });
672 }
673 Ok(Curve::new(points))
674 }
675 ConstructionMethod::Parametric { f, params } => {
676 let (t_start, t_end, steps) = match params {
677 ConstructionParams::D2 {
678 t_start,
679 t_end,
680 steps,
681 } => (t_start, t_end, steps),
682 _ => {
683 return Err(CurveError::ConstructionError(
684 "Invalid parameters".to_string(),
685 ));
686 }
687 };
688 if steps == 0 {
689 return Err(CurveError::ConstructionError(
690 "Parametric construction needs at least one step".to_string(),
691 ));
692 }
693 let op = "Curve::construct::step_size";
694 let span = d_sub(t_end, t_start, op).map_err(construction_err)?;
695 let step_size = d_div(span, Decimal::from(steps), op).map_err(construction_err)?;
696
697 let carrier = ReferenceCarrier::capture();
699 let points: Result<BTreeSet<Point2D>, CurveError> = (0..=steps)
700 .into_par_iter()
701 .map(|i| {
702 let offset = d_mul(step_size, Decimal::from(i), "Curve::construct::offset")
703 .map_err(construction_err)?;
704 let t = grid_coordinate(
707 d_add(t_start, offset, "Curve::construct::t")
708 .map_err(construction_err)?,
709 t_start,
710 t_end,
711 i == steps,
712 );
713 carrier.run(|| f(t))
714 })
715 .collect();
716
717 points.map(Curve::new)
718 }
719 }
720 }
721}
722
723impl Index<usize> for Curve {
784 type Output = Point2D;
785
786 fn index(&self, index: usize) -> &Self::Output {
793 match self.points.iter().nth(index) {
798 Some(p) => p,
799 None => {
800 let len = self.points.len();
805 panic!("Curve::index: out of bounds (index = {index}, len = {len})") }
807 }
808 }
809}
810
811impl Interpolate<Point2D, Decimal> for Curve {
839 fn find_bracket_points(&self, x: Decimal) -> Result<(usize, usize), InterpolationError> {
844 let (left, _) = self.bracket(x)?;
845 let i = self.points.range(..*left).count();
846 i.checked_add(1)
847 .map(|j| (i, j))
848 .ok_or(InterpolationError::DegenerateInterval)
849 }
850}
851
852impl LinearInterpolation<Point2D, Decimal> for Curve {
933 fn linear_interpolate(&self, x: Decimal) -> Result<Point2D, InterpolationError> {
940 if let Some(point) = self.exact_point_at(x)? {
945 return Ok(point);
946 }
947
948 let (p1, p2) = self.bracket(x)?;
949
950 let run = d_sub(p2.x, p1.x, "Curve::linear_interpolate::run")
955 .map_err(interp_err(InterpolationError::Linear))?;
956 if run.is_zero() {
957 return Err(InterpolationError::DegenerateInterval);
958 }
959
960 let dx = d_sub(x, p1.x, "Curve::linear_interpolate::dx")
962 .map_err(interp_err(InterpolationError::Linear))?;
963 let rise = d_sub(p2.y, p1.y, "Curve::linear_interpolate::rise")
964 .map_err(interp_err(InterpolationError::Linear))?;
965 let scaled = d_mul(dx, rise, "Curve::linear_interpolate::scaled")
966 .map_err(interp_err(InterpolationError::Linear))?;
967 let ratio = d_div(scaled, run, "Curve::linear_interpolate::ratio")
968 .map_err(interp_err(InterpolationError::Linear))?;
969 let y = d_add(p1.y, ratio, "Curve::linear_interpolate::y")
970 .map_err(interp_err(InterpolationError::Linear))?;
971
972 Ok(Point2D::new(x, y))
973 }
974}
975
976impl BiLinearInterpolation<Point2D, Decimal> for Curve {
1104 fn bilinear_interpolate(&self, x: Decimal) -> Result<Point2D, InterpolationError> {
1122 let len = self.len();
1123
1124 if len < 4 {
1126 return Err(InterpolationError::Bilinear(
1127 "Need at least four points for bilinear interpolation".to_string(),
1128 ));
1129 }
1130
1131 if let Some(point) = self.exact_point_at(x)? {
1136 return Ok(point);
1137 }
1138
1139 let [p11, p12, p21, p22] = self.bilinear_cell(x)?;
1145
1146 let span = d_sub(p12.x, p11.x, "Curve::bilinear_interpolate::span")
1147 .map_err(interp_err(InterpolationError::Bilinear))?;
1148 if span.is_zero() {
1149 return Err(InterpolationError::DegenerateInterval);
1150 }
1151
1152 let offset = d_sub(x, p11.x, "Curve::bilinear_interpolate::offset")
1154 .map_err(interp_err(InterpolationError::Bilinear))?;
1155 let dx = d_div(offset, span, "Curve::bilinear_interpolate::dx")
1156 .map_err(interp_err(InterpolationError::Bilinear))?;
1157
1158 let bottom_rise = d_sub(p12.y, p11.y, "Curve::bilinear_interpolate::bottom_rise")
1160 .map_err(interp_err(InterpolationError::Bilinear))?;
1161 let bottom_step = d_mul(dx, bottom_rise, "Curve::bilinear_interpolate::bottom_step")
1162 .map_err(interp_err(InterpolationError::Bilinear))?;
1163 let bottom = d_add(p11.y, bottom_step, "Curve::bilinear_interpolate::bottom")
1164 .map_err(interp_err(InterpolationError::Bilinear))?;
1165
1166 let top_rise = d_sub(p22.y, p21.y, "Curve::bilinear_interpolate::top_rise")
1168 .map_err(interp_err(InterpolationError::Bilinear))?;
1169 let top_step = d_mul(dx, top_rise, "Curve::bilinear_interpolate::top_step")
1170 .map_err(interp_err(InterpolationError::Bilinear))?;
1171 let top = d_add(p21.y, top_step, "Curve::bilinear_interpolate::top")
1172 .map_err(interp_err(InterpolationError::Bilinear))?;
1173
1174 let edge_gap = d_sub(top, bottom, "Curve::bilinear_interpolate::edge_gap")
1176 .map_err(interp_err(InterpolationError::Bilinear))?;
1177 let half_gap = d_div(edge_gap, dec!(2), "Curve::bilinear_interpolate::half_gap")
1178 .map_err(interp_err(InterpolationError::Bilinear))?;
1179 let y = d_add(bottom, half_gap, "Curve::bilinear_interpolate::y")
1180 .map_err(interp_err(InterpolationError::Bilinear))?;
1181
1182 Ok(Point2D::new(x, y))
1183 }
1184}
1185
1186impl CubicInterpolation<Point2D, Decimal> for Curve {
1246 fn cubic_interpolate(&self, x: Decimal) -> Result<Point2D, InterpolationError> {
1320 let len = self.len();
1321
1322 if len < 4 {
1324 return Err(InterpolationError::Cubic(
1325 "Need at least four points for cubic interpolation".to_string(),
1326 ));
1327 }
1328
1329 if let Some(point) = self.exact_point_at(x)? {
1333 return Ok(point);
1334 }
1335
1336 let [p0, p1, p2, p3] = self.cubic_window(x)?;
1337
1338 let span = d_sub(p2.x, p1.x, "Curve::cubic_interpolate::span")
1339 .map_err(interp_err(InterpolationError::Cubic))?;
1340 if span.is_zero() {
1341 return Err(InterpolationError::DegenerateInterval);
1342 }
1343
1344 let offset = d_sub(x, p1.x, "Curve::cubic_interpolate::offset")
1346 .map_err(interp_err(InterpolationError::Cubic))?;
1347 let t = d_div(offset, span, "Curve::cubic_interpolate::t")
1348 .map_err(interp_err(InterpolationError::Cubic))?;
1349
1350 let t2 = d_mul(t, t, "Curve::cubic_interpolate::t2")
1352 .map_err(interp_err(InterpolationError::Cubic))?;
1353 let t3 = d_mul(t2, t, "Curve::cubic_interpolate::t3")
1354 .map_err(interp_err(InterpolationError::Cubic))?;
1355
1356 let term0 = d_mul(dec!(2), p1.y, "Curve::cubic_interpolate::term0")
1357 .map_err(interp_err(InterpolationError::Cubic))?;
1358
1359 let linear_coeff = d_sub(p2.y, p0.y, "Curve::cubic_interpolate::linear_coeff")
1360 .map_err(interp_err(InterpolationError::Cubic))?;
1361 let term1 = d_mul(linear_coeff, t, "Curve::cubic_interpolate::term1")
1362 .map_err(interp_err(InterpolationError::Cubic))?;
1363
1364 let two_p0 = d_mul(dec!(2), p0.y, "Curve::cubic_interpolate::two_p0")
1365 .map_err(interp_err(InterpolationError::Cubic))?;
1366 let five_p1 = d_mul(dec!(5), p1.y, "Curve::cubic_interpolate::five_p1")
1367 .map_err(interp_err(InterpolationError::Cubic))?;
1368 let four_p2 = d_mul(dec!(4), p2.y, "Curve::cubic_interpolate::four_p2")
1369 .map_err(interp_err(InterpolationError::Cubic))?;
1370 let quad_coeff = d_sub(two_p0, five_p1, "Curve::cubic_interpolate::quad_coeff_a")
1371 .map_err(interp_err(InterpolationError::Cubic))?;
1372 let quad_coeff = d_add(
1373 quad_coeff,
1374 four_p2,
1375 "Curve::cubic_interpolate::quad_coeff_b",
1376 )
1377 .map_err(interp_err(InterpolationError::Cubic))?;
1378 let quad_coeff = d_sub(quad_coeff, p3.y, "Curve::cubic_interpolate::quad_coeff_c")
1379 .map_err(interp_err(InterpolationError::Cubic))?;
1380 let term2 = d_mul(quad_coeff, t2, "Curve::cubic_interpolate::term2")
1381 .map_err(interp_err(InterpolationError::Cubic))?;
1382
1383 let three_p1 = d_mul(dec!(3), p1.y, "Curve::cubic_interpolate::three_p1")
1384 .map_err(interp_err(InterpolationError::Cubic))?;
1385 let three_p2 = d_mul(dec!(3), p2.y, "Curve::cubic_interpolate::three_p2")
1386 .map_err(interp_err(InterpolationError::Cubic))?;
1387 let cubic_coeff = d_sub(three_p1, p0.y, "Curve::cubic_interpolate::cubic_coeff_a")
1388 .map_err(interp_err(InterpolationError::Cubic))?;
1389 let cubic_coeff = d_sub(
1390 cubic_coeff,
1391 three_p2,
1392 "Curve::cubic_interpolate::cubic_coeff_b",
1393 )
1394 .map_err(interp_err(InterpolationError::Cubic))?;
1395 let cubic_coeff = d_add(cubic_coeff, p3.y, "Curve::cubic_interpolate::cubic_coeff_c")
1396 .map_err(interp_err(InterpolationError::Cubic))?;
1397 let term3 = d_mul(cubic_coeff, t3, "Curve::cubic_interpolate::term3")
1398 .map_err(interp_err(InterpolationError::Cubic))?;
1399
1400 let sum = d_sum_iter(
1401 [term0, term1, term2, term3],
1402 "Curve::cubic_interpolate::sum",
1403 )
1404 .map_err(interp_err(InterpolationError::Cubic))?;
1405 let y = d_mul(dec!(0.5), sum, "Curve::cubic_interpolate::y")
1406 .map_err(interp_err(InterpolationError::Cubic))?;
1407
1408 Ok(Point2D::new(x, y))
1409 }
1410}
1411
1412impl SplineInterpolation<Point2D, Decimal> for Curve {
1469 fn spline_interpolate(&self, x: Decimal) -> Result<Point2D, InterpolationError> {
1573 if let Some(point) = self.spline_precheck(x)? {
1574 return Ok(point);
1575 }
1576 let pts: Vec<&Point2D> = self.points.iter().collect();
1577 let m = spline_moments(&pts)?;
1578 spline_eval(&pts, &m, x)
1579 }
1580}
1581
1582fn spline_moments(pts: &[&Point2D]) -> Result<Vec<Decimal>, InterpolationError> {
1591 let n = pts.len();
1592 let spline = InterpolationError::Spline;
1593 let last_index = shift_index(n, -1, spline)?;
1594 let at = |index: usize| -> Result<&Point2D, InterpolationError> {
1595 pts.get(index).copied().ok_or_else(|| {
1596 InterpolationError::Spline(format!(
1597 "point index {index} is out of bounds for a curve of {n} points"
1598 ))
1599 })
1600 };
1601
1602 let mut a = vec![Decimal::ZERO];
1606 let mut b = vec![Decimal::ONE];
1607 let mut c = vec![Decimal::ZERO];
1608 let mut r = vec![Decimal::ZERO];
1609
1610 for i in 1..last_index {
1612 let prev = at(shift_index(i, -1, spline)?)?;
1613 let curr = at(i)?;
1614 let next = at(shift_index(i, 1, spline)?)?;
1615
1616 let hi = d_sub(curr.x, prev.x, "Curve::spline_interpolate::hi")
1617 .map_err(interp_err(InterpolationError::Spline))?;
1618 let hi1 = d_sub(next.x, curr.x, "Curve::spline_interpolate::hi1")
1619 .map_err(interp_err(InterpolationError::Spline))?;
1620 if hi.is_zero() || hi1.is_zero() {
1623 return Err(InterpolationError::DegenerateInterval);
1624 }
1625
1626 let band = d_add(hi, hi1, "Curve::spline_interpolate::band")
1627 .map_err(interp_err(InterpolationError::Spline))?;
1628 let diag = d_mul(dec!(2), band, "Curve::spline_interpolate::diag")
1629 .map_err(interp_err(InterpolationError::Spline))?;
1630
1631 let rise_next = d_sub(next.y, curr.y, "Curve::spline_interpolate::rise_next")
1632 .map_err(interp_err(InterpolationError::Spline))?;
1633 let slope_next = d_div(rise_next, hi1, "Curve::spline_interpolate::slope_next")
1634 .map_err(interp_err(InterpolationError::Spline))?;
1635 let rise_prev = d_sub(curr.y, prev.y, "Curve::spline_interpolate::rise_prev")
1636 .map_err(interp_err(InterpolationError::Spline))?;
1637 let slope_prev = d_div(rise_prev, hi, "Curve::spline_interpolate::slope_prev")
1638 .map_err(interp_err(InterpolationError::Spline))?;
1639 let curvature = d_sub(
1640 slope_next,
1641 slope_prev,
1642 "Curve::spline_interpolate::curvature",
1643 )
1644 .map_err(interp_err(InterpolationError::Spline))?;
1645 let rhs = d_mul(dec!(6), curvature, "Curve::spline_interpolate::rhs")
1646 .map_err(interp_err(InterpolationError::Spline))?;
1647
1648 a.push(hi);
1649 b.push(diag);
1650 c.push(hi1);
1651 r.push(rhs);
1652 }
1653
1654 a.push(Decimal::ZERO);
1656 b.push(Decimal::ONE);
1657 c.push(Decimal::ZERO);
1658 r.push(Decimal::ZERO);
1659
1660 let mut m = vec![Decimal::ZERO; n];
1662
1663 for i in 1..last_index {
1664 let prev = shift_index(i, -1, spline)?;
1665 let a_i = band_at(&a, i, "a")?;
1666 let b_prev = band_at(&b, prev, "b")?;
1667 if b_prev.is_zero() {
1668 return Err(InterpolationError::DegenerateInterval);
1669 }
1670 let w = d_div(a_i, b_prev, "Curve::spline_interpolate::w")
1671 .map_err(interp_err(InterpolationError::Spline))?;
1672
1673 let c_prev = band_at(&c, prev, "c")?;
1674 let wc = d_mul(w, c_prev, "Curve::spline_interpolate::wc")
1675 .map_err(interp_err(InterpolationError::Spline))?;
1676 let b_i = band_at(&b, i, "b")?;
1677 *band_at_mut(&mut b, i, "b")? = d_sub(b_i, wc, "Curve::spline_interpolate::b_i")
1678 .map_err(interp_err(InterpolationError::Spline))?;
1679
1680 let r_prev = band_at(&r, prev, "r")?;
1681 let wr = d_mul(w, r_prev, "Curve::spline_interpolate::wr")
1682 .map_err(interp_err(InterpolationError::Spline))?;
1683 let r_i = band_at(&r, i, "r")?;
1684 *band_at_mut(&mut r, i, "r")? = d_sub(r_i, wr, "Curve::spline_interpolate::r_i")
1685 .map_err(interp_err(InterpolationError::Spline))?;
1686 }
1687
1688 let b_last = band_at(&b, last_index, "b")?;
1689 if b_last.is_zero() {
1690 return Err(InterpolationError::DegenerateInterval);
1691 }
1692 let r_last = band_at(&r, last_index, "r")?;
1693 *band_at_mut(&mut m, last_index, "m")? =
1694 d_div(r_last, b_last, "Curve::spline_interpolate::m_last")
1695 .map_err(interp_err(InterpolationError::Spline))?;
1696 for i in (1..last_index).rev() {
1697 let c_i = band_at(&c, i, "c")?;
1698 let m_next = band_at(&m, shift_index(i, 1, spline)?, "m")?;
1699 let cm = d_mul(c_i, m_next, "Curve::spline_interpolate::cm")
1700 .map_err(interp_err(InterpolationError::Spline))?;
1701 let r_i = band_at(&r, i, "r")?;
1702 let numerator = d_sub(r_i, cm, "Curve::spline_interpolate::m_numerator")
1703 .map_err(interp_err(InterpolationError::Spline))?;
1704 let b_i = band_at(&b, i, "b")?;
1705 if b_i.is_zero() {
1706 return Err(InterpolationError::DegenerateInterval);
1707 }
1708 *band_at_mut(&mut m, i, "m")? = d_div(numerator, b_i, "Curve::spline_interpolate::m_i")
1709 .map_err(interp_err(InterpolationError::Spline))?;
1710 }
1711
1712 Ok(m)
1713}
1714
1715fn spline_eval(pts: &[&Point2D], m: &[Decimal], x: Decimal) -> Result<Point2D, InterpolationError> {
1718 let n = pts.len();
1719 let spline = InterpolationError::Spline;
1720 let at = |index: usize| -> Result<&Point2D, InterpolationError> {
1721 pts.get(index).copied().ok_or_else(|| {
1722 InterpolationError::Spline(format!(
1723 "point index {index} is out of bounds for a curve of {n} points"
1724 ))
1725 })
1726 };
1727
1728 let first_at_or_after = pts.partition_point(|p| p.x < x);
1733 let segment = shift_index(first_at_or_after.max(1), -1, spline)?;
1734 if segment >= shift_index(n, -1, spline)? {
1735 return Err(InterpolationError::Spline(
1736 "Could not find valid segment for interpolation".to_string(),
1737 ));
1738 }
1739
1740 let left = at(segment)?;
1742 let right = at(shift_index(segment, 1, spline)?)?;
1743 let h = d_sub(right.x, left.x, "Curve::spline_interpolate::h")
1744 .map_err(interp_err(InterpolationError::Spline))?;
1745 if h.is_zero() {
1746 return Err(InterpolationError::DegenerateInterval);
1747 }
1748 let dx = d_sub(right.x, x, "Curve::spline_interpolate::dx")
1749 .map_err(interp_err(InterpolationError::Spline))?;
1750 let dx1 = d_sub(x, left.x, "Curve::spline_interpolate::dx1")
1751 .map_err(interp_err(InterpolationError::Spline))?;
1752
1753 let m_left = band_at(m, segment, "m")?;
1754 let m_right = band_at(m, shift_index(segment, 1, spline)?, "m")?;
1755 let six_h = d_mul(dec!(6), h, "Curve::spline_interpolate::six_h")
1756 .map_err(interp_err(InterpolationError::Spline))?;
1757
1758 let left_cube = cube_scaled(m_left, dx, six_h, "Curve::spline_interpolate::left_cube")?;
1759 let right_cube = cube_scaled(m_right, dx1, six_h, "Curve::spline_interpolate::right_cube")?;
1760 let left_linear = linear_term(left.y, m_left, h, dx, "Curve::spline_interpolate::left")?;
1761 let right_linear = linear_term(right.y, m_right, h, dx1, "Curve::spline_interpolate::right")?;
1762
1763 let y = d_sum_iter(
1764 [left_cube, right_cube, left_linear, right_linear],
1765 "Curve::spline_interpolate::y",
1766 )
1767 .map_err(interp_err(InterpolationError::Spline))?;
1768
1769 Ok(Point2D::new(x, y))
1770}
1771
1772impl StatisticalCurve for Curve {
1773 fn get_x_values(&self) -> Vec<Decimal> {
1774 self.points.iter().map(|p| p.x).collect()
1775 }
1776}
1777
1778impl MetricsExtractor for Curve {
1788 fn compute_basic_metrics(&self) -> Result<BasicMetrics, MetricsError> {
1789 let y_values: Vec<Decimal> = self.points.iter().map(|p| p.y).collect();
1790
1791 if y_values.is_empty() {
1793 return Ok(BasicMetrics {
1794 mean: Decimal::ZERO,
1795 median: Decimal::ZERO,
1796 mode: Decimal::ZERO,
1797 std_dev: Decimal::ZERO,
1798 });
1799 }
1800
1801 let mean = mean_of(&y_values, "Curve::compute_basic_metrics::mean")
1803 .map_err(|e| MetricsError::BasicError(e.to_string()))?;
1804
1805 let mut sorted_values = y_values.clone();
1807 sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1808 let mid = sorted_values.len() / 2;
1809 let median = if sorted_values.len().is_multiple_of(2) {
1810 let below = mid.checked_sub(1).ok_or_else(|| {
1811 MetricsError::BasicError(
1812 "median: empty sample after the emptiness guard".to_string(),
1813 )
1814 })?;
1815 let lower = sample_at(&sorted_values, below, "median")?;
1816 let upper = sample_at(&sorted_values, mid, "median")?;
1817 let pair = d_add(lower, upper, "Curve::compute_basic_metrics::median_pair")
1818 .map_err(|e| MetricsError::BasicError(e.to_string()))?;
1819 d_div(pair, Decimal::TWO, "Curve::compute_basic_metrics::median")
1820 .map_err(|e| MetricsError::BasicError(e.to_string()))?
1821 } else {
1822 sample_at(&sorted_values, mid, "median")?
1823 };
1824
1825 let mode = {
1827 let mut freq_map = std::collections::HashMap::new();
1828 for &val in &y_values {
1829 let count = freq_map.entry(val).or_insert(0usize);
1830 *count = count.checked_add(1).ok_or_else(|| {
1831 MetricsError::BasicError("mode: frequency count overflowed".to_string())
1832 })?;
1833 }
1834 freq_map
1835 .into_iter()
1836 .max_by_key(|&(_, count)| count)
1837 .map(|(val, _)| val)
1838 .unwrap_or(Decimal::ZERO)
1839 };
1840
1841 let variance = variance_of(&y_values, mean, "Curve::compute_basic_metrics::variance")
1843 .map_err(|e| MetricsError::BasicError(e.to_string()))?;
1844 let std_dev = d_sqrt(variance, "Curve::compute_basic_metrics::std_dev")
1846 .map_err(|e| MetricsError::BasicError(e.to_string()))?;
1847
1848 Ok(BasicMetrics {
1849 mean,
1850 median,
1851 mode,
1852 std_dev,
1853 })
1854 }
1855
1856 fn compute_shape_metrics(&self) -> Result<ShapeMetrics, MetricsError> {
1857 let y_values: Vec<Decimal> = self.points.iter().map(|p| p.y).collect();
1858
1859 if y_values.len() < 2 {
1861 return Ok(ShapeMetrics {
1862 skewness: Decimal::ZERO,
1863 kurtosis: Decimal::ZERO,
1864 peaks: vec![],
1865 valleys: vec![],
1866 inflection_points: vec![],
1867 });
1868 }
1869
1870 let mean = mean_of(&y_values, "Curve::compute_shape_metrics::mean")
1872 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1873
1874 let centered_values: Vec<Decimal> = y_values
1876 .iter()
1877 .map(|&x| d_sub(x, mean, "Curve::compute_shape_metrics::centered"))
1878 .collect::<Result<Vec<_>, _>>()
1879 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1880
1881 let variance = variance_of(&y_values, mean, "Curve::compute_shape_metrics::variance")
1883 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1884 let std_dev = d_sqrt(variance, "Curve::compute_shape_metrics::std_dev")
1886 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1887 if std_dev.is_zero() || std_dev < dec!(1e-9) {
1888 return Err(MetricsError::ShapeError(format!(
1889 "standard deviation ({std_dev}) is too small to compute skewness/kurtosis; the curve is degenerate"
1890 )));
1891 }
1892
1893 let skewness = standardized_moment(
1895 ¢ered_values,
1896 std_dev,
1897 3,
1898 "Curve::compute_shape_metrics::skewness",
1899 )
1900 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1901
1902 let raw_kurtosis = standardized_moment(
1904 ¢ered_values,
1905 std_dev,
1906 4,
1907 "Curve::compute_shape_metrics::kurtosis",
1908 )
1909 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1910 let kurtosis = d_sub(
1911 raw_kurtosis,
1912 Decimal::from(3),
1913 "Curve::compute_shape_metrics::excess_kurtosis",
1914 )
1915 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1916
1917 let (peaks, valleys) = detect_peaks_and_valleys(&self.points, dec!(0.1), 2)
1919 .map_err(|e| MetricsError::ShapeError(e.to_string()))?;
1920
1921 Ok(ShapeMetrics {
1922 skewness,
1923 kurtosis,
1924 peaks,
1925 valleys,
1926 inflection_points: vec![],
1927 })
1928 }
1929
1930 fn compute_range_metrics(&self) -> Result<RangeMetrics, MetricsError> {
1931 if self.points.is_empty() {
1933 return Ok(RangeMetrics {
1934 min: Point2D::new(Decimal::ZERO, Decimal::ZERO),
1935 max: Point2D::new(Decimal::ZERO, Decimal::ZERO),
1936 range: Decimal::ZERO,
1937 quartiles: (Decimal::ZERO, Decimal::ZERO, Decimal::ZERO),
1938 interquartile_range: Decimal::ZERO,
1939 });
1940 }
1941
1942 let mut y_values: Vec<Decimal> = self.points.iter().map(|p| p.y).collect();
1943 y_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1944
1945 let len = y_values.len();
1946 let min_point = self
1947 .points
1948 .iter()
1949 .min_by(|a, b| a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
1950 .cloned()
1951 .ok_or_else(|| MetricsError::BasicError("empty curve in min_point".to_string()))?;
1952 let max_point = self
1953 .points
1954 .iter()
1955 .max_by(|a, b| a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
1956 .cloned()
1957 .ok_or_else(|| MetricsError::BasicError("empty curve in max_point".to_string()))?;
1958
1959 let range = d_sub(
1960 max_point.y,
1961 min_point.y,
1962 "Curve::compute_range_metrics::range",
1963 )
1964 .map_err(|e| MetricsError::RangeError(e.to_string()))?;
1965
1966 let q1 = sample_at(&y_values, len / 4, "first quartile")?;
1968 let q2 = sample_at(&y_values, len / 2, "median")?;
1969 let q3 = sample_at(&y_values, third_quartile_index(len)?, "third quartile")?;
1970
1971 let interquartile_range = d_sub(q3, q1, "Curve::compute_range_metrics::iqr")
1972 .map_err(|e| MetricsError::RangeError(e.to_string()))?;
1973
1974 Ok(RangeMetrics {
1975 min: min_point,
1976 max: max_point,
1977 range,
1978 quartiles: (q1, q2, q3),
1979 interquartile_range,
1980 })
1981 }
1982
1983 fn compute_trend_metrics(&self) -> Result<TrendMetrics, MetricsError> {
1984 let points: Vec<Point2D> = self.points.clone().into_iter().collect();
1985
1986 if points.len() < 2 {
1988 return Ok(TrendMetrics {
1989 slope: Decimal::ZERO,
1990 intercept: Decimal::ZERO,
1991 r_squared: Decimal::ZERO,
1992 moving_average: vec![],
1993 });
1994 }
1995
1996 let n = Decimal::from(points.len());
1998 let x_vals: Vec<Decimal> = points.iter().map(|p| p.x).collect();
1999 let y_vals: Vec<Decimal> = points.iter().map(|p| p.y).collect();
2000
2001 let trend = || -> Result<(Decimal, Decimal, Decimal), DecimalError> {
2002 let op = "Curve::compute_trend_metrics";
2003 let sum_x = d_sum_iter(x_vals.iter().copied(), op)?;
2004 let sum_y = d_sum_iter(y_vals.iter().copied(), op)?;
2005 let mut sum_xy = Decimal::ZERO;
2006 let mut sum_xx = Decimal::ZERO;
2007 for (x, y) in x_vals.iter().zip(&y_vals) {
2008 sum_xy = d_add(sum_xy, d_mul(*x, *y, op)?, op)?;
2009 sum_xx = d_add(sum_xx, d_mul(*x, *x, op)?, op)?;
2010 }
2011
2012 let numerator = d_sub(d_mul(n, sum_xy, op)?, d_mul(sum_x, sum_y, op)?, op)?;
2013 let denominator = d_sub(d_mul(n, sum_xx, op)?, d_mul(sum_x, sum_x, op)?, op)?;
2014 let slope = d_div(numerator, denominator, op)?;
2015 let intercept = d_div(d_sub(sum_y, d_mul(slope, sum_x, op)?, op)?, n, op)?;
2016
2017 let mean_y = d_div(sum_y, n, op)?;
2019 let mut sst = Decimal::ZERO;
2020 for y in &y_vals {
2021 let centered = d_sub(*y, mean_y, op)?;
2022 sst = d_add(sst, powu_checked(centered, 2, op)?, op)?;
2023 }
2024
2025 let mut ssr = Decimal::ZERO;
2026 for (y, x) in y_vals.iter().zip(&x_vals) {
2027 let y_predicted = d_add(d_mul(slope, *x, op)?, intercept, op)?;
2028 let residual = d_sub(*y, y_predicted, op)?;
2029 ssr = d_add(ssr, powu_checked(residual, 2, op)?, op)?;
2030 }
2031
2032 let r_squared = if sst == Decimal::ZERO {
2033 Decimal::ONE
2034 } else {
2035 d_sub(Decimal::ONE, d_div(ssr, sst, op)?, op)?
2036 };
2037
2038 Ok((slope, intercept, r_squared))
2039 };
2040
2041 let (slope, intercept, r_squared) =
2046 trend().map_err(|e| MetricsError::TrendError(e.to_string()))?;
2047
2048 let window_sizes = [3, 5, 7];
2050 let mut moving_average: Vec<Point2D> = Vec::new();
2051 for window in window_sizes {
2052 if window > points.len() {
2053 continue;
2054 }
2055 for window_points in points.windows(window) {
2056 let xs: Vec<Decimal> = window_points.iter().map(|p| p.x).collect();
2057 let ys: Vec<Decimal> = window_points.iter().map(|p| p.y).collect();
2058 let avg_x = mean_of(&xs, "Curve::compute_trend_metrics::moving_average_x")
2059 .map_err(|e| MetricsError::TrendError(e.to_string()))?;
2060 let avg_y = mean_of(&ys, "Curve::compute_trend_metrics::moving_average_y")
2061 .map_err(|e| MetricsError::TrendError(e.to_string()))?;
2062 moving_average.push(Point2D::new(avg_x, avg_y));
2063 }
2064 }
2065
2066 Ok(TrendMetrics {
2067 slope,
2068 intercept,
2069 r_squared,
2070 moving_average,
2071 })
2072 }
2073
2074 fn compute_risk_metrics(&self) -> Result<RiskMetrics, MetricsError> {
2075 let y_values: Vec<Decimal> = self.points.iter().map(|p| p.y).collect();
2076
2077 if y_values.is_empty() {
2078 return Ok(RiskMetrics {
2079 volatility: Decimal::ZERO,
2080 value_at_risk: Decimal::ZERO,
2081 expected_shortfall: Decimal::ZERO,
2082 coefficient_of_variation: Decimal::ZERO,
2083 sharpe_ratio: Decimal::ZERO,
2084 });
2085 }
2086
2087 let op = "Curve::compute_risk_metrics";
2088 let mean = mean_of(&y_values, op).map_err(|e| MetricsError::RiskError(e.to_string()))?;
2089 let volatility = population_std_dev(&y_values, mean, op)
2095 .map_err(|e| MetricsError::RiskError(e.to_string()))?;
2096
2097 let z_score = dec!(1.645);
2102 let scaled_vol =
2103 d_mul(z_score, volatility, op).map_err(|e| MetricsError::RiskError(e.to_string()))?;
2104 let var =
2105 d_sub(mean, scaled_vol, op).map_err(|e| MetricsError::RiskError(e.to_string()))?;
2106
2107 let tail: Vec<Decimal> = y_values.iter().copied().filter(|&x| x < var).collect();
2108 let expected_shortfall = if tail.is_empty() {
2109 Decimal::ZERO
2110 } else {
2111 mean_of(&tail, op).map_err(|e| MetricsError::RiskError(e.to_string()))?
2112 };
2113
2114 let coefficient_of_variation = coefficient_of_variation(volatility, mean, op)?;
2117
2118 let sharpe_ratio = if volatility.is_zero() {
2123 Decimal::ZERO
2124 } else {
2125 d_div(mean, volatility, op).map_err(|e| MetricsError::RiskError(e.to_string()))?
2126 };
2127
2128 Ok(RiskMetrics {
2129 volatility,
2130 value_at_risk: var,
2131 expected_shortfall,
2132 coefficient_of_variation,
2133 sharpe_ratio,
2134 })
2135 }
2136}
2137
2138impl Arithmetic<Curve> for Curve {
2142 type Error = CurveError;
2143
2144 fn merge(curves: &[&Curve], operation: MergeOperation) -> Result<Curve, CurveError> {
2244 if curves.is_empty() {
2245 return Err(CurveError::invalid_parameters(
2246 "merge_curves",
2247 "No curves provided for merging",
2248 ));
2249 }
2250
2251 if let [only] = curves {
2253 return Ok((*only).clone());
2254 }
2255
2256 let min_x = curves
2258 .iter()
2259 .map(|c| c.x_range.0)
2260 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
2261 .unwrap_or(Decimal::ZERO);
2262
2263 let max_x = curves
2264 .iter()
2265 .map(|c| c.x_range.1)
2266 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
2267 .unwrap_or(Decimal::ZERO);
2268
2269 if min_x >= max_x {
2271 return Err(CurveError::invalid_parameters(
2272 "merge_curves",
2273 "Curves have incompatible x-ranges",
2274 ));
2275 }
2276
2277 let steps = 100; let op = "Curve::merge";
2280 let span = d_sub(max_x, min_x, op).map_err(construction_err)?;
2281 let step_size = d_div(span, Decimal::from(steps), op).map_err(construction_err)?;
2282
2283 let result_points: Result<Vec<Point2D>, CurveError> = (0..=steps)
2285 .into_par_iter()
2286 .map(|i| {
2287 let offset = d_mul(step_size, Decimal::from(i), op).map_err(construction_err)?;
2288 let x = d_add(min_x, offset, op)
2293 .map_err(construction_err)?
2294 .min(max_x);
2295
2296 let y_values: Result<Vec<Decimal>, CurveError> = curves
2298 .iter()
2299 .map(|curve| {
2300 curve
2301 .interpolate(x, InterpolationType::Cubic)
2302 .map(|point| point.y)
2303 .map_err(CurveError::from)
2304 })
2305 .collect();
2306
2307 let y_values = y_values?;
2308
2309 let result_y: Decimal = match operation {
2311 MergeOperation::Add => {
2312 d_sum_iter(y_values.iter().copied(), op).map_err(construction_err)?
2313 }
2314 MergeOperation::Subtract => {
2315 let signed = y_values
2316 .iter()
2317 .enumerate()
2318 .map(|(i, &val)| if i == 0 { val } else { -val });
2319 d_sum_iter(signed, op).map_err(construction_err)?
2320 }
2321 MergeOperation::Multiply => {
2322 d_product_iter(y_values.iter().copied(), op).map_err(construction_err)?
2323 }
2324 MergeOperation::Divide => {
2325 y_values.iter().copied().enumerate().try_fold(
2335 Decimal::ONE,
2336 |acc, (i, val)| {
2337 let factor = if i == 0 {
2341 val
2342 } else if val == Decimal::ZERO {
2343 Decimal::MAX
2344 } else {
2345 d_div(Decimal::ONE, val, op).map_err(construction_err)?
2346 };
2347 d_mul(acc, factor, op).map_err(construction_err)
2348 },
2349 )?
2350 }
2351 MergeOperation::Max => y_values
2352 .par_iter()
2353 .cloned()
2354 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
2355 .unwrap_or(Decimal::ZERO),
2356 MergeOperation::Min => y_values
2357 .par_iter()
2358 .cloned()
2359 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
2360 .unwrap_or(Decimal::ZERO),
2361 };
2362
2363 Ok(Point2D::new(x, result_y))
2364 })
2365 .collect();
2366
2367 let result_points = result_points?;
2369
2370 Ok(Curve::from_vector(result_points))
2371 }
2372
2373 fn merge_with(&self, other: &Curve, operation: MergeOperation) -> Result<Curve, CurveError> {
2404 Self::merge(&[self, other], operation)
2405 }
2406}
2407
2408impl AxisOperations<Point2D, Decimal> for Curve {
2409 type Error = CurveError;
2410
2411 fn contains_point(&self, x: &Decimal) -> bool {
2417 self.points_at(*x).next().is_some()
2418 }
2419
2420 fn get_index_values(&self) -> Vec<Decimal> {
2426 self.points.iter().map(|p| p.x).collect()
2427 }
2428
2429 fn get_values(&self, x: Decimal) -> Vec<&Decimal> {
2436 self.points_at(x).map(|p| &p.y).collect()
2437 }
2438
2439 fn get_closest_point(&self, x: &Decimal) -> Result<&Point2D, Self::Error> {
2440 let mut closest: Option<(&Point2D, Decimal)> = None;
2444 for point in &self.points {
2445 let distance = d_sub(point.x, *x, "Curve::get_closest_point")
2446 .map_err(analysis_err)?
2447 .abs();
2448 match closest {
2451 Some((_, best)) if best <= distance => {}
2452 _ => closest = Some((point, distance)),
2453 }
2454 }
2455
2456 closest
2457 .map(|(point, _)| point)
2458 .ok_or(CurveError::Point2DError {
2459 reason: "No points available",
2460 })
2461 }
2462
2463 fn get_point(&self, x: &Decimal) -> Option<&Point2D> {
2470 self.points_at(*x).next()
2471 }
2472}
2473
2474fn intersect_all_pairs(
2479 curve: &Curve,
2480 other: &Curve,
2481 epsilon: Decimal,
2482) -> Result<Vec<Point2D>, CurveError> {
2483 let mut intersections = Vec::new();
2484 for p1 in &curve.points {
2485 for p2 in &other.points {
2486 let dx = d_sub(p1.x, p2.x, "Curve::intersect_with::dx")
2488 .map_err(analysis_err)?
2489 .abs();
2490 if dx >= epsilon {
2491 continue;
2492 }
2493 let dy = d_sub(p1.y, p2.y, "Curve::intersect_with::dy")
2494 .map_err(analysis_err)?
2495 .abs();
2496 if dy < epsilon {
2497 intersections.push(*p1);
2498 }
2499 }
2500 }
2501 Ok(intersections)
2502}
2503
2504fn resample<'a>(
2508 curve: &'a Curve,
2509 spline: &mut Option<CurveSpline<'a>>,
2510 x: Decimal,
2511 interpolation: InterpolationType,
2512) -> Result<Point2D, InterpolationError> {
2513 if interpolation == InterpolationType::Spline {
2514 spline
2515 .get_or_insert_with(|| CurveSpline::new(curve))
2516 .interpolate(x)
2517 } else {
2518 curve.interpolate(x, interpolation)
2519 }
2520}
2521
2522impl MergeAxisInterpolate<Point2D, Decimal> for Curve
2523where
2524 Self: Sized,
2525{
2526 fn merge_axis_interpolate(
2535 &self,
2536 other: &Self,
2537 interpolation: InterpolationType,
2538 ) -> Result<(Self, Self), Self::Error> {
2539 let merged_x_values = self.merge_axis_index(other);
2541
2542 let mut sorted_x_values: Vec<Decimal> = merged_x_values.into_iter().collect();
2544 sorted_x_values.sort();
2545
2546 let mut interpolated_self_points = BTreeSet::new();
2547 let mut interpolated_other_points = BTreeSet::new();
2548
2549 let mut self_spline: Option<CurveSpline<'_>> = None;
2552 let mut other_spline: Option<CurveSpline<'_>> = None;
2553
2554 for x in &sorted_x_values {
2555 if self.contains_point(x) {
2556 let pt = self.get_point(x).ok_or_else(|| {
2557 CurveError::InterpolationError(format!(
2558 "missing self point at x={x} despite contains_point()"
2559 ))
2560 })?;
2561 interpolated_self_points.insert(*pt);
2562 } else {
2563 let interpolated_point = resample(self, &mut self_spline, *x, interpolation)?;
2564 interpolated_self_points.insert(interpolated_point);
2565 }
2566 if other.contains_point(x) {
2567 let pt = other.get_point(x).ok_or_else(|| {
2568 CurveError::InterpolationError(format!(
2569 "missing other point at x={x} despite contains_point()"
2570 ))
2571 })?;
2572 interpolated_other_points.insert(*pt);
2573 } else {
2574 let interpolated_point = resample(other, &mut other_spline, *x, interpolation)?;
2575 interpolated_other_points.insert(interpolated_point);
2576 }
2577 }
2578 Ok((
2579 Curve::new(interpolated_self_points),
2580 Curve::new(interpolated_other_points),
2581 ))
2582 }
2583}
2584
2585impl GeometricTransformations<Point2D> for Curve {
2586 type Error = CurveError;
2587
2588 fn translate(&self, deltas: Vec<&Decimal>) -> Result<Self, Self::Error> {
2589 if deltas.len() != 2 {
2590 return Err(CurveError::invalid_parameters(
2591 "translate",
2592 "Expected 2 deltas for 2D translation",
2593 ));
2594 }
2595
2596 let (Some(dx), Some(dy)) = (deltas.first(), deltas.get(1)) else {
2597 return Err(CurveError::invalid_parameters(
2598 "translate",
2599 "Expected 2 deltas for 2D translation",
2600 ));
2601 };
2602
2603 let translated_points = self
2604 .points
2605 .iter()
2606 .map(|point| {
2607 let x = d_add(point.x, **dx, "Curve::translate::x")?;
2608 let y = d_add(point.y, **dy, "Curve::translate::y")?;
2609 Ok(Point2D::new(x, y))
2610 })
2611 .collect::<Result<BTreeSet<Point2D>, DecimalError>>()
2612 .map_err(construction_err)?;
2613
2614 Ok(Curve::new(translated_points))
2615 }
2616
2617 fn scale(&self, factors: Vec<&Decimal>) -> Result<Self, Self::Error> {
2618 if factors.len() != 2 {
2619 return Err(CurveError::invalid_parameters(
2620 "scale",
2621 "Expected 2 factors for 2D scaling",
2622 ));
2623 }
2624
2625 let (Some(fx), Some(fy)) = (factors.first(), factors.get(1)) else {
2626 return Err(CurveError::invalid_parameters(
2627 "scale",
2628 "Expected 2 factors for 2D scaling",
2629 ));
2630 };
2631
2632 let scaled_points = self
2633 .points
2634 .iter()
2635 .map(|point| {
2636 let x = d_mul(point.x, **fx, "Curve::scale::x")?;
2637 let y = d_mul(point.y, **fy, "Curve::scale::y")?;
2638 Ok(Point2D::new(x, y))
2639 })
2640 .collect::<Result<BTreeSet<Point2D>, DecimalError>>()
2641 .map_err(construction_err)?;
2642
2643 Ok(Curve::new(scaled_points))
2644 }
2645
2646 fn intersect_with(&self, other: &Self) -> Result<Vec<Point2D>, Self::Error> {
2682 let epsilon = Decimal::new(1, 6);
2683 let fits = match (
2684 self.points.first(),
2685 self.points.last(),
2686 other.points.first(),
2687 other.points.last(),
2688 ) {
2689 (Some(first), Some(last), Some(other_first), Some(other_last)) => {
2691 d_sub(first.x, other_last.x, "Curve::intersect_with::dx").is_ok()
2692 && d_sub(last.x, other_first.x, "Curve::intersect_with::dx").is_ok()
2693 }
2694 _ => true,
2696 };
2697 if !fits {
2698 return intersect_all_pairs(self, other, epsilon);
2699 }
2700
2701 let mut intersections = Vec::new();
2702 let mut window = other.points.iter();
2706 for p1 in &self.points {
2707 while let Some(p2) = window.clone().next() {
2708 let dx = d_sub(p1.x, p2.x, "Curve::intersect_with::dx").map_err(analysis_err)?;
2709 if dx < epsilon {
2710 break;
2711 }
2712 window.next();
2713 }
2714 for p2 in window.clone() {
2715 let dx = d_sub(p1.x, p2.x, "Curve::intersect_with::dx")
2716 .map_err(analysis_err)?
2717 .abs();
2718 if dx >= epsilon {
2721 break;
2722 }
2723 let dy = d_sub(p1.y, p2.y, "Curve::intersect_with::dy")
2724 .map_err(analysis_err)?
2725 .abs();
2726 if dy < epsilon {
2727 intersections.push(*p1);
2728 }
2729 }
2730 }
2731
2732 Ok(intersections)
2733 }
2734
2735 fn derivative_at(&self, point: &Point2D) -> Result<Vec<Decimal>, Self::Error> {
2736 let (p0, p1) = self.bracket(point.x)?;
2737
2738 let op = "Curve::derivative_at";
2739 let rise = d_sub(p1.y, p0.y, op).map_err(analysis_err)?;
2740 let x1_sq = d_mul(p1.x, p1.x, op).map_err(analysis_err)?;
2741 let x0_sq = d_mul(p0.x, p0.x, op).map_err(analysis_err)?;
2742 let run = d_sub(x1_sq, x0_sq, op).map_err(analysis_err)?;
2743 if run.is_zero() {
2744 return Err(CurveError::AnalysisError(
2748 "derivative_at: bracketing abscissas have equal squares".to_string(),
2749 ));
2750 }
2751
2752 let a = d_div(rise, run, op).map_err(analysis_err)?;
2753 let slope = d_mul(dec!(2.0), a, op).map_err(analysis_err)?;
2754 let derivative = d_mul(slope, point.x, op).map_err(analysis_err)?;
2755
2756 Ok(vec![derivative])
2757 }
2758
2759 fn extrema(&self) -> Result<(Point2D, Point2D), Self::Error> {
2760 if self.points.is_empty() {
2761 return Err(CurveError::invalid_parameters(
2762 "extrema",
2763 "Curve has no points",
2764 ));
2765 }
2766
2767 let min_point = self
2768 .points
2769 .iter()
2770 .min_by(|a, b| a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
2771 .cloned()
2772 .ok_or_else(|| {
2773 CurveError::AnalysisError("extrema: empty curve in min_by".to_string())
2774 })?;
2775
2776 let max_point = self
2777 .points
2778 .iter()
2779 .max_by(|a, b| a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
2780 .cloned()
2781 .ok_or_else(|| {
2782 CurveError::AnalysisError("extrema: empty curve in max_by".to_string())
2783 })?;
2784
2785 Ok((min_point, max_point))
2786 }
2787
2788 fn measure_under(&self, base_value: &Decimal) -> Result<Decimal, Self::Error> {
2789 if self.points.len() < 2 {
2790 return Ok(Decimal::ZERO);
2791 }
2792
2793 let mut area = Decimal::ZERO;
2794 let points: Vec<_> = self.points.iter().collect();
2795
2796 let op = "Curve::measure_under";
2797
2798 for pair in points.windows(2) {
2800 let (Some(left), Some(right)) = (pair.first(), pair.get(1)) else {
2801 return Err(CurveError::AnalysisError(
2802 "measure_under: trapezoid window is shorter than two points".to_string(),
2803 ));
2804 };
2805 let width = d_sub(right.x, left.x, op).map_err(analysis_err)?;
2806 let left_height = d_sub(left.y, *base_value, op).map_err(analysis_err)?;
2807 let right_height = d_sub(right.y, *base_value, op).map_err(analysis_err)?;
2808 let sum_heights = d_add(left_height, right_height, op).map_err(analysis_err)?;
2809 let height = d_div(sum_heights, Decimal::TWO, op).map_err(analysis_err)?;
2810 let slice = d_mul(width, height, op).map_err(analysis_err)?;
2811 area = d_add(area, slice, op).map_err(analysis_err)?;
2812 }
2813
2814 Ok(area.abs())
2815 }
2816}
2817
2818#[cfg(test)]
2819mod tests_curves {
2820 use super::*;
2821
2822 use crate::curves::utils::{create_constant_curve, create_linear_curve};
2823 use Decimal;
2824 use optionstratlib_core::{model::Positive, pos_or_panic};
2825 use rust_decimal_macros::dec;
2826
2827 #[test]
2828 fn test_new_with_decimal() {
2829 let x = dec!(1.5);
2830 let y = dec!(2.5);
2831 let point = Point2D::new(x, y);
2832 assert_eq!(point.x, dec!(1.5));
2833 assert_eq!(point.y, dec!(2.5));
2834 }
2835
2836 #[test]
2837 fn test_new_with_positive() {
2838 let x = pos_or_panic!(1.5_f64);
2839 let y = pos_or_panic!(2.5_f64);
2840 let point = Point2D::new(x, y);
2841 assert_eq!(point.x, dec!(1.5));
2842 assert_eq!(point.y, dec!(2.5));
2843 }
2844
2845 #[test]
2846 fn test_to_tuple_with_decimal() {
2847 let point = Point2D::new(dec!(1.5), dec!(2.5));
2848 let tuple: (Decimal, Decimal) = point.to_tuple().unwrap();
2849 assert_eq!(tuple, (dec!(1.5), dec!(2.5)));
2850 }
2851
2852 #[test]
2853 fn test_to_tuple_with_positive() {
2854 let point = Point2D::new(dec!(1.5), dec!(2.5));
2855 let tuple: (Positive, Positive) = point.to_tuple().unwrap();
2856 assert_eq!(tuple, (pos_or_panic!(1.5), pos_or_panic!(2.5)));
2857 }
2858
2859 #[test]
2860 fn test_from_tuple_with_decimal() {
2861 let x = dec!(1.5);
2862 let y = dec!(2.5);
2863 let point = Point2D::from_tuple(x, y).unwrap();
2864 assert_eq!(point, Point2D::new(dec!(1.5), dec!(2.5)));
2865 }
2866
2867 #[test]
2868 fn test_from_tuple_with_positive() {
2869 let x = pos_or_panic!(1.5_f64);
2870 let y = pos_or_panic!(2.5_f64);
2871 let point = Point2D::from_tuple(x, y).unwrap();
2872 assert_eq!(point, Point2D::new(dec!(1.5), dec!(2.5)));
2873 }
2874
2875 #[test]
2876 fn test_new_with_mixed_types() {
2877 let x = dec!(1.5);
2878 let y = pos_or_panic!(2.5_f64);
2879 let point = Point2D::new(x, y);
2880 assert_eq!(point.x, dec!(1.5));
2881 assert_eq!(point.y, dec!(2.5));
2882 }
2883
2884 #[test]
2885 fn test_create_constant_curve() {
2886 let curve = create_constant_curve(dec!(1.0), dec!(2.0), dec!(5.0)).unwrap();
2887 assert_eq!(curve.get_points().len(), 11);
2888 for point in curve.get_points() {
2889 assert_eq!(point.y, dec!(5.0));
2890 }
2891 }
2892
2893 #[test]
2894 fn test_create_linear_curve() {
2895 let curve = create_linear_curve(dec!(1.0), dec!(2.0), dec!(2.0)).unwrap();
2896 assert_eq!(curve.get_points().len(), 11);
2897 let mut slope = dec!(2.0);
2898 for point in curve.get_points() {
2899 assert_eq!(point.y, slope);
2900 slope += dec!(0.2);
2901 }
2902 }
2903}
2904
2905#[cfg(test)]
2906mod tests_linear_interpolate {
2907 use super::*;
2908 use crate::geometrics::InterpolationType;
2909 use rust_decimal_macros::dec;
2910
2911 #[test]
2912 fn test_linear_interpolation_exact_points() {
2913 let curve = Curve::new(BTreeSet::from_iter(vec![
2914 Point2D::new(Decimal::ZERO, Decimal::ZERO),
2915 Point2D::new(Decimal::ONE, Decimal::TWO),
2916 ]));
2917
2918 let p0 = curve
2920 .interpolate(Decimal::ZERO, InterpolationType::Linear)
2921 .unwrap();
2922 assert_eq!(p0.x, Decimal::ZERO);
2923 assert_eq!(p0.y, Decimal::ZERO);
2924
2925 let p1 = curve
2926 .interpolate(Decimal::ONE, InterpolationType::Linear)
2927 .unwrap();
2928 assert_eq!(p1.x, Decimal::ONE);
2929 assert_eq!(p1.y, Decimal::TWO);
2930 }
2931
2932 #[test]
2933 fn test_linear_interpolation_midpoint() {
2934 let curve = Curve::new(BTreeSet::from_iter(vec![
2935 Point2D::new(Decimal::ZERO, Decimal::ZERO),
2936 Point2D::new(Decimal::ONE, Decimal::TWO),
2937 ]));
2938
2939 let mid = curve
2941 .interpolate(dec!(0.5), InterpolationType::Linear)
2942 .unwrap();
2943 assert_eq!(mid.x, dec!(0.5));
2944 assert_eq!(mid.y, dec!(1.0));
2945 }
2946
2947 #[test]
2948 fn test_linear_interpolation_quarter_points() {
2949 let curve = Curve::new(BTreeSet::from_iter(vec![
2950 Point2D::new(Decimal::ZERO, Decimal::ZERO),
2951 Point2D::new(Decimal::ONE, Decimal::TWO),
2952 ]));
2953
2954 let p25 = curve
2956 .interpolate(dec!(0.25), InterpolationType::Linear)
2957 .unwrap();
2958 assert_eq!(p25.x, dec!(0.25));
2959 assert_eq!(p25.y, dec!(0.5));
2960
2961 let p75 = curve
2963 .interpolate(dec!(0.75), InterpolationType::Linear)
2964 .unwrap();
2965 assert_eq!(p75.x, dec!(0.75));
2966 assert_eq!(p75.y, dec!(1.5));
2967 }
2968
2969 #[test]
2970 fn test_linear_interpolation_out_of_range() {
2971 let curve = Curve::new(BTreeSet::from_iter(vec![
2972 Point2D::new(dec!(0.0), dec!(0.0)),
2973 Point2D::new(dec!(1.0), dec!(2.0)),
2974 ]));
2975
2976 assert!(
2977 curve
2978 .interpolate(dec!(-0.1), InterpolationType::Linear)
2979 .is_err()
2980 );
2981 assert!(
2982 curve
2983 .interpolate(dec!(1.1), InterpolationType::Linear)
2984 .is_err()
2985 );
2986 }
2987
2988 #[test]
2989 fn test_linear_interpolation_insufficient_points() {
2990 let curve = Curve::new(BTreeSet::from_iter(vec![Point2D::new(
2991 dec!(0.0),
2992 dec!(0.0),
2993 )]));
2994
2995 assert!(
2996 curve
2997 .interpolate(dec!(0.5), InterpolationType::Linear)
2998 .is_err()
2999 );
3000 }
3001
3002 #[test]
3003 fn test_linear_interpolation_non_monotonic() {
3004 let curve = Curve::new(BTreeSet::from_iter(vec![
3005 Point2D::new(dec!(0.0), dec!(0.0)),
3006 Point2D::new(dec!(1.0), dec!(2.0)),
3007 Point2D::new(dec!(2.0), dec!(1.0)),
3008 ]));
3009
3010 let p15 = curve
3011 .interpolate(dec!(1.5), InterpolationType::Linear)
3012 .unwrap();
3013 assert_eq!(p15.x, dec!(1.5));
3014 assert_eq!(p15.y, dec!(1.5));
3015 }
3016}
3017
3018#[cfg(test)]
3019mod tests_bilinear_interpolate {
3020 use super::*;
3021 use crate::geometrics::InterpolationType;
3022 use rust_decimal_macros::dec;
3023
3024 #[test]
3025 fn test_bilinear_interpolation() {
3026 let curve = Curve::new(BTreeSet::from_iter(vec![
3027 Point2D::new(Decimal::ZERO, Decimal::ZERO),
3028 Point2D::new(Decimal::TWO, Decimal::ONE),
3029 Point2D::new(Decimal::TEN, Decimal::ONE),
3030 Point2D::new(Decimal::ONE, Decimal::TWO),
3031 ]));
3032
3033 let corner = curve
3035 .interpolate(Decimal::ZERO, InterpolationType::Bilinear)
3036 .unwrap();
3037 assert_eq!(corner.x, Decimal::ZERO);
3038 assert_eq!(corner.y, Decimal::ZERO);
3039
3040 let mid = curve
3042 .interpolate(dec!(0.5), InterpolationType::Bilinear)
3043 .unwrap();
3044 assert_eq!(mid.x, dec!(0.5));
3045 assert_eq!(mid.y, dec!(1.0));
3046 }
3047
3048 #[test]
3049 fn test_bilinear_interpolation_out_of_range() {
3050 let curve = Curve::new(BTreeSet::from_iter(vec![
3051 Point2D::new(dec!(0.0), dec!(0.0)),
3052 Point2D::new(dec!(1.0), dec!(1.0)),
3053 Point2D::new(dec!(0.0), dec!(1.0)),
3054 Point2D::new(dec!(1.0), dec!(2.0)),
3055 ]));
3056
3057 assert!(
3058 curve
3059 .interpolate(dec!(-0.5), InterpolationType::Bilinear)
3060 .is_err()
3061 );
3062 assert!(
3063 curve
3064 .interpolate(dec!(1.5), InterpolationType::Bilinear)
3065 .is_err()
3066 );
3067 }
3068
3069 #[test]
3070 fn test_bilinear_interpolation_insufficient_points() {
3071 let curve = Curve::new(BTreeSet::from_iter(vec![
3072 Point2D::new(dec!(0.0), dec!(0.0)),
3073 Point2D::new(dec!(1.0), dec!(1.0)),
3074 Point2D::new(dec!(2.0), dec!(2.0)),
3075 ]));
3076
3077 assert!(
3078 curve
3079 .interpolate(dec!(0.5), InterpolationType::Bilinear)
3080 .is_err()
3081 );
3082 }
3083
3084 #[test]
3085 fn test_bilinear_interpolation_quarter_points() {
3086 let curve = Curve::new(BTreeSet::from_iter(vec![
3087 Point2D::new(Decimal::ZERO, Decimal::ZERO), Point2D::new(Decimal::ONE, Decimal::ONE), Point2D::new(Decimal::TWO, Decimal::ONE), Point2D::new(Decimal::TEN, Decimal::TWO), ]));
3092
3093 let p25 = curve
3098 .interpolate(dec!(0.25), InterpolationType::Bilinear)
3099 .unwrap();
3100 assert_eq!(p25.x, dec!(0.25));
3101 assert_eq!(p25.y, dec!(0.75));
3102
3103 let p75 = curve
3108 .interpolate(dec!(0.75), InterpolationType::Bilinear)
3109 .unwrap();
3110 assert_eq!(p75.x, dec!(0.75));
3111 assert_eq!(p75.y, dec!(1.25));
3112 }
3113
3114 #[test]
3115 fn test_bilinear_interpolation_boundaries() {
3116 let curve = Curve::new(BTreeSet::from_iter(vec![
3117 Point2D::new(dec!(0.0), dec!(0.0)),
3118 Point2D::new(dec!(1.0), dec!(1.0)),
3119 Point2D::new(dec!(0.0), dec!(1.0)),
3120 Point2D::new(dec!(1.0), dec!(2.0)),
3121 ]));
3122
3123 assert!(
3124 curve
3125 .interpolate(dec!(-0.1), InterpolationType::Bilinear)
3126 .is_err()
3127 );
3128 assert!(
3129 curve
3130 .interpolate(dec!(1.1), InterpolationType::Bilinear)
3131 .is_err()
3132 );
3133 }
3134
3135 #[test]
3136 fn test_out_of_range() {
3137 let curve = Curve::new(BTreeSet::from_iter(vec![
3138 Point2D::new(Decimal::ZERO, Decimal::ZERO),
3139 Point2D::new(Decimal::ONE, Decimal::ONE),
3140 Point2D::new(Decimal::ZERO, Decimal::ONE),
3141 Point2D::new(Decimal::ONE, Decimal::TWO),
3142 ]));
3143
3144 assert!(
3145 curve
3146 .interpolate(dec!(-1), InterpolationType::Bilinear)
3147 .is_err()
3148 );
3149 assert!(
3150 curve
3151 .interpolate(Decimal::TWO, InterpolationType::Bilinear)
3152 .is_err()
3153 );
3154 }
3155
3156 fn quadratic_four() -> Curve {
3161 Curve::new(BTreeSet::from_iter(vec![
3162 Point2D::new(dec!(0), dec!(0)),
3163 Point2D::new(dec!(1), dec!(1)),
3164 Point2D::new(dec!(2), dec!(4)),
3165 Point2D::new(dec!(3), dec!(9)),
3166 ]))
3167 }
3168
3169 fn quadratic_five() -> Curve {
3170 Curve::new(BTreeSet::from_iter(vec![
3171 Point2D::new(dec!(0), dec!(0)),
3172 Point2D::new(dec!(1), dec!(1)),
3173 Point2D::new(dec!(2), dec!(4)),
3174 Point2D::new(dec!(3), dec!(9)),
3175 Point2D::new(dec!(4), dec!(16)),
3176 ]))
3177 }
3178
3179 fn irregular() -> Curve {
3180 Curve::new(BTreeSet::from_iter(vec![
3181 Point2D::new(dec!(0), dec!(1)),
3182 Point2D::new(dec!(0.5), dec!(2.25)),
3183 Point2D::new(dec!(2), dec!(-1.5)),
3184 Point2D::new(dec!(3.25), dec!(4)),
3185 Point2D::new(dec!(5), dec!(0.75)),
3186 ]))
3187 }
3188
3189 #[test]
3199 fn test_bilinear_interpolate_four_point_upper_segments_match_reference() {
3200 let curve = quadratic_four();
3201
3202 let mid = curve.bilinear_interpolate(dec!(1.5)).unwrap();
3203 assert_eq!(mid.x, dec!(1.5));
3204 assert_eq!(mid.y, dec!(4.5));
3205
3206 let last = curve.bilinear_interpolate(dec!(2.5)).unwrap();
3207 assert_eq!(last.x, dec!(2.5));
3208 assert_eq!(last.y, dec!(6.5));
3209 }
3210
3211 #[test]
3216 fn test_bilinear_interpolate_five_point_upper_segments_match_reference() {
3217 let curve = quadratic_five();
3218
3219 let second_to_last = curve.bilinear_interpolate(dec!(2.5)).unwrap();
3220 assert_eq!(second_to_last.y, dec!(9.5));
3221
3222 let last = curve.bilinear_interpolate(dec!(3.5)).unwrap();
3223 assert_eq!(last.y, dec!(12.5));
3224 }
3225
3226 #[test]
3230 fn test_bilinear_interpolate_interior_is_unchanged_by_the_clamp() {
3231 let four = quadratic_four();
3232 assert_eq!(four.bilinear_interpolate(dec!(0.5)).unwrap().y, dec!(3.5));
3233
3234 let five = quadratic_five();
3235 assert_eq!(five.bilinear_interpolate(dec!(0.5)).unwrap().y, dec!(3.5));
3236 assert_eq!(five.bilinear_interpolate(dec!(1.5)).unwrap().y, dec!(7.5));
3237 }
3238
3239 #[test]
3242 fn test_bilinear_interpolate_final_segment_equals_linear() {
3243 for (curve, x) in [(quadratic_four(), dec!(2.5)), (quadratic_five(), dec!(3.5))] {
3244 let bilinear = curve.bilinear_interpolate(x).unwrap();
3245 let linear = curve.linear_interpolate(x).unwrap();
3246 assert_eq!(bilinear.y, linear.y);
3247 }
3248 }
3249
3250 #[test]
3256 fn test_bilinear_interpolate_final_segment_equals_linear_to_the_last_place() {
3257 let curve = irregular();
3258 let bilinear = curve.bilinear_interpolate(dec!(4)).unwrap().y;
3259 let linear = curve.linear_interpolate(dec!(4)).unwrap().y;
3260
3261 assert_ne!(bilinear, linear);
3262 assert!((bilinear - linear).abs() < dec!(0.0000000000000000000000001));
3263 }
3264
3265 #[test]
3279 fn test_bilinear_interpolate_irregular_curve_matches_reference() {
3280 const TOLERANCE: Decimal = dec!(0.0000000000000000000000001);
3281 let curve = irregular();
3282
3283 assert_eq!(
3284 curve.bilinear_interpolate(dec!(0.25)).unwrap().y,
3285 dec!(1.4375)
3286 );
3287 assert_eq!(curve.bilinear_interpolate(dec!(2.5)).unwrap().y, dec!(1.7));
3288
3289 let recurring = curve.bilinear_interpolate(dec!(1)).unwrap().y;
3290 assert!((recurring - dec!(1.9583333333333333333333333333)).abs() < TOLERANCE);
3291
3292 let last = curve.bilinear_interpolate(dec!(4)).unwrap().y;
3293 assert!((last - dec!(2.6071428571428571428571428571)).abs() < TOLERANCE);
3294 }
3295
3296 #[test]
3299 fn test_bilinear_interpolate_sample_abscissa_returns_the_sample() {
3300 let curve = quadratic_four();
3301 for (x, y) in [
3302 (dec!(0), dec!(0)),
3303 (dec!(1), dec!(1)),
3304 (dec!(2), dec!(4)),
3305 (dec!(3), dec!(9)),
3306 ] {
3307 let point = curve.bilinear_interpolate(x).unwrap();
3308 assert_eq!(point.x, x);
3309 assert_eq!(point.y, y);
3310 }
3311 }
3312
3313 #[test]
3318 fn test_bilinear_interpolate_stacked_abscissa_is_degenerate() {
3319 let curve = Curve::new(BTreeSet::from_iter(vec![
3320 Point2D::new(dec!(0), dec!(0)),
3321 Point2D::new(dec!(1), dec!(1)),
3322 Point2D::new(dec!(1), dec!(7)),
3323 Point2D::new(dec!(2), dec!(4)),
3324 Point2D::new(dec!(3), dec!(9)),
3325 ]));
3326
3327 let err = curve.bilinear_interpolate(dec!(1)).unwrap_err();
3328 assert!(matches!(err, InterpolationError::DegenerateInterval));
3329 }
3330
3331 #[test]
3337 fn test_bilinear_interpolate_jump_at_a_sample_predates_the_clamp() {
3338 const EPSILON: Decimal = dec!(0.0000000001);
3339 const SLACK: Decimal = dec!(0.00000001);
3340 let curve = quadratic_five();
3341
3342 let from_left = curve.bilinear_interpolate(dec!(1) - EPSILON).unwrap().y;
3343 assert!((from_left - dec!(5)).abs() < SLACK);
3344
3345 let from_right = curve.bilinear_interpolate(dec!(1) + EPSILON).unwrap().y;
3346 assert!((from_right - dec!(5)).abs() < SLACK);
3347
3348 assert_eq!(curve.bilinear_interpolate(dec!(1)).unwrap().y, dec!(1));
3349 }
3350
3351 #[test]
3359 fn test_bilinear_interpolate_jumps_where_the_clamp_starts() {
3360 const EPSILON: Decimal = dec!(0.0000000001);
3361 const SLACK: Decimal = dec!(0.00000001);
3362 let curve = quadratic_five();
3363
3364 let from_left = curve.bilinear_interpolate(dec!(2) - EPSILON).unwrap().y;
3365 assert!((from_left - dec!(10)).abs() < SLACK);
3366
3367 let from_right = curve.bilinear_interpolate(dec!(2) + EPSILON).unwrap().y;
3368 assert!((from_right - dec!(6.5)).abs() < SLACK);
3369
3370 assert_eq!(curve.bilinear_interpolate(dec!(2)).unwrap().y, dec!(4));
3371 }
3372
3373 #[test]
3379 fn test_bilinear_interpolate_stays_within_the_ordinate_range() {
3380 for curve in [quadratic_four(), quadratic_five(), irregular()] {
3381 let ordinates: Vec<Decimal> = curve.points.iter().map(|p| p.y).collect();
3382 let low = ordinates.iter().copied().fold(Decimal::MAX, Decimal::min);
3383 let high = ordinates.iter().copied().fold(Decimal::MIN, Decimal::max);
3384
3385 let first = curve.points.iter().next().unwrap().x;
3386 let last = curve.points.iter().next_back().unwrap().x;
3387 let step = (last - first) / dec!(64);
3388
3389 let mut x = first;
3390 while x <= last {
3391 let y = curve.bilinear_interpolate(x).unwrap().y;
3392 assert!(y >= low, "{y} below the sample range at x = {x}");
3393 assert!(y <= high, "{y} above the sample range at x = {x}");
3394 x += step;
3395 }
3396 }
3397 }
3398}
3399
3400#[cfg(test)]
3401mod tests_cubic_interpolate {
3402 use super::*;
3403 use crate::geometrics::InterpolationType;
3404 use rust_decimal_macros::dec;
3405 use tracing::info;
3406
3407 #[test]
3408 fn test_cubic_interpolation_exact_points() {
3409 let curve = Curve::new(BTreeSet::from_iter(vec![
3410 Point2D::new(dec!(0.0), dec!(0.0)),
3411 Point2D::new(dec!(1.0), dec!(1.0)),
3412 Point2D::new(dec!(2.0), dec!(4.0)),
3413 Point2D::new(dec!(3.0), dec!(9.0)),
3414 ]));
3415
3416 let p1 = curve
3418 .interpolate(dec!(1.0), InterpolationType::Cubic)
3419 .unwrap();
3420 assert_eq!(p1.x, dec!(1.0));
3421 assert_eq!(p1.y, dec!(1.0));
3422 }
3423
3424 #[test]
3425 fn test_cubic_interpolation_midpoints() {
3426 let curve = Curve::new(BTreeSet::from_iter(vec![
3427 Point2D::new(dec!(0.0), dec!(0.0)),
3428 Point2D::new(dec!(1.0), dec!(1.0)),
3429 Point2D::new(dec!(2.0), dec!(4.0)),
3430 Point2D::new(dec!(3.0), dec!(9.0)),
3431 ]));
3432
3433 let mid = curve
3435 .interpolate(dec!(1.5), InterpolationType::Cubic)
3436 .unwrap();
3437 assert_eq!(mid.x, dec!(1.5));
3438 assert!(mid.y > dec!(1.0) && mid.y < dec!(4.0));
3440 }
3441
3442 #[test]
3443 fn test_cubic_interpolation_insufficient_points() {
3444 let curve = Curve::new(BTreeSet::from_iter(vec![
3445 Point2D::new(dec!(0.0), dec!(0.0)),
3446 Point2D::new(dec!(1.0), dec!(1.0)),
3447 Point2D::new(dec!(2.0), dec!(4.0)),
3448 ]));
3449
3450 assert!(
3451 curve
3452 .interpolate(dec!(1.5), InterpolationType::Cubic)
3453 .is_err()
3454 );
3455 }
3456
3457 #[test]
3458 fn test_cubic_interpolation_out_of_range() {
3459 let curve = Curve::new(BTreeSet::from_iter(vec![
3460 Point2D::new(dec!(0.0), dec!(0.0)),
3461 Point2D::new(dec!(1.0), dec!(1.0)),
3462 Point2D::new(dec!(2.0), dec!(4.0)),
3463 Point2D::new(dec!(3.0), dec!(9.0)),
3464 ]));
3465
3466 assert!(
3467 curve
3468 .interpolate(dec!(-0.5), InterpolationType::Cubic)
3469 .is_err()
3470 );
3471 assert!(
3472 curve
3473 .interpolate(dec!(3.5), InterpolationType::Cubic)
3474 .is_err()
3475 );
3476 }
3477
3478 #[test]
3479 fn test_cubic_interpolation_monotonicity() {
3480 let curve = Curve::new(BTreeSet::from_iter(vec![
3481 Point2D::new(dec!(0.0), dec!(0.0)),
3482 Point2D::new(dec!(1.0), dec!(1.0)),
3483 Point2D::new(dec!(2.0), dec!(4.0)),
3484 Point2D::new(dec!(3.0), dec!(9.0)),
3485 ]));
3486
3487 let p1 = curve
3488 .interpolate(dec!(0.5), InterpolationType::Cubic)
3489 .unwrap();
3490 let p2 = curve
3491 .interpolate(dec!(1.5), InterpolationType::Cubic)
3492 .unwrap();
3493 let p3 = curve
3494 .interpolate(dec!(2.5), InterpolationType::Cubic)
3495 .unwrap();
3496
3497 assert!(p1.y < p2.y);
3498 assert!(p2.y < p3.y);
3499 info!("p1: {:?}, p2: {:?}, p3: {:?}", p1, p2, p3);
3500 }
3501}
3502
3503#[cfg(test)]
3504mod tests_spline_interpolate {
3505 use super::*;
3506 use crate::geometrics::InterpolationType;
3507 use rust_decimal_macros::dec;
3508
3509 #[test]
3510 fn test_spline_interpolation_exact_points() {
3511 let curve = Curve::new(BTreeSet::from_iter(vec![
3512 Point2D::new(dec!(0.0), dec!(0.0)),
3513 Point2D::new(dec!(1.0), dec!(1.0)),
3514 Point2D::new(dec!(2.0), dec!(4.0)),
3515 Point2D::new(dec!(3.0), dec!(9.0)),
3516 ]));
3517
3518 let p1 = curve
3519 .interpolate(dec!(1.0), InterpolationType::Spline)
3520 .unwrap();
3521 assert_eq!(p1.x, dec!(1.0));
3522 assert_eq!(p1.y, dec!(1.0));
3523 }
3524
3525 #[test]
3526 fn test_spline_interpolation_midpoints() {
3527 let curve = Curve::new(BTreeSet::from_iter(vec![
3528 Point2D::new(dec!(0.0), dec!(0.0)),
3529 Point2D::new(dec!(1.0), dec!(1.0)),
3530 Point2D::new(dec!(2.0), dec!(4.0)),
3531 Point2D::new(dec!(3.0), dec!(9.0)),
3532 ]));
3533
3534 let mid = curve
3535 .interpolate(dec!(1.5), InterpolationType::Spline)
3536 .unwrap();
3537 assert_eq!(mid.x, dec!(1.5));
3538 assert!(mid.y > dec!(1.0) && mid.y < dec!(4.0));
3540 }
3541
3542 #[test]
3543 fn test_spline_interpolation_insufficient_points() {
3544 let curve = Curve::new(BTreeSet::from_iter(vec![
3545 Point2D::new(dec!(0.0), dec!(0.0)),
3546 Point2D::new(dec!(1.0), dec!(1.0)),
3547 ]));
3548
3549 assert!(
3550 curve
3551 .interpolate(dec!(0.5), InterpolationType::Spline)
3552 .is_err()
3553 );
3554 }
3555
3556 #[test]
3557 fn test_spline_interpolation_out_of_range() {
3558 let curve = Curve::new(BTreeSet::from_iter(vec![
3559 Point2D::new(dec!(0.0), dec!(0.0)),
3560 Point2D::new(dec!(1.0), dec!(1.0)),
3561 Point2D::new(dec!(2.0), dec!(4.0)),
3562 ]));
3563
3564 assert!(
3565 curve
3566 .interpolate(dec!(-0.5), InterpolationType::Spline)
3567 .is_err()
3568 );
3569 assert!(
3570 curve
3571 .interpolate(dec!(2.5), InterpolationType::Spline)
3572 .is_err()
3573 );
3574 }
3575
3576 #[test]
3577 fn test_spline_interpolation_smoothness() {
3578 let curve = Curve::new(BTreeSet::from_iter(vec![
3579 Point2D::new(dec!(0.0), dec!(0.0)),
3580 Point2D::new(dec!(1.0), dec!(1.0)),
3581 Point2D::new(dec!(2.0), dec!(4.0)),
3582 Point2D::new(dec!(3.0), dec!(9.0)),
3583 ]));
3584
3585 let p1 = curve
3587 .interpolate(dec!(1.48), InterpolationType::Spline)
3588 .unwrap();
3589 let p2 = curve
3590 .interpolate(dec!(1.49), InterpolationType::Spline)
3591 .unwrap();
3592 let p3 = curve
3593 .interpolate(dec!(1.50), InterpolationType::Spline)
3594 .unwrap();
3595 let p4 = curve
3596 .interpolate(dec!(1.51), InterpolationType::Spline)
3597 .unwrap();
3598 let p5 = curve
3599 .interpolate(dec!(1.52), InterpolationType::Spline)
3600 .unwrap();
3601
3602 assert!(p1.y < p2.y);
3604 assert!(p2.y < p3.y);
3605 assert!(p3.y < p4.y);
3606 assert!(p4.y < p5.y);
3607
3608 let d1 = p2.y - p1.y;
3610 let d2 = p3.y - p2.y;
3611 let d3 = p4.y - p3.y;
3612 let d4 = p5.y - p4.y;
3613
3614 assert!((d2 - d1).abs() < dec!(0.001));
3616 assert!((d3 - d2).abs() < dec!(0.001));
3617 assert!((d4 - d3).abs() < dec!(0.001));
3618 }
3619}
3620
3621#[cfg(test)]
3622mod tests_curve_arithmetic {
3623 use super::*;
3624 use crate::curves::utils::{create_constant_curve, create_linear_curve};
3625 use crate::geometrics::InterpolationType;
3626
3627 #[test]
3628 fn test_merge_curves_add() {
3629 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(1.0)).unwrap();
3630 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3631
3632 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Add).unwrap();
3633
3634 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
3636 for x in &test_points {
3637 let expected_y = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y
3638 + curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y;
3639
3640 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
3641 assert!(
3642 (result_point.y - expected_y).abs() < dec!(0.001),
3643 "Failed at x = {}, expected {}, got {}",
3644 x,
3645 expected_y,
3646 result_point.y
3647 );
3648 }
3649 }
3650
3651 #[test]
3652 fn test_merge_curves_subtract() {
3653 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(3.0)).unwrap();
3654 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(1.0)).unwrap();
3655 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Subtract).unwrap();
3656 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
3658 for x in &test_points {
3659 let expected_y = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y
3660 - curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y;
3661
3662 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
3663 assert!(
3664 (result_point.y - expected_y).abs() < dec!(0.001),
3665 "Failed at x = {}, expected {}, got {}",
3666 x,
3667 expected_y,
3668 result_point.y
3669 );
3670 }
3671 }
3672
3673 #[test]
3674 fn test_merge_curves_multiply() {
3675 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3676 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(3.0)).unwrap();
3677
3678 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Multiply).unwrap();
3679
3680 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
3682 for x in &test_points {
3683 let expected_y = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y
3684 * curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y;
3685
3686 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
3687 assert!(
3688 (result_point.y - expected_y).abs() < dec!(0.001),
3689 "Failed at x = {}, expected {}, got {}",
3690 x,
3691 expected_y,
3692 result_point.y
3693 );
3694 }
3695 }
3696
3697 #[test]
3698 fn test_merge_curves_divide() {
3699 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(6.0)).unwrap();
3700 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3701 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Divide).unwrap();
3702
3703 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
3705 for x in &test_points {
3706 let y2 = curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y;
3707
3708 if y2 == Decimal::ZERO {
3710 continue;
3711 }
3712
3713 let expected_y = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y / y2;
3714
3715 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
3716 assert!(
3717 (result_point.y - expected_y).abs() < dec!(0.001),
3718 "Failed at x = {}, expected {}, got {}",
3719 x,
3720 expected_y,
3721 result_point.y
3722 );
3723 }
3724 }
3725
3726 #[test]
3732 fn test_merge_curves_divide_folds_every_divisor() {
3733 let eight = create_constant_curve(dec!(0.0), dec!(10.0), dec!(8.0)).unwrap();
3734 let two_a = create_constant_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3735 let two_b = create_constant_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3736
3737 let result = Curve::merge(&[&eight, &two_a, &two_b], MergeOperation::Divide).unwrap();
3738
3739 for x in [dec!(0.0), dec!(5.0), dec!(10.0)] {
3740 let y = result.interpolate(x, InterpolationType::Cubic).unwrap().y;
3741 assert!(
3742 (y - dec!(2)).abs() < dec!(0.0001),
3743 "expected 8 / 2 / 2 = 2 at x = {x}, got {y}"
3744 );
3745 }
3746 }
3747
3748 const DETERMINISM_REPEATS: usize = 512;
3766
3767 fn ordinate_fingerprint(curve: &Curve) -> Vec<(i128, u32)> {
3773 curve
3774 .points
3775 .iter()
3776 .map(|point| (point.y.mantissa(), point.y.scale()))
3777 .collect()
3778 }
3779
3780 const POOL_SIZES: [usize; 5] = [1, 2, 4, 8, 16];
3789
3790 const RUNS_PER_POOL: usize = 4;
3798
3799 const CROSS_POOL_OPERANDS: u32 = 40;
3806
3807 fn cross_pool_operands() -> Vec<Curve> {
3813 (0..CROSS_POOL_OPERANDS)
3814 .map(|i| {
3815 let value = Decimal::ONE - Decimal::ONE / Decimal::from(1000 + i * 7);
3816 create_constant_curve(dec!(0), dec!(10), value)
3817 .expect("constant curve fixture must build")
3818 })
3819 .collect()
3820 }
3821
3822 fn assert_reproducible_across_pools(curves: &[&Curve], operation: MergeOperation) {
3825 let mut reference: Option<Vec<(i128, u32)>> = None;
3826
3827 for threads in POOL_SIZES {
3828 let pool = rayon::ThreadPoolBuilder::new()
3829 .num_threads(threads)
3830 .build()
3831 .expect("rayon must build a pool of a fixed size");
3832
3833 for run in 0..RUNS_PER_POOL {
3834 let merged = pool
3835 .install(|| Curve::merge(curves, operation))
3836 .expect("the fixture merge must succeed");
3837 let fingerprint = ordinate_fingerprint(&merged);
3838
3839 match &reference {
3840 None => reference = Some(fingerprint),
3841 Some(reference) => assert_eq!(
3842 &fingerprint, reference,
3843 "run {run} on a {threads}-thread pool produced different digits \
3844 from the first merge, under {operation:?}"
3845 ),
3846 }
3847 }
3848 }
3849 }
3850
3851 #[test]
3868 fn test_merge_curves_multiply_is_reproducible_across_pool_sizes() {
3869 let curves = cross_pool_operands();
3870 let refs: Vec<&Curve> = curves.iter().collect();
3871 assert_reproducible_across_pools(&refs, MergeOperation::Multiply);
3872 }
3873
3874 #[test]
3881 fn test_merge_curves_divide_is_reproducible_across_pool_sizes() {
3882 let curves = cross_pool_operands();
3883 let refs: Vec<&Curve> = curves.iter().collect();
3884 assert_reproducible_across_pools(&refs, MergeOperation::Divide);
3885 }
3886
3887 #[test]
3906 fn test_merge_curves_multiply_is_reproducible() {
3907 let a =
3908 create_constant_curve(dec!(0), dec!(10), dec!(0.010989010989010989010989011)).unwrap();
3909 let b =
3910 create_constant_curve(dec!(0), dec!(10), dec!(0.010752688172043010752688172)).unwrap();
3911 let c =
3912 create_constant_curve(dec!(0), dec!(10), dec!(0.0094339622641509433962264151)).unwrap();
3913
3914 let expected = dec!(0.0000011147302687168785768908);
3915 let first = Curve::merge(&[&a, &b, &c], MergeOperation::Multiply).unwrap();
3916 for point in first.points.iter() {
3917 assert_eq!(
3918 (point.y.mantissa(), point.y.scale()),
3919 (expected.mantissa(), expected.scale()),
3920 "expected the left-to-right product {expected} at x = {}, got {}",
3921 point.x,
3922 point.y
3923 );
3924 }
3925
3926 let reference = ordinate_fingerprint(&first);
3927 for run in 1..DETERMINISM_REPEATS {
3928 let again = Curve::merge(&[&a, &b, &c], MergeOperation::Multiply).unwrap();
3929 assert_eq!(
3930 ordinate_fingerprint(&again),
3931 reference,
3932 "run {run} of {DETERMINISM_REPEATS} produced different digits from the first"
3933 );
3934 }
3935 }
3936
3937 #[test]
3949 fn test_merge_curves_divide_is_reproducible() {
3950 let a = create_constant_curve(dec!(0), dec!(10), dec!(97)).unwrap();
3951 let b = create_constant_curve(dec!(0), dec!(10), dec!(91)).unwrap();
3952 let c = create_constant_curve(dec!(0), dec!(10), dec!(93)).unwrap();
3953
3954 let first = Curve::merge(&[&a, &b, &c], MergeOperation::Divide).unwrap();
3955 let reference = ordinate_fingerprint(&first);
3956 for run in 1..DETERMINISM_REPEATS {
3957 let again = Curve::merge(&[&a, &b, &c], MergeOperation::Divide).unwrap();
3958 assert_eq!(
3959 ordinate_fingerprint(&again),
3960 reference,
3961 "run {run} of {DETERMINISM_REPEATS} produced different digits from the first"
3962 );
3963 }
3964 }
3965
3966 #[test]
3967 fn test_merge_curves_max() {
3968 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3969 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(3.0)).unwrap();
3970
3971 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Max).unwrap();
3972
3973 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
3975 for x in &test_points {
3976 let y1 = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y;
3977 let y2 = curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y;
3978 let expected_y = y1.max(y2);
3979
3980 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
3981 assert!(
3982 (result_point.y - expected_y).abs() < dec!(0.001),
3983 "Failed at x = {}, expected {}, got {}",
3984 x,
3985 expected_y,
3986 result_point.y
3987 );
3988 }
3989 }
3990
3991 #[test]
3992 fn test_merge_curves_min() {
3993 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
3994 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(3.0)).unwrap();
3995
3996 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Min).unwrap();
3997
3998 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
4000 for x in &test_points {
4001 let y1 = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y;
4002 let y2 = curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y;
4003 let expected_y = y1.min(y2);
4004
4005 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
4006 assert!(
4007 (result_point.y - expected_y).abs() < dec!(0.001),
4008 "Failed at x = {}, expected {}, got {}",
4009 x,
4010 expected_y,
4011 result_point.y
4012 );
4013 }
4014 }
4015
4016 #[test]
4017 fn test_merge_with_single_operation() {
4018 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
4019 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(3.0)).unwrap();
4020
4021 let result = curve1.merge_with(&curve2, MergeOperation::Add).unwrap();
4022
4023 let merged_result = Curve::merge(&[&curve1, &curve2], MergeOperation::Add).unwrap();
4025
4026 assert_eq!(result.points.len(), merged_result.points.len());
4028
4029 for i in 0..result.points.len() {
4030 assert!((result[i].x - merged_result[i].x).abs() < dec!(0.001));
4031 assert!((result[i].y - merged_result[i].y).abs() < dec!(0.001));
4032 }
4033 }
4034
4035 fn long_mantissa_curve() -> Curve {
4040 let step = dec!(10) / dec!(511);
4041 let points: Vec<Point2D> = (0..=511i64)
4042 .map(|i| {
4043 let x = step * Decimal::from(i);
4044 Point2D::new(x, x)
4045 })
4046 .collect();
4047 Curve::from_vector(points.iter().collect())
4048 }
4049
4050 #[test]
4051 fn test_merge_curves_long_mantissa_range_ends_on_max_x() {
4052 let curve = long_mantissa_curve();
4053 let max_x = curve.x_range.1;
4054 assert_eq!(max_x, dec!(10.000000000000000000000000016));
4055
4056 let result = curve.merge_with(&curve, MergeOperation::Add).unwrap();
4057
4058 assert_eq!(result.points.len(), 101);
4060 assert!(result.points.iter().all(|p| p.x <= max_x));
4061 assert_eq!(result.x_range, (Decimal::ZERO, max_x));
4062 let last = result.points.last().unwrap();
4064 assert!((last.y - dec!(2) * max_x).abs() < dec!(0.0000000001));
4065 let mid = result
4066 .interpolate(dec!(5), InterpolationType::Linear)
4067 .unwrap();
4068 assert!((mid.y - dec!(10)).abs() < dec!(0.0000000001));
4069 }
4070
4071 #[test]
4072 fn test_merge_curves_error_handling() {
4073 let result = Curve::merge(&[], MergeOperation::Add);
4075 assert!(result.is_err());
4076
4077 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(1.0)).unwrap();
4079 let curve2 = create_linear_curve(dec!(5.0), dec!(15.0), dec!(2.0)).unwrap();
4080
4081 let result = Curve::merge(&[&curve1, &curve2], MergeOperation::Add);
4083 assert!(result.is_ok());
4084 }
4085
4086 #[test]
4087 fn test_merge_multiple_curves() {
4088 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(1.0)).unwrap();
4089 let curve2 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(2.0)).unwrap();
4090 let curve3 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(3.0)).unwrap();
4091
4092 let result = Curve::merge(&[&curve1, &curve2, &curve3], MergeOperation::Add).unwrap();
4093
4094 let test_points = [dec!(0.0), dec!(5.0), dec!(10.0)];
4096 for x in &test_points {
4097 let expected_y = curve1.interpolate(*x, InterpolationType::Cubic).unwrap().y
4098 + curve2.interpolate(*x, InterpolationType::Cubic).unwrap().y
4099 + curve3.interpolate(*x, InterpolationType::Cubic).unwrap().y;
4100
4101 let result_point = result.interpolate(*x, InterpolationType::Cubic).unwrap();
4102 assert!(
4103 (result_point.y - expected_y).abs() < dec!(0.001),
4104 "Failed at x = {}, expected {}, got {}",
4105 x,
4106 expected_y,
4107 result_point.y
4108 );
4109 }
4110 }
4111}
4112
4113#[cfg(test)]
4114mod tests_extended {
4115 use super::*;
4116 use crate::error::CurveError::OperationError;
4117 use crate::geometrics::{ConstructionMethod, ConstructionParams};
4118 use optionstratlib_core::error::OperationErrorKind;
4119
4120 #[derive(Debug, thiserror::Error)]
4123 #[error("{0}")]
4124 struct GeneratorFailure(&'static str);
4125
4126 #[test]
4127 fn test_construct_from_data_empty() {
4128 let result = Curve::construct(ConstructionMethod::FromData {
4129 points: BTreeSet::new(),
4130 });
4131 assert!(result.is_err());
4132 let error = result.unwrap_err();
4133 match error {
4134 CurveError::Point2DError { reason } => {
4135 assert_eq!(reason, "Empty points array");
4136 }
4137 _ => {
4138 panic!("Unexpected error type");
4139 }
4140 }
4141 }
4142
4143 #[test]
4144 fn test_construct_parametric_valid() {
4145 let f = |t: Decimal| Ok(Point2D::new(t, t * dec!(2.0)));
4146 let params = ConstructionParams::D2 {
4147 t_start: Decimal::ZERO,
4148 t_end: dec!(10.0),
4149 steps: 10,
4150 };
4151 let result = Curve::construct(ConstructionMethod::Parametric {
4152 f: Box::new(f),
4153 params,
4154 });
4155 assert!(result.is_ok());
4156 }
4157
4158 #[test]
4159 fn test_construct_parametric_invalid_function() {
4160 let f = |_t: Decimal| -> Result<Point2D, CurveError> {
4161 Err(CurveError::generator(GeneratorFailure(
4162 "Function evaluation failed",
4163 )))
4164 };
4165 let params = ConstructionParams::D2 {
4166 t_start: Decimal::ZERO,
4167 t_end: dec!(10.0),
4168 steps: 10,
4169 };
4170 let result = Curve::construct(ConstructionMethod::Parametric {
4171 f: Box::new(f),
4172 params,
4173 });
4174 assert!(result.is_err());
4175 let error = result.unwrap_err();
4176 match error {
4179 CurveError::Generator(source) => {
4180 let cause = source
4181 .downcast_ref::<GeneratorFailure>()
4182 .expect("the generator's own error must survive boxing");
4183 assert!(cause.to_string().contains("Function evaluation failed"));
4184 }
4185 _ => {
4186 panic!("Unexpected error type");
4187 }
4188 }
4189 }
4190
4191 #[test]
4192 fn test_segment_not_found_error() {
4193 let segment: Option<Point2D> = None;
4194 let result: Result<Point2D, CurveError> = segment.ok_or_else(|| {
4195 CurveError::ConstructionError(
4196 "Could not find valid segment for interpolation".to_string(),
4197 )
4198 });
4199 assert!(result.is_err());
4200 let error = result.unwrap_err();
4201 match error {
4202 CurveError::ConstructionError(reason) => {
4203 assert_eq!(reason, "Could not find valid segment for interpolation");
4204 }
4205 _ => {
4206 panic!("Unexpected error type");
4207 }
4208 }
4209 }
4210
4211 #[test]
4212 fn test_compute_basic_metrics_placeholder() {
4213 let curve = Curve {
4214 points: BTreeSet::new(),
4215 x_range: (Default::default(), Default::default()),
4216 };
4217 let metrics = curve.compute_basic_metrics();
4218 assert!(metrics.is_ok());
4219 let metrics = metrics.unwrap();
4220 assert_eq!(metrics.mean, Decimal::ZERO);
4221 }
4222
4223 #[test]
4224 fn test_single_curve_return() {
4225 let curve = Curve {
4226 points: BTreeSet::new(),
4227 x_range: (Default::default(), Default::default()),
4228 };
4229 let result = if vec![curve.clone()].len() == 1 {
4230 Ok(curve.clone())
4231 } else {
4232 Err(CurveError::invalid_parameters(
4233 "merge_curves",
4234 "Invalid state",
4235 ))
4236 };
4237 assert!(result.is_ok());
4238 }
4239
4240 #[test]
4241 fn test_merge_curves_invalid_x_range() {
4242 let min_x = dec!(10.0);
4243 let max_x = dec!(5.0);
4244 let result = if min_x >= max_x {
4245 Err(CurveError::invalid_parameters(
4246 "merge_curves",
4247 "Curves have incompatible x-ranges",
4248 ))
4249 } else {
4250 Ok(())
4251 };
4252 assert!(result.is_err());
4253 let error = result.unwrap_err();
4254 match error {
4255 OperationError(OperationErrorKind::InvalidParameters { operation, reason }) => {
4256 assert_eq!(operation, "merge_curves");
4257 assert_eq!(reason, "Curves have incompatible x-ranges");
4258 }
4259 _ => {
4260 panic!("Unexpected error type");
4261 }
4262 }
4263 }
4264}
4265
4266#[cfg(test)]
4267mod tests_curve_metrics {
4268 use super::*;
4269 use optionstratlib_core::assert_decimal_eq;
4270 use rust_decimal_macros::dec;
4271 use std::collections::BTreeSet;
4272
4273 fn create_linear_curve() -> Curve {
4275 let points = BTreeSet::from_iter(vec![
4276 Point2D::new(dec!(0.0), dec!(0.0)),
4277 Point2D::new(dec!(1.0), dec!(2.0)),
4278 Point2D::new(dec!(2.0), dec!(4.0)),
4279 Point2D::new(dec!(3.0), dec!(6.0)),
4280 Point2D::new(dec!(4.0), dec!(8.0)),
4281 ]);
4282 Curve::new(points)
4283 }
4284
4285 fn create_non_linear_curve() -> Curve {
4286 Curve {
4287 points: (0..=20)
4288 .map(|x| Point2D {
4289 x: Decimal::from(x),
4290 y: Decimal::from(x * x % 7), })
4292 .collect(),
4293 x_range: (Default::default(), Default::default()),
4294 }
4295 }
4296
4297 fn create_constant_curve() -> Curve {
4298 let points = BTreeSet::from_iter(vec![
4299 Point2D::new(dec!(0.0), dec!(5.0)),
4300 Point2D::new(dec!(1.0), dec!(5.0)),
4301 Point2D::new(dec!(2.0), dec!(5.0)),
4302 ]);
4303 Curve::new(points)
4304 }
4305
4306 #[test]
4307 fn test_basic_metrics() {
4308 let linear_curve = create_linear_curve();
4310 let basic_metrics = linear_curve.compute_basic_metrics().unwrap();
4311
4312 assert_decimal_eq!(basic_metrics.mean, dec!(4.0), dec!(0.001));
4314 assert_decimal_eq!(basic_metrics.median, dec!(4.0), dec!(0.001));
4315 assert_decimal_eq!(basic_metrics.std_dev, dec!(2.82842712), dec!(0.001));
4316
4317 let constant_curve = create_constant_curve();
4319 let constant_metrics = constant_curve.compute_basic_metrics().unwrap();
4320
4321 assert_decimal_eq!(constant_metrics.mean, dec!(5.0), dec!(0.001));
4322 assert_decimal_eq!(constant_metrics.median, dec!(5.0), dec!(0.001));
4323 assert_decimal_eq!(constant_metrics.std_dev, dec!(0.0), dec!(0.001));
4324 }
4325
4326 #[test]
4327 fn test_shape_metrics() {
4328 let linear_curve = create_linear_curve();
4330 let shape_metrics = linear_curve.compute_shape_metrics().unwrap();
4331
4332 assert!(
4334 shape_metrics.skewness.abs() < dec!(0.5),
4335 "Skewness for linear curve should be very close to 0, got {}",
4336 shape_metrics.skewness
4337 );
4338
4339 assert!(
4341 shape_metrics.kurtosis.abs() < dec!(2.0),
4342 "Kurtosis for linear curve should be close to 0, got {}",
4343 shape_metrics.kurtosis
4344 );
4345
4346 let non_linear_curve = create_non_linear_curve();
4348 let non_linear_metrics = non_linear_curve.compute_shape_metrics().unwrap();
4349
4350 assert!(
4352 non_linear_metrics.skewness.abs() > dec!(0.003),
4353 "Non-linear curve should have significant skewness, got {}",
4354 non_linear_metrics.skewness
4355 );
4356
4357 assert!(
4359 non_linear_metrics.kurtosis.abs() > dec!(1.0),
4360 "Non-linear curve should have significant kurtosis, got {}",
4361 non_linear_metrics.kurtosis
4362 );
4363
4364 assert!(
4366 !non_linear_metrics.peaks.is_empty(),
4367 "Peaks should be detected"
4368 );
4369 assert!(
4370 !non_linear_metrics.valleys.is_empty(),
4371 "Valleys should be detected"
4372 );
4373 }
4374
4375 #[test]
4376 fn test_range_metrics() {
4377 let linear_curve = create_linear_curve();
4379 let range_metrics = linear_curve.compute_range_metrics().unwrap();
4380
4381 assert_decimal_eq!(range_metrics.min.y, dec!(0.0), dec!(0.001));
4382 assert_decimal_eq!(range_metrics.max.y, dec!(8.0), dec!(0.001));
4383 assert_decimal_eq!(range_metrics.range, dec!(8.0), dec!(0.001));
4384
4385 let constant_curve = create_constant_curve();
4387 let constant_range_metrics = constant_curve.compute_range_metrics().unwrap();
4388
4389 assert_decimal_eq!(constant_range_metrics.min.y, dec!(5.0), dec!(0.001));
4390 assert_decimal_eq!(constant_range_metrics.max.y, dec!(5.0), dec!(0.001));
4391 assert_decimal_eq!(constant_range_metrics.range, dec!(0.0), dec!(0.001));
4392 }
4393
4394 #[test]
4395 fn test_trend_metrics() {
4396 let linear_curve = create_linear_curve();
4398 let trend_metrics = linear_curve.compute_trend_metrics().unwrap();
4399
4400 assert_decimal_eq!(trend_metrics.slope, dec!(2.0), dec!(0.001));
4402 assert_decimal_eq!(trend_metrics.intercept, dec!(0.0), dec!(0.001));
4403 assert_decimal_eq!(trend_metrics.r_squared, dec!(1.0), dec!(0.001));
4404
4405 let non_linear_curve = create_non_linear_curve();
4407 let non_linear_trend_metrics = non_linear_curve.compute_trend_metrics().unwrap();
4408
4409 assert!(non_linear_trend_metrics.r_squared < dec!(1.0));
4411
4412 assert!(!non_linear_trend_metrics.moving_average.is_empty());
4414 }
4415
4416 #[test]
4417 fn test_constant_curve_risk_metrics() {
4418 let constant_curve = create_constant_curve();
4419 let risk_metrics = constant_curve.compute_risk_metrics().unwrap();
4420
4421 assert_eq!(risk_metrics.volatility, dec!(0.0));
4422 assert_eq!(risk_metrics.coefficient_of_variation, dec!(0.0));
4423 assert_eq!(risk_metrics.sharpe_ratio, dec!(0.0));
4424 }
4425
4426 #[test]
4430 fn test_risk_metrics_flat_curve_keeps_deterministic_var() {
4431 let curve = create_constant_curve();
4432 let metrics = curve.compute_risk_metrics().unwrap();
4433
4434 assert_eq!(metrics.volatility, Decimal::ZERO);
4436 assert_eq!(metrics.value_at_risk, dec!(5));
4438 assert_eq!(metrics.expected_shortfall, Decimal::ZERO);
4441 assert_eq!(metrics.coefficient_of_variation, Decimal::ZERO);
4443 assert_eq!(metrics.sharpe_ratio, Decimal::ZERO);
4445 }
4446
4447 #[test]
4450 fn test_risk_metrics_flat_negative_curve_keeps_deterministic_var() {
4451 let points = BTreeSet::from_iter(vec![
4452 Point2D::new(dec!(0.0), dec!(-2.5)),
4453 Point2D::new(dec!(1.0), dec!(-2.5)),
4454 Point2D::new(dec!(2.0), dec!(-2.5)),
4455 ]);
4456 let metrics = Curve::new(points).compute_risk_metrics().unwrap();
4457
4458 assert_eq!(metrics.volatility, Decimal::ZERO);
4459 assert_eq!(metrics.value_at_risk, dec!(-2.5));
4460 assert_eq!(metrics.sharpe_ratio, Decimal::ZERO);
4461 }
4462
4463 #[test]
4464 fn test_risk_metrics() {
4465 let linear_curve = create_linear_curve();
4467 let risk_metrics = linear_curve.compute_risk_metrics().unwrap();
4468
4469 assert!(
4470 risk_metrics.volatility > dec!(0.0),
4471 "Volatility debe ser mayor a cero."
4472 );
4473 assert!(
4474 risk_metrics.value_at_risk != dec!(0.0),
4475 "Value at Risk no debe ser cero."
4476 );
4477 assert!(
4478 risk_metrics.coefficient_of_variation != dec!(0.0),
4479 "the coefficient of variation must not be zero"
4480 );
4481 }
4482
4483 #[test]
4484 fn test_risk_metrics_bis() {
4485 let linear_curve = create_linear_curve();
4487 let risk_metrics = linear_curve.compute_risk_metrics().unwrap();
4488
4489 assert!(risk_metrics.volatility > dec!(0.0));
4491 assert!(risk_metrics.value_at_risk != dec!(0.0));
4492 assert!(risk_metrics.coefficient_of_variation != dec!(0.0));
4493
4494 let constant_curve = create_constant_curve();
4496 let constant_risk_metrics = constant_curve.compute_risk_metrics().unwrap();
4497
4498 assert_decimal_eq!(constant_risk_metrics.volatility, dec!(0.0), dec!(0.001));
4500 }
4501
4502 #[test]
4503 fn test_edge_cases() {
4504 let empty_curve = Curve::new(BTreeSet::new());
4506
4507 assert!(empty_curve.compute_basic_metrics().is_ok());
4508 assert!(empty_curve.compute_shape_metrics().is_ok());
4509 assert!(empty_curve.compute_range_metrics().is_ok());
4510 assert!(empty_curve.compute_trend_metrics().is_ok());
4511 assert!(empty_curve.compute_risk_metrics().is_ok());
4512
4513 let single_point_curve = Curve::new(BTreeSet::from_iter(vec![Point2D::new(
4515 dec!(1.0),
4516 dec!(1.0),
4517 )]));
4518
4519 assert!(single_point_curve.compute_basic_metrics().is_ok());
4520 assert!(single_point_curve.compute_shape_metrics().is_ok());
4521 assert!(single_point_curve.compute_range_metrics().is_ok());
4522 assert!(single_point_curve.compute_trend_metrics().is_ok());
4523 assert!(single_point_curve.compute_risk_metrics().is_ok());
4524 }
4525}
4526
4527#[cfg(test)]
4528mod tests_merge_axis_interpolate {
4529 use super::*;
4530 use crate::curves::utils::create_linear_curve;
4531 use crate::geometrics::InterpolationType;
4532 use rust_decimal_macros::dec;
4533
4534 #[test]
4535 fn test_merge_axis_interpolate_linear() {
4536 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(0.5)).unwrap();
4538 let curve2 = create_linear_curve(dec!(4.0), dec!(20.0), dec!(1.0)).unwrap();
4539
4540 let result = curve1.merge_axis_interpolate(&curve2, InterpolationType::Linear);
4542
4543 assert!(result.is_ok());
4544 let (interpolated_curve1, interpolated_curve2) = result.unwrap();
4545
4546 assert_eq!(interpolated_curve1.x_range.0, interpolated_curve2.x_range.0);
4548 assert_eq!(interpolated_curve1.x_range.1, interpolated_curve2.x_range.1);
4549
4550 assert_eq!(interpolated_curve1.points.len(), 10);
4552 assert_eq!(interpolated_curve2.points.len(), 10);
4553 assert_eq!(interpolated_curve1.x_range, interpolated_curve2.x_range);
4554 assert_eq!(
4555 interpolated_curve1.get_index_values(),
4556 interpolated_curve2.get_index_values()
4557 );
4558 }
4559
4560 #[test]
4561 fn test_merge_axis_interpolate_cubic() {
4562 let curve1 = create_linear_curve(dec!(0.0), dec!(10.0), dec!(0.5)).unwrap();
4564 let curve2 = create_linear_curve(dec!(4.0), dec!(20.0), dec!(1.0)).unwrap();
4565
4566 let result = curve1.merge_axis_interpolate(&curve2, InterpolationType::Cubic);
4568
4569 assert!(result.is_ok());
4570 let (interpolated_curve1, interpolated_curve2) = result.unwrap();
4571
4572 assert_eq!(interpolated_curve1.x_range.0, interpolated_curve2.x_range.0);
4574 assert_eq!(interpolated_curve1.x_range.1, interpolated_curve2.x_range.1);
4575
4576 assert_eq!(interpolated_curve1.points.len(), 10);
4578 assert_eq!(interpolated_curve2.points.len(), 10);
4579 assert_eq!(interpolated_curve1.x_range, interpolated_curve2.x_range);
4580 assert_eq!(
4581 interpolated_curve1.get_index_values(),
4582 interpolated_curve2.get_index_values()
4583 );
4584 }
4585}
4586
4587#[cfg(test)]
4588mod tests_geometric_transformations {
4589 use super::*;
4590 use rust_decimal_macros::dec;
4591
4592 fn create_test_curve() -> Curve {
4593 Curve::new(BTreeSet::from_iter(vec![
4594 Point2D::new(dec!(0.0), dec!(0.0)),
4595 Point2D::new(dec!(1.0), dec!(1.0)),
4596 Point2D::new(dec!(2.0), dec!(4.0)),
4597 Point2D::new(dec!(3.0), dec!(9.0)),
4598 ]))
4599 }
4600
4601 mod test_translate {
4602 use super::*;
4603
4604 #[test]
4605 fn test_translate_positive() {
4606 let curve = create_test_curve();
4607 let result = curve.translate(vec![&dec!(2.0), &dec!(3.0)]).unwrap();
4608
4609 let translated_points: Vec<_> = result.points.iter().collect();
4610 assert_eq!(translated_points[0].x, dec!(2.0));
4611 assert_eq!(translated_points[0].y, dec!(3.0));
4612
4613 assert_eq!(translated_points[1].x, dec!(3.0));
4614 assert_eq!(translated_points[1].y, dec!(4.0));
4615
4616 assert_eq!(translated_points[2].x, dec!(4.0));
4617 assert_eq!(translated_points[2].y, dec!(7.0));
4618
4619 assert_eq!(translated_points[3].x, dec!(5.0));
4620 assert_eq!(translated_points[3].y, dec!(12.0));
4621 }
4622
4623 #[test]
4624 fn test_translate_negative() {
4625 let curve = create_test_curve();
4626 let result = curve.translate(vec![&dec!(-1.0), &dec!(-2.0)]).unwrap();
4627
4628 let translated_points: Vec<_> = result.points.iter().collect();
4629 assert_eq!(translated_points[0].x, dec!(-1.0));
4630 assert_eq!(translated_points[0].y, dec!(-2.0));
4631 }
4632
4633 #[test]
4634 fn test_translate_zero() {
4635 let curve = create_test_curve();
4636 let result = curve.translate(vec![&dec!(0.0), &dec!(0.0)]).unwrap();
4637 assert_eq!(curve.points, result.points);
4638 }
4639
4640 #[test]
4641 fn test_translate_wrong_dimensions() {
4642 let curve = create_test_curve();
4643 let result = curve.translate(vec![&dec!(1.0)]);
4644 assert!(result.is_err());
4645 }
4646
4647 #[test]
4648 fn test_translate_preserves_shape() {
4649 let curve = create_test_curve();
4650 let result = curve.translate(vec![&dec!(1.0), &dec!(1.0)]).unwrap();
4651
4652 let original_diffs: Vec<Decimal> = curve
4653 .points
4654 .iter()
4655 .zip(curve.points.iter().skip(1))
4656 .map(|(a, b)| b.y - a.y)
4657 .collect();
4658
4659 let translated_diffs: Vec<Decimal> = result
4660 .points
4661 .iter()
4662 .zip(result.points.iter().skip(1))
4663 .map(|(a, b)| b.y - a.y)
4664 .collect();
4665
4666 assert_eq!(original_diffs, translated_diffs);
4667 }
4668 }
4669
4670 mod test_scale {
4671 use super::*;
4672
4673 #[test]
4674 fn test_scale_uniform() {
4675 let curve = create_test_curve();
4676 let result = curve.scale(vec![&dec!(2.0), &dec!(2.0)]).unwrap();
4677
4678 let scaled_points: Vec<_> = result.points.iter().collect();
4679
4680 assert_eq!(scaled_points[0].x, dec!(0.0));
4681 assert_eq!(scaled_points[0].y, dec!(0.0));
4682
4683 assert_eq!(scaled_points[1].x, dec!(2.0));
4684 assert_eq!(scaled_points[1].y, dec!(2.0));
4685
4686 assert_eq!(scaled_points[2].x, dec!(4.0));
4687 assert_eq!(scaled_points[2].y, dec!(8.0));
4688
4689 assert_eq!(scaled_points[3].x, dec!(6.0));
4690 assert_eq!(scaled_points[3].y, dec!(18.0));
4691 }
4692
4693 #[test]
4694 fn test_scale_non_uniform() {
4695 let curve = create_test_curve();
4696 let result = curve.scale(vec![&dec!(2.0), &dec!(3.0)]).unwrap();
4697
4698 let scaled_points: Vec<_> = result.points.iter().collect();
4699 assert_eq!(scaled_points[1].x, dec!(2.0));
4700 assert_eq!(scaled_points[1].y, dec!(3.0));
4701 }
4702
4703 #[test]
4704 fn test_scale_zero() {
4705 let curve = create_test_curve();
4706 let result = curve.scale(vec![&dec!(0.0), &dec!(0.0)]).unwrap();
4707
4708 assert!(
4709 result
4710 .points
4711 .iter()
4712 .all(|p| p.x == dec!(0.0) && p.y == dec!(0.0))
4713 );
4714 }
4715
4716 #[test]
4717 fn test_scale_wrong_dimensions() {
4718 let curve = create_test_curve();
4719 let result = curve.scale(vec![&dec!(2.0)]);
4720 assert!(result.is_err());
4721 }
4722
4723 #[test]
4724 fn test_scale_negative() {
4725 let curve = create_test_curve();
4726 let result = curve.scale(vec![&dec!(-1.0), &dec!(-1.0)]).unwrap();
4727
4728 assert_eq!(result[1].x, dec!(-2.0));
4729 assert_eq!(result[1].y, dec!(-4.0));
4730
4731 assert_eq!(result[3].x, dec!(0.0));
4732 assert_eq!(result[3].y, dec!(0.0));
4733 }
4734 }
4735
4736 mod test_intersect_with {
4737 use super::*;
4738
4739 #[test]
4740 fn test_curves_intersect() {
4741 let curve1 = create_test_curve();
4742 let curve2 = Curve::new(BTreeSet::from_iter(vec![
4743 Point2D::new(dec!(0.0), dec!(0.0)),
4744 Point2D::new(dec!(1.0), dec!(2.0)),
4745 ]));
4746
4747 let intersections = curve1.intersect_with(&curve2).unwrap();
4748 assert_eq!(intersections.len(), 1);
4749 }
4750
4751 #[test]
4752 fn test_no_intersection() {
4753 let curve1 = create_test_curve();
4754 let curve2 = Curve::new(BTreeSet::from_iter(vec![
4755 Point2D::new(dec!(10.0), dec!(10.0)),
4756 Point2D::new(dec!(11.0), dec!(11.0)),
4757 ]));
4758
4759 let intersections = curve1.intersect_with(&curve2).unwrap();
4760 assert!(intersections.is_empty());
4761 }
4762
4763 #[test]
4764 fn test_multiple_intersections() {
4765 let curve1 = create_test_curve();
4766 let curve2 = create_test_curve();
4767
4768 let intersections = curve1.intersect_with(&curve2).unwrap();
4769 assert_eq!(intersections.len(), curve1.points.len());
4770 }
4771
4772 #[test]
4773 fn test_self_intersection() {
4774 let curve = create_test_curve();
4775 let intersections = curve.intersect_with(&curve).unwrap();
4776 assert_eq!(intersections.len(), curve.points.len());
4777 }
4778
4779 #[test]
4780 fn test_empty_curves() {
4781 let curve1 = Curve::new(BTreeSet::new());
4782 let curve2 = Curve::new(BTreeSet::new());
4783
4784 let intersections = curve1.intersect_with(&curve2).unwrap();
4785 assert!(intersections.is_empty());
4786 }
4787 }
4788
4789 mod test_derivative_at {
4790 use super::*;
4791
4792 #[test]
4793 fn test_linear_derivative() {
4794 let curve = Curve::new(BTreeSet::from_iter(vec![
4795 Point2D::new(dec!(0.0), dec!(0.0)),
4796 Point2D::new(dec!(1.0), dec!(1.0)),
4797 ]));
4798
4799 let derivative = curve
4800 .derivative_at(&Point2D::new(dec!(0.5), dec!(0.5)))
4801 .unwrap();
4802 assert_eq!(derivative[0], dec!(1.0));
4803 }
4804
4805 #[test]
4806 fn test_quadratic_derivative() {
4807 let curve = create_test_curve();
4808 let derivative = curve
4809 .derivative_at(&Point2D::new(dec!(1.0), dec!(1.0)))
4810 .unwrap();
4811 assert_eq!(derivative[0], dec!(2.0));
4812 let derivative2 = curve
4813 .derivative_at(&Point2D::new(dec!(2.0), dec!(4.0)))
4814 .unwrap();
4815 assert_eq!(derivative2[0], dec!(4.0));
4816 }
4817
4818 #[test]
4819 fn test_out_of_range() {
4820 let curve = create_test_curve();
4821 let result = curve.derivative_at(&Point2D::new(dec!(10.0), dec!(0.0)));
4822 assert!(result.is_err());
4823 }
4824
4825 #[test]
4826 fn test_at_endpoint() {
4827 let curve = create_test_curve();
4828 let derivative = curve
4829 .derivative_at(&Point2D::new(dec!(0.0), dec!(0.0)))
4830 .unwrap();
4831 assert!(derivative[0] == dec!(0.0));
4832 }
4833
4834 #[test]
4835 fn test_vertical_line() {
4836 let curve = Curve::new(BTreeSet::from_iter(vec![
4837 Point2D::new(dec!(1.0), dec!(0.0)),
4838 Point2D::new(dec!(1.0), dec!(1.0)),
4839 ]));
4840
4841 let result = curve.derivative_at(&Point2D::new(dec!(1.0), dec!(0.5)));
4842 assert!(result.is_err());
4843 }
4844 }
4845
4846 mod test_extrema {
4847 use super::*;
4848
4849 #[test]
4850 fn test_find_extrema() {
4851 let curve = create_test_curve();
4852 let (min, max) = curve.extrema().unwrap();
4853 assert_eq!(min.y, dec!(0.0));
4854 assert_eq!(max.y, dec!(9.0));
4855 }
4856
4857 #[test]
4858 fn test_empty_curve() {
4859 let curve = Curve::new(BTreeSet::new());
4860 let result = curve.extrema();
4861 assert!(result.is_err());
4862 }
4863
4864 #[test]
4865 fn test_single_point() {
4866 let curve = Curve::new(BTreeSet::from_iter(vec![Point2D::new(
4867 dec!(1.0),
4868 dec!(1.0),
4869 )]));
4870
4871 let (min, max) = curve.extrema().unwrap();
4872 assert_eq!(min, max);
4873 }
4874
4875 #[test]
4876 fn test_flat_curve() {
4877 let curve = Curve::new(BTreeSet::from_iter(vec![
4878 Point2D::new(dec!(0.0), dec!(1.0)),
4879 Point2D::new(dec!(1.0), dec!(1.0)),
4880 ]));
4881
4882 let (min, max) = curve.extrema().unwrap();
4883 assert_eq!(min.y, max.y);
4884 }
4885
4886 #[test]
4887 fn test_multiple_extrema() {
4888 let curve = Curve::new(BTreeSet::from_iter(vec![
4889 Point2D::new(dec!(0.0), dec!(0.0)),
4890 Point2D::new(dec!(1.0), dec!(1.0)),
4891 Point2D::new(dec!(2.0), dec!(0.0)),
4892 ]));
4893
4894 let (min, max) = curve.extrema().unwrap();
4895 assert_eq!(min.y, dec!(0.0));
4896 assert_eq!(max.y, dec!(1.0));
4897 }
4898 }
4899
4900 mod test_measure_under {
4901 use super::*;
4902
4903 #[test]
4904 fn test_area_under_linear() {
4905 let curve = Curve::new(BTreeSet::from_iter(vec![
4906 Point2D::new(dec!(0.0), dec!(0.0)),
4907 Point2D::new(dec!(1.0), dec!(1.0)),
4908 ]));
4909
4910 let area = curve.measure_under(&dec!(0.0)).unwrap();
4911 assert_eq!(area, dec!(0.5));
4912 }
4913
4914 #[test]
4915 fn test_area_empty_curve() {
4916 let curve = Curve::new(BTreeSet::new());
4917 let area = curve.measure_under(&dec!(0.0)).unwrap();
4918 assert_eq!(area, dec!(0.0));
4919 }
4920
4921 #[test]
4922 fn test_area_single_point() {
4923 let curve = Curve::new(BTreeSet::from_iter(vec![Point2D::new(
4924 dec!(1.0),
4925 dec!(1.0),
4926 )]));
4927
4928 let area = curve.measure_under(&dec!(0.0)).unwrap();
4929 assert_eq!(area, dec!(0.0));
4930 }
4931
4932 #[test]
4933 fn test_area_with_base_value() {
4934 let curve = create_test_curve();
4935 let area1 = curve.measure_under(&dec!(0.0)).unwrap();
4936 let area2 = curve.measure_under(&dec!(1.0)).unwrap();
4937 assert!(area1 > area2);
4938 }
4939
4940 #[test]
4941 fn test_negative_area() {
4942 let curve = Curve::new(BTreeSet::from_iter(vec![
4943 Point2D::new(dec!(0.0), dec!(-1.0)),
4944 Point2D::new(dec!(1.0), dec!(-2.0)),
4945 ]));
4946
4947 let area = curve.measure_under(&dec!(0.0)).unwrap();
4948 assert!(area > dec!(0.0));
4949 }
4950 }
4951}
4952
4953#[cfg(test)]
4954mod tests_curve_serde {
4955 use super::*;
4956 use rust_decimal_macros::dec;
4957
4958 fn create_test_curve() -> Curve {
4960 let mut points = BTreeSet::new();
4961 points.insert(Point2D {
4962 x: dec!(1.0),
4963 y: dec!(2.0),
4964 });
4965 points.insert(Point2D {
4966 x: dec!(3.0),
4967 y: dec!(4.0),
4968 });
4969 points.insert(Point2D {
4970 x: dec!(5.0),
4971 y: dec!(6.0),
4972 });
4973
4974 Curve {
4975 points,
4976 x_range: (dec!(1.0), dec!(5.0)),
4977 }
4978 }
4979
4980 #[test]
4981 fn test_basic_serialization() {
4982 let curve = create_test_curve();
4983 let serialized = serde_json::to_string(&curve).unwrap();
4984 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
4985
4986 assert_eq!(curve.points, deserialized.points);
4987 assert_eq!(curve.x_range, deserialized.x_range);
4988 }
4989
4990 #[test]
4991 fn test_pretty_print() {
4992 let curve = create_test_curve();
4993 let serialized = serde_json::to_string_pretty(&curve).unwrap();
4994
4995 assert!(serialized.contains('\n'));
4997 assert!(serialized.contains(" "));
4998
4999 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
5001 assert_eq!(curve.points, deserialized.points);
5002 }
5003
5004 #[test]
5005 fn test_empty_curve() {
5006 let curve = Curve {
5007 points: BTreeSet::new(),
5008 x_range: (dec!(0.0), dec!(0.0)),
5009 };
5010
5011 let serialized = serde_json::to_string(&curve).unwrap();
5012 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
5013
5014 assert!(deserialized.points.is_empty());
5015 assert_eq!(deserialized.x_range, (dec!(0.0), dec!(0.0)));
5016 }
5017
5018 #[test]
5019 fn test_curve_with_negative_values() {
5020 let mut points = BTreeSet::new();
5021 points.insert(Point2D {
5022 x: dec!(-1.0),
5023 y: dec!(-2.0),
5024 });
5025 points.insert(Point2D {
5026 x: dec!(-3.0),
5027 y: dec!(-4.0),
5028 });
5029
5030 let curve = Curve {
5031 points,
5032 x_range: (dec!(-3.0), dec!(-1.0)),
5033 };
5034
5035 let serialized = serde_json::to_string(&curve).unwrap();
5036 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
5037
5038 assert_eq!(curve.points, deserialized.points);
5039 assert_eq!(curve.x_range, deserialized.x_range);
5040 }
5041
5042 #[test]
5043 fn test_curve_with_high_precision() {
5044 let mut points = BTreeSet::new();
5045 points.insert(Point2D {
5046 x: dec!(1.12345678901234567890),
5047 y: dec!(2.12345678901234567890),
5048 });
5049 points.insert(Point2D {
5050 x: dec!(3.12345678901234567890),
5051 y: dec!(4.12345678901234567890),
5052 });
5053
5054 let curve = Curve {
5055 points,
5056 x_range: (dec!(1.12345678901234567890), dec!(3.12345678901234567890)),
5057 };
5058
5059 let serialized = serde_json::to_string(&curve).unwrap();
5060 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
5061
5062 assert_eq!(curve.points, deserialized.points);
5063 assert_eq!(curve.x_range, deserialized.x_range);
5064 }
5065
5066 #[test]
5067 fn test_invalid_json() {
5068 let json_str = r#"{"points": []}"#;
5070 let result = serde_json::from_str::<Curve>(json_str);
5071 assert!(result.is_err());
5072
5073 let json_str = r#"{"points": [1, 2, 3], "x_range": [0, 1]}"#;
5075 let result = serde_json::from_str::<Curve>(json_str);
5076 assert!(result.is_err());
5077
5078 let json_str = r#"{"points": [], "x_range": "invalid"}"#;
5080 let result = serde_json::from_str::<Curve>(json_str);
5081 assert!(result.is_err());
5082 }
5083
5084 #[test]
5085 fn test_json_structure() {
5086 let curve = create_test_curve();
5087 let serialized = serde_json::to_string(&curve).unwrap();
5088 let json: serde_json::Value = serde_json::from_str(&serialized).unwrap();
5089
5090 assert!(json.is_object());
5092 assert!(json.get("points").is_some());
5093 assert!(json.get("x_range").is_some());
5094
5095 assert!(json.get("points").unwrap().is_array());
5097
5098 let x_range = json.get("x_range").unwrap().as_array().unwrap();
5100 assert_eq!(x_range.len(), 2);
5101 }
5102
5103 #[test]
5104 fn test_multiple_curves() {
5105 let curve1 = create_test_curve();
5106 let mut curve2 = create_test_curve();
5107 curve2.x_range = (dec!(6.0), dec!(10.0));
5108
5109 let curves = vec![curve1, curve2];
5110 let serialized = serde_json::to_string(&curves).unwrap();
5111 let deserialized: Vec<Curve> = serde_json::from_str(&serialized).unwrap();
5112
5113 assert_eq!(curves.len(), deserialized.len());
5114 assert_eq!(curves[0].points, deserialized[0].points);
5115 assert_eq!(curves[1].points, deserialized[1].points);
5116 }
5117
5118 #[test]
5119 fn test_ordering_preservation() {
5120 let curve = create_test_curve();
5121 let serialized = serde_json::to_string(&curve).unwrap();
5122 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
5123
5124 let original_points: Vec<_> = curve.points.into_iter().collect();
5126 let deserialized_points: Vec<_> = deserialized.points.into_iter().collect();
5127
5128 assert_eq!(original_points, deserialized_points);
5130 }
5131
5132 #[test]
5133 fn test_curve_with_extremes() {
5134 let mut points = BTreeSet::new();
5135 points.insert(Point2D {
5136 x: Decimal::MAX,
5137 y: Decimal::MAX,
5138 });
5139 points.insert(Point2D {
5140 x: Decimal::MIN,
5141 y: Decimal::MIN,
5142 });
5143
5144 let curve = Curve {
5145 points,
5146 x_range: (Decimal::MIN, Decimal::MAX),
5147 };
5148
5149 let serialized = serde_json::to_string(&curve).unwrap();
5150 let deserialized: Curve = serde_json::from_str(&serialized).unwrap();
5151
5152 assert_eq!(curve.points, deserialized.points);
5153 assert_eq!(curve.x_range, deserialized.x_range);
5154 }
5155}
5156
5157#[cfg(test)]
5158mod tests_curve_display_and_default {
5159 use crate::curves::{Curve, Point2D};
5160 use rust_decimal::Decimal;
5161 use rust_decimal_macros::dec;
5162 use std::collections::BTreeSet;
5163
5164 #[test]
5165 fn test_curve_display() {
5166 let point1 = Point2D::new(dec!(1.0), dec!(2.0));
5167 let point2 = Point2D::new(dec!(3.0), dec!(4.0));
5168
5169 let mut points = BTreeSet::new();
5170 points.insert(point1);
5171 points.insert(point2);
5172
5173 let curve = Curve::new(points);
5174
5175 let display_string = format!("{curve}");
5176 assert!(display_string.contains("(x: 1.0, y: 2.0)"));
5177 assert!(display_string.contains("(x: 3.0, y: 4.0)"));
5178 }
5179
5180 #[test]
5181 fn test_curve_default() {
5182 let curve = Curve::default();
5184 assert!(curve.points.is_empty());
5185 assert_eq!(curve.x_range, (Decimal::ZERO, Decimal::ZERO));
5186 }
5187}
5188
5189#[cfg(test)]
5190mod tests_curve_len_and_geometric {
5191 use crate::curves::{Curve, Point2D};
5192 use crate::error::CurveError;
5193 use crate::geometrics::{ConstructionMethod, ConstructionParams, GeometricObject};
5194 use optionstratlib_core::utils::Len;
5195 use rust_decimal_macros::dec;
5196 use std::collections::BTreeSet;
5197
5198 #[test]
5199 fn test_curve_len() {
5200 let curve = Curve::default();
5202 assert_eq!(curve.len(), 0);
5203 assert!(curve.is_empty());
5204
5205 let mut points = BTreeSet::new();
5206 points.insert(Point2D::new(dec!(1.0), dec!(2.0)));
5207 let curve_with_point = Curve::new(points);
5208 assert_eq!(curve_with_point.len(), 1);
5209 assert!(!curve_with_point.is_empty());
5210 }
5211
5212 #[test]
5213 fn test_curve_get_points() {
5214 let mut points = BTreeSet::new();
5216 points.insert(Point2D::new(dec!(1.0), dec!(2.0)));
5217 points.insert(Point2D::new(dec!(3.0), dec!(4.0)));
5218
5219 let curve = Curve::new(points);
5220 let retrieved_points = curve.get_points();
5221
5222 assert_eq!(retrieved_points.len(), 2);
5223 assert!(
5224 retrieved_points
5225 .iter()
5226 .any(|p| p.x == dec!(1.0) && p.y == dec!(2.0))
5227 );
5228 assert!(
5229 retrieved_points
5230 .iter()
5231 .any(|p| p.x == dec!(3.0) && p.y == dec!(4.0))
5232 );
5233 }
5234
5235 #[test]
5236 fn test_construct_method_error() {
5237 let result = Curve::construct(ConstructionMethod::Parametric {
5239 f: Box::new(|_| Err(CurveError::generator(std::io::Error::other("Test error")))),
5240 params: ConstructionParams::D2 {
5241 t_start: dec!(0.0),
5242 t_end: dec!(1.0),
5243 steps: 10,
5244 },
5245 });
5246
5247 assert!(result.is_err());
5248 match result {
5249 Err(CurveError::Generator(source)) => {
5250 assert!(source.to_string().contains("Test error"));
5251 }
5252 _ => panic!("Expected Generator"),
5253 }
5254
5255 let result = Curve::construct(ConstructionMethod::Parametric {
5257 f: Box::new(|t| Ok(Point2D::new(t, t * dec!(2.0)))),
5258 params: ConstructionParams::D3 {
5259 x_start: dec!(0.0),
5260 x_end: dec!(1.0),
5261 y_start: dec!(0.0),
5262 y_end: dec!(1.0),
5263 x_steps: 10,
5264 y_steps: 10,
5265 },
5266 });
5267
5268 assert!(result.is_err());
5269 match result {
5270 Err(CurveError::ConstructionError(msg)) => {
5271 assert_eq!(msg, "Invalid parameters");
5272 }
5273 _ => panic!("Expected ConstructionError"),
5274 }
5275 }
5276}
5277
5278#[cfg(test)]
5279mod tests_interpolation_edge_cases {
5280 use crate::curves::{Curve, Point2D};
5281 use crate::geometrics::{AxisOperations, Interpolate, InterpolationType};
5282 use rust_decimal_macros::dec;
5283 use std::collections::BTreeSet;
5284 use tracing::info;
5285
5286 #[test]
5287 fn test_cubic_interpolation_edge_cases() {
5288 let curve = Curve::new(BTreeSet::from_iter(vec![
5290 Point2D::new(dec!(0.0), dec!(0.0)),
5291 Point2D::new(dec!(1.0), dec!(1.0)),
5292 Point2D::new(dec!(2.0), dec!(4.0)),
5293 Point2D::new(dec!(3.0), dec!(9.0)),
5294 ]));
5295
5296 let start_result = curve.interpolate(dec!(0.25), InterpolationType::Cubic);
5298 assert!(start_result.is_ok());
5299
5300 let end_result = curve.interpolate(dec!(2.75), InterpolationType::Cubic);
5302 assert!(end_result.is_ok());
5303 }
5304
5305 #[test]
5306 fn test_spline_interpolation_edge_cases() {
5307 let curve = Curve::new(BTreeSet::from_iter(vec![
5309 Point2D::new(dec!(0.0), dec!(0.0)),
5310 Point2D::new(dec!(1.0), dec!(1.0)),
5311 Point2D::new(dec!(2.0), dec!(4.0)),
5312 Point2D::new(dec!(3.0), dec!(9.0)),
5313 ]));
5314
5315 let exact_result = curve.interpolate(dec!(1.0), InterpolationType::Spline);
5317 assert!(exact_result.is_ok());
5318 let exact_point = exact_result.unwrap();
5319 assert_eq!(exact_point.x, dec!(1.0));
5320 assert_eq!(exact_point.y, dec!(1.0));
5321
5322 let mid_result = curve.interpolate(dec!(1.5), InterpolationType::Spline);
5324 assert!(mid_result.is_ok());
5325 }
5326
5327 #[test]
5328 fn test_axis_operations() {
5329 let curve = Curve::new(BTreeSet::from_iter(vec![
5331 Point2D::new(dec!(0.0), dec!(0.0)),
5332 Point2D::new(dec!(1.0), dec!(1.0)),
5333 Point2D::new(dec!(2.0), dec!(4.0)),
5334 ]));
5335
5336 let indices = curve.get_index_values();
5338 assert_eq!(indices, vec![dec!(0.0), dec!(1.0), dec!(2.0)]);
5339
5340 let values = curve.get_values(dec!(1.0));
5342 assert_eq!(values.len(), 1);
5343 assert_eq!(*values[0], dec!(1.0));
5344
5345 let closest = curve.get_closest_point(&dec!(0.9)).unwrap();
5347 assert_eq!(closest.x, dec!(1.0));
5348 assert_eq!(closest.y, dec!(1.0));
5349
5350 info!("{:?}", curve);
5351 let point = curve.get_point(&dec!(1.0));
5353 assert!(point.is_some());
5354 assert_eq!(point.unwrap().y, dec!(1.0));
5355
5356 let non_existent = curve.get_point(&dec!(1.5));
5358 assert!(non_existent.is_none());
5359 }
5360}
5361
5362#[cfg(test)]
5363mod tests_axis_merge_and_transformations {
5364 use super::*;
5365 use rust_decimal_macros::dec;
5366
5367 #[test]
5368 fn test_merge_axis_index() {
5369 let curve1 = Curve::new(BTreeSet::from_iter(vec![
5371 Point2D::new(dec!(0.0), dec!(0.0)),
5372 Point2D::new(dec!(1.0), dec!(1.0)),
5373 ]));
5374
5375 let curve2 = Curve::new(BTreeSet::from_iter(vec![
5376 Point2D::new(dec!(1.0), dec!(2.0)),
5377 Point2D::new(dec!(2.0), dec!(4.0)),
5378 ]));
5379
5380 let merged = curve1.merge_axis_index(&curve2);
5382 assert_eq!(merged.len(), 1);
5383 assert!(merged.contains(&dec!(1.0)));
5384 }
5385
5386 #[test]
5387 fn test_translate_with_negative_values() {
5388 let curve = Curve::new(BTreeSet::from_iter(vec![
5390 Point2D::new(dec!(0.0), dec!(0.0)),
5391 Point2D::new(dec!(1.0), dec!(1.0)),
5392 ]));
5393
5394 let result = curve.translate(vec![&dec!(-2.0), &dec!(-3.0)]).unwrap();
5395
5396 assert_eq!(result.points.len(), 2);
5397
5398 let translated_points: Vec<_> = result.points.iter().collect();
5399 assert_eq!(translated_points[0].x, dec!(-2.0));
5400 assert_eq!(translated_points[0].y, dec!(-3.0));
5401 assert_eq!(translated_points[1].x, dec!(-1.0));
5402 assert_eq!(translated_points[1].y, dec!(-2.0));
5403 }
5404
5405 #[test]
5406 fn test_intersect_with_empty_curves() {
5407 let curve1 = Curve::new(BTreeSet::new());
5409 let curve2 = Curve::new(BTreeSet::new());
5410
5411 let result = curve1.intersect_with(&curve2).unwrap();
5412 assert!(result.is_empty());
5413 }
5414
5415 #[test]
5416 fn test_derivative_edge_cases() {
5417 let curve = Curve::new(BTreeSet::from_iter(vec![
5419 Point2D::new(dec!(0.0), dec!(0.0)),
5420 Point2D::new(dec!(1.0), dec!(1.0)),
5421 ]));
5422
5423 let result = curve
5425 .derivative_at(&Point2D::new(dec!(0.0), dec!(0.0)))
5426 .unwrap();
5427 assert_eq!(result[0], dec!(0.0));
5428
5429 let result = curve
5431 .derivative_at(&Point2D::new(dec!(0.5), dec!(0.5)))
5432 .unwrap();
5433 assert_eq!(result[0], dec!(1.0));
5434 }
5435
5436 #[test]
5437 fn test_derivative_vertical_line() {
5438 let curve = Curve::new(BTreeSet::from_iter(vec![
5440 Point2D::new(dec!(1.0), dec!(0.0)),
5441 Point2D::new(dec!(1.0), dec!(1.0)),
5442 ]));
5443
5444 let result = curve.derivative_at(&Point2D::new(dec!(1.0), dec!(0.5)));
5446 assert!(result.is_err());
5447 }
5448
5449 #[test]
5450 fn test_extrema_single_point() {
5451 let curve = Curve::new(BTreeSet::from_iter(vec![Point2D::new(
5453 dec!(1.0),
5454 dec!(1.0),
5455 )]));
5456
5457 let (min, max) = curve.extrema().unwrap();
5458 assert_eq!(min, max);
5459 assert_eq!(min.x, dec!(1.0));
5460 assert_eq!(min.y, dec!(1.0));
5461 }
5462
5463 #[test]
5464 fn test_extrema_flat_curve() {
5465 let curve = Curve::new(BTreeSet::from_iter(vec![
5467 Point2D::new(dec!(0.0), dec!(1.0)),
5468 Point2D::new(dec!(1.0), dec!(1.0)),
5469 ]));
5470
5471 let (min, max) = curve.extrema().unwrap();
5472 assert_eq!(min.y, max.y);
5473 assert_eq!(min.y, dec!(1.0));
5474 }
5475
5476 #[test]
5477 fn test_measure_under_single_point() {
5478 let curve = Curve::new(BTreeSet::from_iter(vec![Point2D::new(
5480 dec!(1.0),
5481 dec!(1.0),
5482 )]));
5483
5484 let area = curve.measure_under(&dec!(0.0)).unwrap();
5485 assert_eq!(area, dec!(0.0));
5486 }
5487
5488 #[test]
5489 fn test_measure_under_negative_area() {
5490 let curve = Curve::new(BTreeSet::from_iter(vec![
5492 Point2D::new(dec!(0.0), dec!(-1.0)),
5493 Point2D::new(dec!(1.0), dec!(-2.0)),
5494 ]));
5495
5496 let area = curve.measure_under(&dec!(0.0)).unwrap();
5497 assert!(area > dec!(0.0));
5498 assert_eq!(area, dec!(1.5)); }
5500
5501 #[test]
5502 fn test_extrema_multiple_extrema() {
5503 let curve = Curve::new(BTreeSet::from_iter(vec![
5505 Point2D::new(dec!(0.0), dec!(0.0)),
5506 Point2D::new(dec!(1.0), dec!(2.0)),
5507 Point2D::new(dec!(2.0), dec!(1.0)),
5508 Point2D::new(dec!(3.0), dec!(3.0)),
5509 ]));
5510
5511 let (min, max) = curve.extrema().unwrap();
5512 assert_eq!(min.y, dec!(0.0));
5513 assert_eq!(max.y, dec!(3.0));
5514 }
5515}
5516
5517#[cfg(test)]
5521mod tests_duplicate_abscissa {
5522 use crate::curves::{Curve, Point2D};
5523 use crate::error::InterpolationError;
5524 use crate::geometrics::{AxisOperations, Interpolate, InterpolationType};
5525 use rust_decimal_macros::dec;
5526 use std::collections::BTreeSet;
5527
5528 fn stacked_curve() -> Curve {
5531 Curve::new(BTreeSet::from_iter(vec![
5532 Point2D::new(dec!(0.0), dec!(0.0)),
5533 Point2D::new(dec!(1.0), dec!(1.0)),
5534 Point2D::new(dec!(1.0), dec!(3.0)),
5535 Point2D::new(dec!(2.0), dec!(2.0)),
5536 Point2D::new(dec!(3.0), dec!(5.0)),
5537 ]))
5538 }
5539
5540 #[test]
5541 fn test_curve_new_stacked_abscissa_keeps_both_points() {
5542 let curve = stacked_curve();
5543
5544 assert_eq!(curve.points.len(), 5);
5546 assert_eq!(curve.x_range, (dec!(0.0), dec!(3.0)));
5547 }
5548
5549 #[test]
5550 fn test_curve_get_values_stacked_abscissa_returns_every_ordinate() {
5551 let curve = stacked_curve();
5552
5553 let values: Vec<_> = curve.get_values(dec!(1.0)).into_iter().copied().collect();
5554 assert_eq!(values, vec![dec!(1.0), dec!(3.0)]);
5555 }
5556
5557 #[test]
5560 fn test_curve_get_point_stacked_abscissa_returns_lowest_ordinate() {
5561 let curve = stacked_curve();
5562
5563 let point = curve.get_point(&dec!(1.0)).expect("x = 1 is present");
5564 assert_eq!(*point, Point2D::new(dec!(1.0), dec!(1.0)));
5565 assert!(curve.contains_point(&dec!(1.0)));
5566 }
5567
5568 #[test]
5569 fn test_curve_linear_interpolate_stacked_abscissa_is_degenerate() {
5570 let curve = stacked_curve();
5571
5572 assert!(matches!(
5573 curve.interpolate(dec!(1.0), InterpolationType::Linear),
5574 Err(InterpolationError::DegenerateInterval)
5575 ));
5576 }
5577
5578 #[test]
5579 fn test_curve_cubic_interpolate_stacked_abscissa_is_degenerate() {
5580 let curve = stacked_curve();
5581
5582 assert!(matches!(
5583 curve.interpolate(dec!(1.0), InterpolationType::Cubic),
5584 Err(InterpolationError::DegenerateInterval)
5585 ));
5586 }
5587
5588 #[test]
5589 fn test_curve_spline_interpolate_stacked_abscissa_is_degenerate() {
5590 let curve = stacked_curve();
5591
5592 assert!(matches!(
5593 curve.interpolate(dec!(1.0), InterpolationType::Spline),
5594 Err(InterpolationError::DegenerateInterval)
5595 ));
5596 }
5597
5598 #[test]
5601 fn test_curve_linear_interpolate_away_from_stack_reads_its_own_side() {
5602 let curve = stacked_curve();
5603
5604 let left = curve
5605 .interpolate(dec!(0.5), InterpolationType::Linear)
5606 .expect("0.5 brackets between (0, 0) and the lowest ordinate at x = 1");
5607 assert_eq!(left, Point2D::new(dec!(0.5), dec!(0.5)));
5608
5609 let right = curve
5610 .interpolate(dec!(1.5), InterpolationType::Linear)
5611 .expect("1.5 brackets between the highest ordinate at x = 1 and (2, 2)");
5612 assert_eq!(right, Point2D::new(dec!(1.5), dec!(2.5)));
5613 }
5614
5615 #[test]
5618 fn test_curve_interpolate_single_ordinate_returns_the_stored_point() {
5619 let curve = Curve::new(BTreeSet::from_iter(vec![
5620 Point2D::new(dec!(0.0), dec!(0.0)),
5621 Point2D::new(dec!(1.0), dec!(1.0)),
5622 Point2D::new(dec!(2.0), dec!(4.0)),
5623 Point2D::new(dec!(3.0), dec!(9.0)),
5624 ]));
5625
5626 for interpolation in [
5627 InterpolationType::Linear,
5628 InterpolationType::Cubic,
5629 InterpolationType::Spline,
5630 ] {
5631 let point = curve
5632 .interpolate(dec!(2.0), interpolation)
5633 .expect("x = 2 is a stored abscissa");
5634 assert_eq!(point, Point2D::new(dec!(2.0), dec!(4.0)));
5635 }
5636 }
5637}
5638
5639#[cfg(test)]
5640mod tests_coefficient_of_variation {
5641 use super::*;
5642 use crate::error::MetricsError;
5643 use crate::geometrics::MetricsExtractor;
5644
5645 fn line(values: impl IntoIterator<Item = i64>) -> Curve {
5646 Curve::new(
5647 values
5648 .into_iter()
5649 .enumerate()
5650 .map(|(x, y)| Point2D::new(Decimal::from(x), Decimal::from(y)))
5651 .collect(),
5652 )
5653 }
5654
5655 #[test]
5662 fn test_risk_metrics_coefficient_of_variation_is_std_dev_over_mean() {
5663 let curve = line(1..=5);
5664 let metrics = curve.compute_risk_metrics().unwrap();
5665 assert_eq!(
5666 metrics.coefficient_of_variation,
5667 dec!(0.4714045207910316829338962414)
5668 );
5669 let basic = curve.compute_basic_metrics().unwrap();
5670 assert_eq!(
5671 metrics.coefficient_of_variation,
5672 d_div(basic.std_dev, basic.mean, "test").unwrap()
5673 );
5674 }
5675
5676 #[test]
5678 fn test_risk_metrics_coefficient_of_variation_follows_the_sign_of_the_mean() {
5679 let metrics = line((1..=5).map(|y| -y)).compute_risk_metrics().unwrap();
5680 assert_eq!(
5681 metrics.coefficient_of_variation,
5682 dec!(-0.4714045207910316829338962414)
5683 );
5684 }
5685
5686 #[test]
5689 fn test_risk_metrics_zero_mean_is_an_error() {
5690 assert!(matches!(
5691 line([-1, 0, 1]).compute_risk_metrics(),
5692 Err(MetricsError::ZeroMean {
5693 metric: "coefficient of variation"
5694 })
5695 ));
5696 }
5697}
5698
5699#[cfg(test)]
5700mod tests_risk_volatility {
5701 use super::*;
5702 use crate::geometrics::MetricsExtractor;
5703
5704 fn line(values: impl IntoIterator<Item = i64>) -> Curve {
5705 Curve::new(
5706 values
5707 .into_iter()
5708 .enumerate()
5709 .map(|(x, y)| Point2D::new(Decimal::from(x), Decimal::from(y)))
5710 .collect(),
5711 )
5712 }
5713
5714 #[test]
5721 fn test_risk_metrics_volatility_is_the_population_std_dev() {
5722 let metrics = line(1..=5).compute_risk_metrics().unwrap();
5723 assert_eq!(metrics.volatility, dec!(1.4142135623730950488016887242));
5724 assert_eq!(metrics.value_at_risk, dec!(0.6736186898962586447212220487));
5725 assert_eq!(metrics.expected_shortfall, Decimal::ZERO);
5726 assert_eq!(
5727 metrics.coefficient_of_variation,
5728 dec!(0.4714045207910316829338962414)
5729 );
5730 assert_eq!(metrics.sharpe_ratio, dec!(2.1213203435596425732025330863));
5731 }
5732
5733 #[test]
5738 fn test_risk_metrics_expected_shortfall_sees_the_tail() {
5739 let metrics = line([1, 10, 10, 10, 10, 10, 10, 10, 10, 10])
5740 .compute_risk_metrics()
5741 .unwrap();
5742 assert_eq!(metrics.volatility, dec!(2.7));
5743 assert_eq!(metrics.value_at_risk, dec!(4.6585));
5744 assert_eq!(metrics.expected_shortfall, dec!(1));
5745 assert_eq!(metrics.sharpe_ratio, dec!(3.3703703703703703703703703704));
5746 }
5747
5748 #[test]
5750 fn test_risk_metrics_volatility_matches_basic_std_dev() {
5751 let curve = Curve::new(
5752 (0..=20i64)
5753 .map(|x| Point2D::new(Decimal::from(x), Decimal::from(x * x % 7)))
5754 .collect(),
5755 );
5756 assert_eq!(
5757 curve.compute_risk_metrics().unwrap().volatility,
5758 curve.compute_basic_metrics().unwrap().std_dev
5759 );
5760 }
5761}
5762
5763#[cfg(test)]
5767mod tests_bracket_matches_scan {
5768 use super::*;
5769
5770 fn scan_bracket(curve: &Curve, x: Decimal) -> Result<(usize, usize), InterpolationError> {
5772 let points: Vec<&Point2D> = curve.points.iter().collect();
5773 if points.len() < 2 {
5774 return Err(InterpolationError::EmptyData);
5775 }
5776 let (first, last) = (points[0], points[points.len() - 1]);
5777 if x < first.x || x > last.x {
5778 return Err(InterpolationError::OutOfRange {
5779 target: x.to_string(),
5780 });
5781 }
5782 for (i, pair) in points.windows(2).enumerate() {
5783 if pair[0].x <= x && x <= pair[1].x {
5784 return Ok((i, i + 1));
5785 }
5786 }
5787 Err(InterpolationError::DegenerateInterval)
5788 }
5789
5790 fn scan_bilinear_cell(curve: &Curve, i: usize) -> [usize; 4] {
5792 let far = (i + 2).min(curve.len() - 2);
5793 [i, i + 1, far, far + 1]
5794 }
5795
5796 fn scan_cubic_window(curve: &Curve, i: usize) -> [usize; 4] {
5798 let len = curve.len();
5799 let first = if i == 0 {
5800 0
5801 } else if i == len - 2 {
5802 len - 4
5803 } else {
5804 i - 1
5805 };
5806 [first, first + 1, first + 2, first + 3]
5807 }
5808
5809 fn nth(curve: &Curve, index: usize) -> &Point2D {
5810 curve.points.iter().nth(index).unwrap()
5811 }
5812
5813 fn same_points(curve: &Curve, got: [&Point2D; 4], want: [usize; 4]) -> bool {
5814 got.iter()
5815 .zip(want)
5816 .all(|(point, index)| *point == nth(curve, index))
5817 }
5818
5819 fn check(curve: &Curve, x: Decimal) {
5821 let scanned = scan_bracket(curve, x);
5822 let fast = curve.find_bracket_points(x);
5823 assert_eq!(
5824 format!("{fast:?}"),
5825 format!("{scanned:?}"),
5826 "find_bracket_points at {x} on {curve:?}"
5827 );
5828 let pair = curve.bracket(x);
5829 match &scanned {
5830 Ok((i, j)) => {
5831 let (left, right) = pair.unwrap();
5832 assert_eq!((left, right), (nth(curve, *i), nth(curve, *j)));
5833 if curve.len() >= 4 {
5834 let cell = curve.bilinear_cell(x).unwrap();
5835 assert!(same_points(curve, cell, scan_bilinear_cell(curve, *i)));
5836 let window = curve.cubic_window(x).unwrap();
5837 assert!(same_points(curve, window, scan_cubic_window(curve, *i)));
5838 }
5839 }
5840 Err(e) => assert_eq!(format!("{:?}", pair.unwrap_err()), format!("{e:?}")),
5841 }
5842
5843 let filtered: Vec<&Point2D> = curve.points.iter().filter(|p| p.x == x).collect();
5844 assert_eq!(curve.points_at(x).collect::<Vec<_>>(), filtered);
5845 assert_eq!(curve.contains_point(&x), !filtered.is_empty());
5846 assert_eq!(curve.get_point(&x), filtered.first().copied());
5847 assert_eq!(
5848 curve.get_values(x),
5849 filtered.iter().map(|p| &p.y).collect::<Vec<_>>()
5850 );
5851 let exact = match filtered[..] {
5852 [] => Ok(None),
5853 [only] => Ok(Some(*only)),
5854 _ => Err(InterpolationError::DegenerateInterval),
5855 };
5856 assert_eq!(
5857 format!("{:?}", curve.exact_point_at(x)),
5858 format!("{exact:?}")
5859 );
5860 }
5861
5862 fn probes(curve: &Curve) -> Vec<Decimal> {
5864 let mut xs = vec![dec!(-1000), dec!(1000)];
5865 let abscissas: Vec<Decimal> = curve.points.iter().map(|p| p.x).collect();
5866 for pair in abscissas.windows(2) {
5867 xs.push((pair[0] + pair[1]) / dec!(2));
5868 }
5869 for x in abscissas {
5870 xs.extend([x, x - dec!(0.001), x + dec!(0.001)]);
5871 }
5872 xs
5873 }
5874
5875 fn curve_of(points: &[(i64, i64)]) -> Curve {
5876 Curve::new(
5877 points
5878 .iter()
5879 .map(|&(x, y)| Point2D::new(Decimal::from(x), Decimal::from(y)))
5880 .collect(),
5881 )
5882 }
5883
5884 #[test]
5885 fn edge_curves() {
5886 let cases: Vec<Vec<(i64, i64)>> = vec![
5887 vec![],
5888 vec![(1, 1)],
5889 vec![(1, 1), (1, 2)],
5890 vec![(0, 0), (1, 1)],
5891 vec![(0, 0), (1, 1), (2, 4), (3, 9)],
5892 vec![(0, 0), (0, 5), (1, 1), (2, 4), (3, 9)],
5893 vec![(0, 0), (1, 1), (1, 3), (1, 7), (2, 4), (3, 9)],
5894 vec![(0, 0), (1, 1), (2, 4), (3, 9), (3, 10)],
5895 vec![(0, 0), (0, 1), (0, 2), (0, 3)],
5896 vec![(-3, 2), (-1, 0), (4, 4), (5, -1), (9, 3), (12, 0)],
5897 ];
5898 for points in cases {
5899 let curve = curve_of(&points);
5900 for x in probes(&curve) {
5901 check(&curve, x);
5902 }
5903 }
5904 }
5905
5906 #[test]
5907 fn decimal_scales_compare_by_value() {
5908 let curve = Curve::new(
5910 [
5911 Point2D::new(dec!(0), dec!(0)),
5912 Point2D::new(dec!(1.0), dec!(1)),
5913 Point2D::new(dec!(2.00), dec!(4)),
5914 Point2D::new(dec!(3), dec!(9)),
5915 ]
5916 .into_iter()
5917 .collect(),
5918 );
5919 for x in [
5920 dec!(1),
5921 dec!(1.000),
5922 dec!(2),
5923 dec!(2.0),
5924 dec!(0.0),
5925 dec!(3.00),
5926 ] {
5927 check(&curve, x);
5928 }
5929 }
5930
5931 #[test]
5932 fn random_curves_with_stacked_abscissas() {
5933 let mut state: u64 = 0x2545_f491_4f6c_dd1d;
5936 let mut next = |bound: u64| {
5937 state ^= state << 13;
5938 state ^= state >> 7;
5939 state ^= state << 17;
5940 state % bound
5941 };
5942 for _ in 0..2_000 {
5943 let len = next(14) as usize;
5944 let points: Vec<(i64, i64)> = (0..len)
5945 .map(|_| (next(10) as i64 - 3, next(6) as i64 - 2))
5946 .collect();
5947 let curve = curve_of(&points);
5948 for x in probes(&curve) {
5949 check(&curve, x);
5950 }
5951 }
5952 }
5953}
5954
5955#[cfg(test)]
5959mod tests_spline_matches_legacy {
5960 use super::*;
5961
5962 fn legacy_point_at(
5963 curve: &Curve,
5964 index: usize,
5965 kind: fn(String) -> InterpolationError,
5966 ) -> Result<&Point2D, InterpolationError> {
5967 curve.points.iter().nth(index).ok_or_else(|| {
5968 kind(format!(
5969 "point index {index} is out of bounds for a curve of {} points",
5970 curve.points.len()
5971 ))
5972 })
5973 }
5974
5975 fn legacy_exact_point_at(
5976 curve: &Curve,
5977 x: Decimal,
5978 ) -> Result<Option<Point2D>, InterpolationError> {
5979 let mut at_x = curve.points.iter().filter(|p| p.x == x);
5980 let Some(point) = at_x.next() else {
5981 return Ok(None);
5982 };
5983 if at_x.next().is_some() {
5984 return Err(InterpolationError::DegenerateInterval);
5985 }
5986 Ok(Some(*point))
5987 }
5988
5989 #[rustfmt::skip]
5991fn legacy_spline(curve: &Curve, x: Decimal) -> Result<Point2D, InterpolationError> {
5992 let len = curve.len();
5993
5994 if len < 3 {
5996 return Err(InterpolationError::Spline(
5997 "Need at least three points for spline interpolation".to_string(),
5998 ));
5999 }
6000
6001 let first = legacy_point_at(curve, 0, InterpolationError::Spline)?;
6003 let spline = InterpolationError::Spline;
6004 let last_index = shift_index(len, -1, spline)?;
6005 let last = legacy_point_at(curve, last_index, spline)?;
6006 if x < first.x || x > last.x {
6007 return Err(InterpolationError::Spline(
6008 "x is outside the range of points".to_string(),
6009 ));
6010 }
6011
6012 if let Some(point) = legacy_exact_point_at(curve, x)? {
6016 return Ok(point);
6017 }
6018
6019 let n = len;
6020 let pts: Vec<&Point2D> = curve.points.iter().collect();
6021 let at = |index: usize| -> Result<&Point2D, InterpolationError> {
6022 pts.get(index).copied().ok_or_else(|| {
6023 InterpolationError::Spline(format!(
6024 "point index {index} is out of bounds for a curve of {n} points"
6025 ))
6026 })
6027 };
6028
6029 let mut a = vec![Decimal::ZERO];
6033 let mut b = vec![Decimal::ONE];
6034 let mut c = vec![Decimal::ZERO];
6035 let mut r = vec![Decimal::ZERO];
6036
6037 for i in 1..last_index {
6039 let prev = at(shift_index(i, -1, spline)?)?;
6040 let curr = at(i)?;
6041 let next = at(shift_index(i, 1, spline)?)?;
6042
6043 let hi = d_sub(curr.x, prev.x, "Curve::spline_interpolate::hi")
6044 .map_err(interp_err(InterpolationError::Spline))?;
6045 let hi1 = d_sub(next.x, curr.x, "Curve::spline_interpolate::hi1")
6046 .map_err(interp_err(InterpolationError::Spline))?;
6047 if hi.is_zero() || hi1.is_zero() {
6050 return Err(InterpolationError::DegenerateInterval);
6051 }
6052
6053 let band = d_add(hi, hi1, "Curve::spline_interpolate::band")
6054 .map_err(interp_err(InterpolationError::Spline))?;
6055 let diag = d_mul(dec!(2), band, "Curve::spline_interpolate::diag")
6056 .map_err(interp_err(InterpolationError::Spline))?;
6057
6058 let rise_next = d_sub(next.y, curr.y, "Curve::spline_interpolate::rise_next")
6059 .map_err(interp_err(InterpolationError::Spline))?;
6060 let slope_next = d_div(rise_next, hi1, "Curve::spline_interpolate::slope_next")
6061 .map_err(interp_err(InterpolationError::Spline))?;
6062 let rise_prev = d_sub(curr.y, prev.y, "Curve::spline_interpolate::rise_prev")
6063 .map_err(interp_err(InterpolationError::Spline))?;
6064 let slope_prev = d_div(rise_prev, hi, "Curve::spline_interpolate::slope_prev")
6065 .map_err(interp_err(InterpolationError::Spline))?;
6066 let curvature = d_sub(
6067 slope_next,
6068 slope_prev,
6069 "Curve::spline_interpolate::curvature",
6070 )
6071 .map_err(interp_err(InterpolationError::Spline))?;
6072 let rhs = d_mul(dec!(6), curvature, "Curve::spline_interpolate::rhs")
6073 .map_err(interp_err(InterpolationError::Spline))?;
6074
6075 a.push(hi);
6076 b.push(diag);
6077 c.push(hi1);
6078 r.push(rhs);
6079 }
6080
6081 a.push(Decimal::ZERO);
6083 b.push(Decimal::ONE);
6084 c.push(Decimal::ZERO);
6085 r.push(Decimal::ZERO);
6086
6087 let mut m = vec![Decimal::ZERO; n];
6089
6090 for i in 1..last_index {
6091 let prev = shift_index(i, -1, spline)?;
6092 let a_i = band_at(&a, i, "a")?;
6093 let b_prev = band_at(&b, prev, "b")?;
6094 if b_prev.is_zero() {
6095 return Err(InterpolationError::DegenerateInterval);
6096 }
6097 let w = d_div(a_i, b_prev, "Curve::spline_interpolate::w")
6098 .map_err(interp_err(InterpolationError::Spline))?;
6099
6100 let c_prev = band_at(&c, prev, "c")?;
6101 let wc = d_mul(w, c_prev, "Curve::spline_interpolate::wc")
6102 .map_err(interp_err(InterpolationError::Spline))?;
6103 let b_i = band_at(&b, i, "b")?;
6104 *band_at_mut(&mut b, i, "b")? = d_sub(b_i, wc, "Curve::spline_interpolate::b_i")
6105 .map_err(interp_err(InterpolationError::Spline))?;
6106
6107 let r_prev = band_at(&r, prev, "r")?;
6108 let wr = d_mul(w, r_prev, "Curve::spline_interpolate::wr")
6109 .map_err(interp_err(InterpolationError::Spline))?;
6110 let r_i = band_at(&r, i, "r")?;
6111 *band_at_mut(&mut r, i, "r")? = d_sub(r_i, wr, "Curve::spline_interpolate::r_i")
6112 .map_err(interp_err(InterpolationError::Spline))?;
6113 }
6114
6115 let b_last = band_at(&b, last_index, "b")?;
6116 if b_last.is_zero() {
6117 return Err(InterpolationError::DegenerateInterval);
6118 }
6119 let r_last = band_at(&r, last_index, "r")?;
6120 *band_at_mut(&mut m, last_index, "m")? =
6121 d_div(r_last, b_last, "Curve::spline_interpolate::m_last")
6122 .map_err(interp_err(InterpolationError::Spline))?;
6123 for i in (1..last_index).rev() {
6124 let c_i = band_at(&c, i, "c")?;
6125 let m_next = band_at(&m, shift_index(i, 1, spline)?, "m")?;
6126 let cm = d_mul(c_i, m_next, "Curve::spline_interpolate::cm")
6127 .map_err(interp_err(InterpolationError::Spline))?;
6128 let r_i = band_at(&r, i, "r")?;
6129 let numerator = d_sub(r_i, cm, "Curve::spline_interpolate::m_numerator")
6130 .map_err(interp_err(InterpolationError::Spline))?;
6131 let b_i = band_at(&b, i, "b")?;
6132 if b_i.is_zero() {
6133 return Err(InterpolationError::DegenerateInterval);
6134 }
6135 *band_at_mut(&mut m, i, "m")? = d_div(numerator, b_i, "Curve::spline_interpolate::m_i")
6136 .map_err(interp_err(InterpolationError::Spline))?;
6137 }
6138
6139 let mut segment = None;
6141 for i in 0..last_index {
6142 if at(i)?.x <= x && x <= at(shift_index(i, 1, spline)?)?.x {
6143 segment = Some(i);
6144 break;
6145 }
6146 }
6147
6148 let segment = segment.ok_or_else(|| {
6149 InterpolationError::Spline("Could not find valid segment for interpolation".to_string())
6150 })?;
6151
6152 let left = at(segment)?;
6154 let right = at(shift_index(segment, 1, spline)?)?;
6155 let h = d_sub(right.x, left.x, "Curve::spline_interpolate::h")
6156 .map_err(interp_err(InterpolationError::Spline))?;
6157 if h.is_zero() {
6158 return Err(InterpolationError::DegenerateInterval);
6159 }
6160 let dx = d_sub(right.x, x, "Curve::spline_interpolate::dx")
6161 .map_err(interp_err(InterpolationError::Spline))?;
6162 let dx1 = d_sub(x, left.x, "Curve::spline_interpolate::dx1")
6163 .map_err(interp_err(InterpolationError::Spline))?;
6164
6165 let m_left = band_at(&m, segment, "m")?;
6166 let m_right = band_at(&m, shift_index(segment, 1, spline)?, "m")?;
6167 let six_h = d_mul(dec!(6), h, "Curve::spline_interpolate::six_h")
6168 .map_err(interp_err(InterpolationError::Spline))?;
6169
6170 let left_cube = cube_scaled(m_left, dx, six_h, "Curve::spline_interpolate::left_cube")?;
6171 let right_cube = cube_scaled(m_right, dx1, six_h, "Curve::spline_interpolate::right_cube")?;
6172 let left_linear = linear_term(left.y, m_left, h, dx, "Curve::spline_interpolate::left")?;
6173 let right_linear =
6174 linear_term(right.y, m_right, h, dx1, "Curve::spline_interpolate::right")?;
6175
6176 let y = d_sum_iter(
6177 [left_cube, right_cube, left_linear, right_linear],
6178 "Curve::spline_interpolate::y",
6179 )
6180 .map_err(interp_err(InterpolationError::Spline))?;
6181
6182 Ok(Point2D::new(x, y))
6183}
6184
6185 fn check(curve: &Curve) {
6186 let spline = CurveSpline::new(curve);
6187 let mut xs = vec![dec!(-1000), dec!(1000), dec!(0.123456789)];
6188 let knots: Vec<Decimal> = curve.points.iter().map(|p| p.x).collect();
6189 for pair in knots.windows(2) {
6190 xs.push((pair[0] + pair[1]) / dec!(2));
6191 xs.push(pair[0] + (pair[1] - pair[0]) / dec!(3));
6192 }
6193 for x in knots {
6194 xs.extend([x, x - dec!(0.001), x + dec!(0.001)]);
6195 }
6196 for x in xs {
6197 let legacy = format!("{:?}", legacy_spline(curve, x));
6198 assert_eq!(
6199 format!("{:?}", curve.spline_interpolate(x)),
6200 legacy,
6201 "spline_interpolate at {x} on {curve:?}"
6202 );
6203 assert_eq!(
6204 format!("{:?}", spline.interpolate(x)),
6205 legacy,
6206 "CurveSpline at {x} on {curve:?}"
6207 );
6208 }
6209 }
6210
6211 fn curve_of(points: &[(Decimal, Decimal)]) -> Curve {
6212 Curve::new(points.iter().map(|&(x, y)| Point2D::new(x, y)).collect())
6213 }
6214
6215 #[test]
6216 fn edge_curves() {
6217 let d = |v: i64| Decimal::from(v);
6218 let cases: Vec<Vec<(Decimal, Decimal)>> = vec![
6219 vec![],
6220 vec![(d(1), d(1))],
6221 vec![(d(0), d(0)), (d(1), d(1))],
6222 vec![(d(0), d(0)), (d(1), d(1)), (d(2), d(4))],
6223 vec![(d(0), d(0)), (d(1), d(1)), (d(1), d(3)), (d(2), d(4))],
6224 vec![(d(0), d(0)), (d(0), d(2)), (d(1), d(1)), (d(2), d(4))],
6225 vec![(d(0), d(0)), (d(1), d(1)), (d(2), d(4)), (d(2), d(5))],
6226 (0..9)
6228 .map(|i| {
6229 let x = dec!(10) / dec!(7) * d(i);
6230 (x, (x - dec!(5)) / dec!(7) * (x - dec!(5)) / dec!(3))
6231 })
6232 .collect(),
6233 vec![
6235 (d(-3), d(2)),
6236 (d(-1), d(0)),
6237 (dec!(-0.999), d(1)),
6238 (d(4), d(4)),
6239 (d(9), d(-3)),
6240 ],
6241 ];
6242 for points in cases {
6243 check(&curve_of(&points));
6244 }
6245 }
6246
6247 #[test]
6248 fn overflowing_solve_reports_the_same_error() {
6249 let curve = curve_of(&[
6250 (dec!(0), dec!(0)),
6251 (dec!(0.0000000000000000000000000001), Decimal::MAX),
6252 (dec!(1), Decimal::MIN),
6253 (dec!(2), dec!(1)),
6254 ]);
6255 assert!(legacy_spline(&curve, dec!(0.5)).is_err());
6256 check(&curve);
6257 }
6258
6259 #[test]
6260 fn random_curves() {
6261 let mut state: u64 = 0xd1b5_4a32_d192_ed03;
6262 let mut next = |bound: u64| {
6263 state ^= state << 13;
6264 state ^= state >> 7;
6265 state ^= state << 17;
6266 state % bound
6267 };
6268 for _ in 0..300 {
6269 let len = next(16) as usize;
6270 let points: Vec<(Decimal, Decimal)> = (0..len)
6271 .map(|_| {
6272 (
6273 Decimal::new(next(400) as i64 - 100, 1),
6274 Decimal::new(next(20_000) as i64 - 10_000, 3),
6275 )
6276 })
6277 .collect();
6278 check(&curve_of(&points));
6279 }
6280 }
6281
6282 #[test]
6283 fn merge_resampling_matches_per_read_splines() {
6284 let a = curve_of(
6287 &(0..12)
6288 .map(|i| (Decimal::from(i), Decimal::from(i * i % 7)))
6289 .collect::<Vec<_>>(),
6290 );
6291 let b = curve_of(
6292 &(0..9)
6293 .map(|i| {
6294 (
6295 dec!(0.5) + Decimal::from(i) * dec!(1.3),
6296 Decimal::from(i % 4),
6297 )
6298 })
6299 .collect::<Vec<_>>(),
6300 );
6301 let (ra, rb) = a
6302 .merge_axis_interpolate(&b, InterpolationType::Spline)
6303 .unwrap();
6304 for (resampled, source) in [(&ra, &a), (&rb, &b)] {
6305 for p in &resampled.points {
6306 let want = match source.get_point(&p.x) {
6307 Some(point) => *point,
6308 None => legacy_spline(source, p.x).unwrap(),
6309 };
6310 assert_eq!(*p, want);
6311 }
6312 }
6313 }
6314}
6315
6316#[cfg(test)]
6320mod tests_intersect_matches_all_pairs {
6321 use super::*;
6322
6323 fn check(a: &Curve, b: &Curve) {
6324 for (left, right) in [(a, b), (b, a), (a, a)] {
6325 let fast = left.intersect_with(right);
6326 let all = intersect_all_pairs(left, right, Decimal::new(1, 6));
6327 assert_eq!(
6328 format!("{fast:?}"),
6329 format!("{all:?}"),
6330 "{left:?} with {right:?}"
6331 );
6332 }
6333 }
6334
6335 fn curve_of(points: &[(Decimal, Decimal)]) -> Curve {
6336 Curve::new(points.iter().map(|&(x, y)| Point2D::new(x, y)).collect())
6337 }
6338
6339 #[test]
6340 fn samples_within_tolerance_are_matched_in_pair_order() {
6341 let tiny = dec!(0.0000005);
6342 let a = curve_of(&[
6343 (dec!(0), dec!(1)),
6344 (dec!(1), dec!(2)),
6345 (dec!(1), dec!(5)),
6346 (dec!(2), dec!(3)),
6347 ]);
6348 let b = curve_of(&[
6349 (dec!(0) + tiny, dec!(1) - tiny),
6350 (dec!(1) - tiny, dec!(2)),
6351 (dec!(1), dec!(2) + tiny),
6352 (dec!(1) + dec!(0.000001), dec!(5)),
6353 (dec!(2.000001), dec!(3)),
6354 ]);
6355 check(&a, &b);
6356 let found = match a.intersect_with(&b) {
6357 Ok(found) => found,
6358 Err(e) => panic!("intersect_with failed: {e:?}"),
6359 };
6360 assert_eq!(
6363 found,
6364 vec![
6365 Point2D::new(dec!(0), dec!(1)),
6366 Point2D::new(dec!(1), dec!(2)),
6367 Point2D::new(dec!(1), dec!(2)),
6368 ]
6369 );
6370 }
6371
6372 #[test]
6373 fn crossings_between_samples_are_not_reported() {
6374 let rising = curve_of(&[(dec!(0), dec!(0)), (dec!(2), dec!(2))]);
6375 let falling = curve_of(&[(dec!(0), dec!(2)), (dec!(2), dec!(0))]);
6376 assert!(matches!(rising.intersect_with(&falling), Ok(found) if found.is_empty()));
6377 }
6378
6379 #[test]
6380 fn empty_and_disjoint_curves() {
6381 let empty = Curve::default();
6382 let a = curve_of(&[(dec!(0), dec!(0)), (dec!(1), dec!(1))]);
6383 let far = curve_of(&[(dec!(10), dec!(0)), (dec!(11), dec!(1))]);
6384 check(&empty, &a);
6385 check(&a, &far);
6386 check(&empty, &empty);
6387 }
6388
6389 #[test]
6390 fn overflowing_abscissa_difference_reports_the_same_pair() {
6391 let a = curve_of(&[
6392 (dec!(-70000000000000000000000000000), dec!(0)),
6393 (dec!(0), dec!(0)),
6394 ]);
6395 let b = curve_of(&[
6396 (dec!(0), dec!(0)),
6397 (dec!(70000000000000000000000000000), dec!(1)),
6398 ]);
6399 assert!(a.intersect_with(&b).is_err());
6400 check(&a, &b);
6401 let c = curve_of(&[
6403 (dec!(0), dec!(-70000000000000000000000000000)),
6404 (dec!(1), dec!(0)),
6405 ]);
6406 let d = curve_of(&[
6407 (dec!(0), dec!(70000000000000000000000000000)),
6408 (dec!(1), dec!(0)),
6409 ]);
6410 assert!(c.intersect_with(&d).is_err());
6411 check(&c, &d);
6412 }
6413
6414 #[test]
6415 fn random_curves_with_stacks_and_near_misses() {
6416 let mut state: u64 = 0xa076_1d64_78bd_642f;
6417 let mut next = |bound: u64| {
6418 state ^= state << 13;
6419 state ^= state >> 7;
6420 state ^= state << 17;
6421 state % bound
6422 };
6423 let curve = |next: &mut dyn FnMut(u64) -> u64| {
6426 let len = next(30) as usize;
6427 let points: Vec<(Decimal, Decimal)> = (0..len)
6428 .map(|_| {
6429 (
6430 Decimal::new(next(12) as i64, 7) * dec!(5),
6431 Decimal::new(next(4) as i64, 7) * dec!(5),
6432 )
6433 })
6434 .collect();
6435 curve_of(&points)
6436 };
6437 for _ in 0..1_000 {
6438 let a = curve(&mut next);
6439 let b = curve(&mut next);
6440 check(&a, &b);
6441 }
6442 }
6443}