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