Skip to main content

growth_and_decay/
lib.rs

1use std::ops::Neg;
2
3/// Represents the parameters and results of an exponential growth or decay process.
4///
5/// This struct is used to calculate the final value, rate, or time for exponential growth or decay.
6/// It supports modifying the final value or time and recalculating the other parameters accordingly.
7#[derive(Clone)]
8pub struct ExponentialChange {
9    /// The initial value (principal) at the start of the process.
10    pub principal: f64,
11    /// The final value after the specified time.
12    pub final_value: f64,
13    /// The growth or decay rate (as a fraction, e.g., 0.025 for 2.5%).
14    pub rate: f64,
15    /// The time over which the growth or decay occurs.
16    pub time: f64,
17}
18
19/// Represents the parameters and results of an exponential decay process.
20///
21/// This struct is used to calculate the decay constant, time, and ratios for processes such as radioactive decay.
22pub struct GrowthOrDecayRatios {
23    /// The final ratio (Rt) after the specified time.
24    pub rt: f64,
25    /// The initial ratio (R0) at the start of the process.
26    pub r0: f64,
27    /// The decay constant, calculated based on the half-life or decay years.
28    pub decay_constant: f64,
29    /// The time elapsed during the decay process.
30    pub time: f64,
31    /// The half-life or characteristic decay time of the process.
32    pub decay_years: f64,
33}
34
35impl ExponentialChange {
36    /// Creates a new instance of `ExponentialChange`.
37    ///
38    /// # Parameters
39    /// - `principal`: The initial value at the start of the process.
40    /// - `final_value`: The final value after the specified time. Can be `None` if the rate is provided.
41    /// - `rate`: The growth or decay rate. Can be `None` if the final value is provided.
42    /// - `time`: The time over which the growth or decay occurs.
43    ///
44    /// # Panics
45    /// Panics if both `final_value` and `rate` are not provided, as at least one must be specified.
46    ///
47    /// # Returns
48    /// A new instance of `ExponentialChange` with calculated values.
49    pub fn new(
50        principal: f64,
51        final_value: impl Into<Option<f64>>,
52        rate: impl Into<Option<f64>>,
53        time: f64,
54    ) -> Self {
55        let final_value = final_value.into();
56        let rate = rate.into();
57
58        // If neither final_value nor rate is provided, panic.
59        assert!(
60            !(final_value.is_none() && rate.is_none()),
61            "Either final_value or rate must be provided."
62        );
63
64        // If the final value is provided, calculate the rate.
65        let rate = rate.unwrap_or_else(|| {
66            if final_value < Some(principal) {
67                // Rearranged to solve for a negative rate
68                (-(final_value.unwrap() / principal).ln() / time).neg()
69            } else {
70                (final_value.unwrap() / principal).powf(1.0 / time) - 1.0
71            }
72        });
73
74        // If the rate is provided, calculate the final value.
75        let final_value = final_value.unwrap_or_else(|| principal * (1.0 + rate).powf(time));
76
77        Self {
78            principal,
79            final_value,
80            rate,
81            time,
82        }
83    }
84
85    /// Modifies the final value of the instance and recalculates the time required.
86    ///
87    /// # Parameters
88    /// - `new_final_value`: The new final value to set.
89    ///
90    /// # Behavior
91    /// Updates the `final_value` field and recalculates the `time` field based on the current rate.
92    pub fn modify_final_value(&mut self, new_final_value: f64) {
93        // Update the final value
94        self.final_value = new_final_value;
95
96        // Recalculate the time using the correct formula
97        self.time = ((self.final_value / self.principal).ln() / self.rate).abs(); // Time cannot be negative
98    }
99
100    /// Modifies the time of the instance and recalculates the final value.
101    ///
102    /// # Parameters
103    /// - `new_time`: The new time to set.
104    ///
105    /// # Behavior
106    /// Updates the `time` field and recalculates the `final_value` field based on the current rate.
107    pub fn modify_final_time(&mut self, new_time: f64) {
108        // Update the time and recalculate the final value.
109        self.time = new_time;
110
111        // Recalculate the final value using the appropriate formula
112        self.final_value = if self.rate < 0.0 {
113            self.principal * (self.rate * self.time).exp()
114        } else {
115            self.principal * (1.0 + self.rate).powf(self.time)
116        };
117    }
118}
119
120impl GrowthOrDecayRatios {
121    /// Creates a new instance of `GrowthOrDecayRatios`.
122    ///
123    /// # Parameters
124    /// - `rt`: The final ratio after the specified time. Can be `None` if the time is provided.
125    /// - `r0`: The initial ratio at the start of the process.
126    /// - `decay_years`: The half-life or characteristic decay time of the process.
127    /// - `time`: The time elapsed during the decay process. Can be `None` if the final ratio is provided.
128    ///
129    /// # Panics
130    /// Panics if both `rt` and `time` are not provided, as at least one must be specified.
131    ///
132    /// # Returns
133    /// A new instance of `GrowthOrDecayRatios` with calculated values.
134    pub fn new(
135        rt: impl Into<Option<f64>>,
136        r0: f64,
137        decay_years: f64,
138        time: impl Into<Option<f64>>,
139    ) -> Self {
140        let rt = rt.into();
141        let time = time.into();
142
143        // Assert that either nt or time is provided.
144        assert!(
145            rt.is_some() || time.is_some(),
146            "Either nt or time must be provided."
147        );
148
149        // Use the provided time or calculate it from the ratio
150        let time = time.map_or_else(
151            || {
152                let ratio = rt.unwrap() / r0;
153                -(ratio.ln()) * decay_years
154            },
155            |time_value| time_value,
156        );
157
158        // Calculate the final ratio using the formula R = R0 * e^(-t / decay_years)
159        let nt = rt.unwrap_or_else(|| r0 * (-time / decay_years).exp());
160
161        // Calculate the decay constant for informational purposes
162        let decay_constant = (2.0_f64).ln() / decay_years;
163
164        Self {
165            rt: nt,
166            r0,
167            decay_constant,
168            time,
169            decay_years,
170        }
171    }
172}