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}