1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[doc(inline)]
7pub use linear::{LinearEquation, solve_linear};
8#[cfg(feature = "polynomial")]
9#[doc(inline)]
10pub use polynomial::solve_polynomial_degree_1_or_2;
11#[doc(inline)]
12pub use quadratic::{QuadraticEquation, solve_quadratic};
13#[doc(inline)]
14pub use root::{RootSolver, Roots};
15#[doc(inline)]
16pub use system::{LinearSystem2, solve_2x2};
17
18pub mod root {
20 #[derive(Debug, Clone, PartialEq)]
22 pub enum Roots {
23 None,
25 One(f64),
27 Two(f64, f64),
29 Infinite,
31 }
32
33 pub trait RootSolver {
35 type Output;
37
38 fn solve(&self) -> Self::Output;
40 }
41}
42
43pub mod linear {
45 use crate::root::{RootSolver, Roots};
46
47 #[derive(Debug, Clone, Copy, PartialEq)]
49 pub struct LinearEquation {
50 pub a: f64,
52 pub b: f64,
54 }
55
56 impl LinearEquation {
57 #[must_use]
59 pub const fn new(a: f64, b: f64) -> Self {
60 Self { a, b }
61 }
62 }
63
64 impl RootSolver for LinearEquation {
65 type Output = Roots;
66
67 fn solve(&self) -> Self::Output {
68 solve_linear(self.a, self.b)
69 }
70 }
71
72 #[must_use]
77 pub fn solve_linear(a: f64, b: f64) -> Roots {
78 if a == 0.0 {
79 if b == 0.0 {
80 Roots::Infinite
81 } else {
82 Roots::None
83 }
84 } else {
85 let root = -b / a;
86
87 if root.is_finite() {
88 Roots::One(root)
89 } else {
90 Roots::None
91 }
92 }
93 }
94}
95
96pub mod quadratic {
98 use crate::{
99 linear::solve_linear,
100 root::{RootSolver, Roots},
101 };
102
103 #[derive(Debug, Clone, Copy, PartialEq)]
105 pub struct QuadraticEquation {
106 pub a: f64,
108 pub b: f64,
110 pub c: f64,
112 }
113
114 impl QuadraticEquation {
115 #[must_use]
117 pub const fn new(a: f64, b: f64, c: f64) -> Self {
118 Self { a, b, c }
119 }
120 }
121
122 impl RootSolver for QuadraticEquation {
123 type Output = Roots;
124
125 fn solve(&self) -> Self::Output {
126 solve_quadratic(self.a, self.b, self.c)
127 }
128 }
129
130 #[must_use]
136 pub fn solve_quadratic(a: f64, b: f64, c: f64) -> Roots {
137 if a == 0.0 {
138 return solve_linear(b, c);
139 }
140
141 let discriminant = b.mul_add(b, -4.0 * a * c);
142 if !discriminant.is_finite() {
143 return Roots::None;
144 }
145
146 if discriminant < 0.0 {
147 return Roots::None;
148 }
149
150 if discriminant == 0.0 {
151 let root = -b / (2.0 * a);
152 return if root.is_finite() {
153 Roots::One(root)
154 } else {
155 Roots::None
156 };
157 }
158
159 let sqrt_discriminant = discriminant.sqrt();
160 let first_root = (-b - sqrt_discriminant) / (2.0 * a);
161 let second_root = (-b + sqrt_discriminant) / (2.0 * a);
162
163 if !first_root.is_finite() || !second_root.is_finite() {
164 return Roots::None;
165 }
166
167 if first_root <= second_root {
168 Roots::Two(first_root, second_root)
169 } else {
170 Roots::Two(second_root, first_root)
171 }
172 }
173}
174
175pub mod system {
177 use crate::root::RootSolver;
178
179 #[derive(Debug, Clone, Copy, PartialEq)]
185 pub struct LinearSystem2 {
186 pub a11: f64,
188 pub a12: f64,
190 pub b1: f64,
192 pub a21: f64,
194 pub a22: f64,
196 pub b2: f64,
198 }
199
200 impl LinearSystem2 {
201 #[must_use]
203 pub const fn new(a11: f64, a12: f64, b1: f64, a21: f64, a22: f64, b2: f64) -> Self {
204 Self {
205 a11,
206 a12,
207 b1,
208 a21,
209 a22,
210 b2,
211 }
212 }
213 }
214
215 impl RootSolver for LinearSystem2 {
216 type Output = Option<(f64, f64)>;
217
218 fn solve(&self) -> Self::Output {
219 solve_2x2(self.a11, self.a12, self.b1, self.a21, self.a22, self.b2)
220 }
221 }
222
223 #[must_use]
228 pub fn solve_2x2(
229 a11: f64,
230 a12: f64,
231 b1: f64,
232 a21: f64,
233 a22: f64,
234 b2: f64,
235 ) -> Option<(f64, f64)> {
236 let determinant = a11 * a22 - a12 * a21;
237 if determinant == 0.0 || !determinant.is_finite() {
238 return None;
239 }
240
241 let x = (b1 * a22 - a12 * b2) / determinant;
242 let y = (a11 * b2 - b1 * a21) / determinant;
243
244 if x.is_finite() && y.is_finite() {
245 Some((x, y))
246 } else {
247 None
248 }
249 }
250}
251
252#[cfg(feature = "polynomial")]
253pub mod polynomial {
255 use crate::{linear::solve_linear, quadratic::solve_quadratic, root::Roots};
256 use use_polynomial::Polynomial;
257
258 #[must_use]
263 pub fn solve_polynomial_degree_1_or_2(polynomial: &Polynomial) -> Option<Roots> {
264 match polynomial.degree() {
265 None => Some(Roots::Infinite),
266 Some(0) => {
267 let constant = polynomial.coefficients().first().copied().unwrap_or(0.0);
268 Some(if constant == 0.0 {
269 Roots::Infinite
270 } else {
271 Roots::None
272 })
273 },
274 Some(1) => Some(solve_linear(
275 polynomial.coefficients()[1],
276 polynomial.coefficients()[0],
277 )),
278 Some(2) => Some(solve_quadratic(
279 polynomial.coefficients()[2],
280 polynomial.coefficients()[1],
281 polynomial.coefficients()[0],
282 )),
283 Some(_) => None,
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::{
291 LinearEquation, LinearSystem2, QuadraticEquation, RootSolver, Roots, solve_2x2,
292 solve_linear, solve_quadratic,
293 };
294
295 #[test]
296 fn solves_linear_equation_with_one_root() {
297 assert_eq!(solve_linear(2.0, -4.0), Roots::One(2.0));
298 }
299
300 #[test]
301 fn solves_linear_equation_with_no_roots() {
302 assert_eq!(solve_linear(0.0, 5.0), Roots::None);
303 }
304
305 #[test]
306 fn solves_linear_equation_with_infinite_roots() {
307 assert_eq!(solve_linear(0.0, 0.0), Roots::Infinite);
308 }
309
310 #[test]
311 fn solves_quadratic_equation_with_two_real_roots() {
312 assert_eq!(solve_quadratic(1.0, -3.0, 2.0), Roots::Two(1.0, 2.0));
313 }
314
315 #[test]
316 fn solves_quadratic_equation_with_one_repeated_root() {
317 assert_eq!(solve_quadratic(1.0, -2.0, 1.0), Roots::One(1.0));
318 }
319
320 #[test]
321 fn solves_quadratic_equation_with_no_real_roots() {
322 assert_eq!(solve_quadratic(1.0, 0.0, 1.0), Roots::None);
323 }
324
325 #[test]
326 fn quadratic_falls_back_to_linear_solving() {
327 assert_eq!(solve_quadratic(0.0, 2.0, -4.0), Roots::One(2.0));
328 }
329
330 #[test]
331 fn solves_2x2_system_with_one_solution() {
332 assert_eq!(solve_2x2(2.0, 1.0, 5.0, 1.0, -1.0, 1.0), Some((2.0, 1.0)));
333 }
334
335 #[test]
336 fn rejects_singular_2x2_system() {
337 assert_eq!(solve_2x2(1.0, 2.0, 3.0, 2.0, 4.0, 6.0), None);
338 }
339
340 #[test]
341 fn rejects_non_finite_determinant() {
342 assert_eq!(solve_2x2(f64::INFINITY, 0.0, 1.0, 0.0, 1.0, 2.0), None);
343 }
344
345 #[test]
346 fn linear_equation_struct_solves() {
347 let equation = LinearEquation::new(2.0, -4.0);
348
349 assert_eq!(equation.solve(), Roots::One(2.0));
350 }
351
352 #[test]
353 fn quadratic_equation_struct_solves() {
354 let equation = QuadraticEquation::new(1.0, -3.0, 2.0);
355
356 assert_eq!(equation.solve(), Roots::Two(1.0, 2.0));
357 }
358
359 #[test]
360 fn linear_system_struct_solves() {
361 let system = LinearSystem2::new(2.0, 1.0, 5.0, 1.0, -1.0, 1.0);
362
363 assert_eq!(system.solve(), Some((2.0, 1.0)));
364 }
365
366 #[cfg(feature = "polynomial")]
367 #[test]
368 fn solves_supported_polynomials() {
369 use super::solve_polynomial_degree_1_or_2;
370 use use_polynomial::Polynomial;
371
372 assert_eq!(
373 solve_polynomial_degree_1_or_2(&Polynomial::new(vec![2.0, -3.0, 1.0])),
374 Some(Roots::Two(1.0, 2.0))
375 );
376 assert_eq!(
377 solve_polynomial_degree_1_or_2(&Polynomial::zero()),
378 Some(Roots::Infinite)
379 );
380 assert_eq!(
381 solve_polynomial_degree_1_or_2(&Polynomial::new(vec![1.0, 0.0, 0.0, 1.0])),
382 None
383 );
384 }
385}