decline_curve_analysis/
lib.rs

1use std::marker::PhantomData;
2use thiserror::Error;
3
4mod decline_rate;
5mod delay;
6mod exponential;
7mod flat;
8mod harmonic;
9mod hyperbolic;
10mod linear;
11
12pub use decline_rate::*;
13pub use delay::*;
14pub use exponential::*;
15pub use flat::*;
16pub use harmonic::*;
17pub use hyperbolic::*;
18pub use linear::*;
19
20/// An error type for invalid parameters.
21#[derive(Clone, Debug, Error, Eq, PartialEq)]
22pub enum DeclineCurveAnalysisError {
23    #[error("decline rate too high")]
24    DeclineRateTooHigh,
25    #[error("decline rate has wrong sign")]
26    DeclineRateWrongSign,
27    #[error("cannot solve decline")]
28    CannotSolveDecline,
29}
30
31/// The production rate for a specific time unit.
32#[derive(Debug, Clone, Copy, PartialEq)]
33pub struct ProductionRate<Time: DeclineTimeUnit> {
34    value: f64,
35    _time: PhantomData<Time>,
36}
37
38impl<Time: DeclineTimeUnit> ProductionRate<Time> {
39    pub const fn new(value: f64) -> Self {
40        Self {
41            value,
42            _time: PhantomData,
43        }
44    }
45
46    pub const fn value(&self) -> f64 {
47        self.value
48    }
49}
50
51impl Into<ProductionRate<AverageDaysTime>> for ProductionRate<AverageYearsTime> {
52    fn into(self) -> ProductionRate<AverageDaysTime> {
53        ProductionRate::new(self.value * AverageDaysTime::LENGTH / AverageYearsTime::LENGTH)
54    }
55}
56
57impl Into<ProductionRate<AverageYearsTime>> for ProductionRate<AverageDaysTime> {
58    fn into(self) -> ProductionRate<AverageYearsTime> {
59        ProductionRate::new(self.value * AverageYearsTime::LENGTH / AverageDaysTime::LENGTH)
60    }
61}
62
63#[derive(Clone, Copy, PartialEq, Eq)]
64enum DeclineRateSignValidation {
65    Continue,
66    ZeroDuration,
67}
68
69fn validate_decline_rate_sign(
70    decline_rate: f64,
71    initial_rate: f64,
72    final_rate: f64,
73) -> Result<DeclineRateSignValidation, DeclineCurveAnalysisError> {
74    if initial_rate < final_rate {
75        if decline_rate > 0. {
76            return Err(DeclineCurveAnalysisError::DeclineRateWrongSign);
77        }
78    } else if initial_rate > final_rate {
79        if decline_rate < 0. {
80            return Err(DeclineCurveAnalysisError::DeclineRateWrongSign);
81        }
82    } else {
83        // If the rates are equal, the duration is zero.
84        return Ok(DeclineRateSignValidation::ZeroDuration);
85    }
86
87    Ok(DeclineRateSignValidation::Continue)
88}