1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[doc(inline)]
7pub use polynomial::Polynomial;
8#[doc(inline)]
9pub use roots::{linear_root, quadratic_roots};
10
11pub mod polynomial {
13 use core::ops::{Add, Div, Mul, Neg, Sub};
14
15 use crate::roots::{linear_root, quadratic_roots};
16
17 #[derive(Debug, Clone, PartialEq)]
24 pub struct Polynomial {
25 coefficients: Vec<f64>,
26 }
27
28 impl Polynomial {
29 #[must_use]
34 pub fn new(mut coefficients: Vec<f64>) -> Self {
35 while coefficients
36 .last()
37 .is_some_and(|coefficient| *coefficient == 0.0)
38 {
39 coefficients.pop();
40 }
41
42 Self { coefficients }
43 }
44
45 #[must_use]
47 pub fn constant(value: f64) -> Self {
48 Self::new(vec![value])
49 }
50
51 #[must_use]
53 pub fn zero() -> Self {
54 Self::new(Vec::new())
55 }
56
57 #[must_use]
59 pub fn one() -> Self {
60 Self::constant(1.0)
61 }
62
63 #[must_use]
65 pub fn coefficients(&self) -> &[f64] {
66 &self.coefficients
67 }
68
69 #[must_use]
71 pub fn degree(&self) -> Option<usize> {
72 if self.coefficients.is_empty() {
73 None
74 } else {
75 Some(self.coefficients.len() - 1)
76 }
77 }
78
79 #[must_use]
81 pub fn leading_coefficient(&self) -> Option<f64> {
82 self.coefficients.last().copied()
83 }
84
85 #[must_use]
87 pub fn is_zero(&self) -> bool {
88 self.coefficients.is_empty()
89 }
90
91 #[must_use]
93 pub fn evaluate(&self, x: f64) -> f64 {
94 self.coefficients
95 .iter()
96 .rev()
97 .fold(0.0, |accumulator, coefficient| {
98 accumulator * x + coefficient
99 })
100 }
101
102 #[must_use]
104 pub fn derivative(&self) -> Self {
105 if self.coefficients.len() < 2 {
106 return Self::zero();
107 }
108
109 let coefficients = self
110 .coefficients
111 .iter()
112 .enumerate()
113 .skip(1)
114 .map(|(index, coefficient)| *coefficient * index as f64)
115 .collect();
116
117 Self::new(coefficients)
118 }
119
120 #[must_use]
122 pub fn nth_derivative(&self, n: usize) -> Self {
123 if n == 0 {
124 return self.clone();
125 }
126
127 let mut derivative = self.clone();
128 for _ in 0..n {
129 derivative = derivative.derivative();
130 if derivative.is_zero() {
131 break;
132 }
133 }
134
135 derivative
136 }
137
138 #[must_use]
140 pub fn integral(&self, constant: f64) -> Self {
141 let mut coefficients = Vec::with_capacity(self.coefficients.len() + 1);
142 coefficients.push(constant);
143 coefficients.extend(
144 self.coefficients
145 .iter()
146 .enumerate()
147 .map(|(index, coefficient)| *coefficient / (index + 1) as f64),
148 );
149
150 Self::new(coefficients)
151 }
152
153 #[must_use]
158 pub fn real_roots_degree_1_or_2(&self) -> Option<Vec<f64>> {
159 match self.degree() {
160 None | Some(0) => Some(Vec::new()),
161 Some(1) => Some(
162 linear_root(self.coefficients[1], self.coefficients[0])
163 .into_iter()
164 .collect(),
165 ),
166 Some(2) => Some(quadratic_roots(
167 self.coefficients[2],
168 self.coefficients[1],
169 self.coefficients[0],
170 )),
171 Some(_) => None,
172 }
173 }
174 }
175
176 impl Add for Polynomial {
177 type Output = Self;
178
179 fn add(self, rhs: Self) -> Self::Output {
180 let max_len = self.coefficients.len().max(rhs.coefficients.len());
181 let coefficients = (0..max_len)
182 .map(|index| {
183 self.coefficients.get(index).copied().unwrap_or(0.0)
184 + rhs.coefficients.get(index).copied().unwrap_or(0.0)
185 })
186 .collect();
187
188 Self::new(coefficients)
189 }
190 }
191
192 impl Sub for Polynomial {
193 type Output = Self;
194
195 fn sub(self, rhs: Self) -> Self::Output {
196 let max_len = self.coefficients.len().max(rhs.coefficients.len());
197 let coefficients = (0..max_len)
198 .map(|index| {
199 self.coefficients.get(index).copied().unwrap_or(0.0)
200 - rhs.coefficients.get(index).copied().unwrap_or(0.0)
201 })
202 .collect();
203
204 Self::new(coefficients)
205 }
206 }
207
208 impl Mul for Polynomial {
209 type Output = Self;
210
211 fn mul(self, rhs: Self) -> Self::Output {
212 if self.is_zero() || rhs.is_zero() {
213 return Self::zero();
214 }
215
216 let mut coefficients = vec![0.0; self.coefficients.len() + rhs.coefficients.len() - 1];
217
218 for (left_index, left_coefficient) in self.coefficients.iter().enumerate() {
219 for (right_index, right_coefficient) in rhs.coefficients.iter().enumerate() {
220 coefficients[left_index + right_index] += left_coefficient * right_coefficient;
221 }
222 }
223
224 Self::new(coefficients)
225 }
226 }
227
228 impl Neg for Polynomial {
229 type Output = Self;
230
231 fn neg(self) -> Self::Output {
232 Self::new(
233 self.coefficients
234 .into_iter()
235 .map(|coefficient| -coefficient)
236 .collect(),
237 )
238 }
239 }
240
241 impl Mul<f64> for Polynomial {
242 type Output = Self;
243
244 fn mul(self, rhs: f64) -> Self::Output {
245 Self::new(
246 self.coefficients
247 .into_iter()
248 .map(|coefficient| coefficient * rhs)
249 .collect(),
250 )
251 }
252 }
253
254 impl Div<f64> for Polynomial {
255 type Output = Self;
256
257 fn div(self, rhs: f64) -> Self::Output {
258 Self::new(
259 self.coefficients
260 .into_iter()
261 .map(|coefficient| coefficient / rhs)
262 .collect(),
263 )
264 }
265 }
266}
267
268pub mod roots {
270 #[must_use]
274 pub fn linear_root(a: f64, b: f64) -> Option<f64> {
275 (a != 0.0).then_some(-b / a)
276 }
277
278 #[must_use]
283 pub fn quadratic_roots(a: f64, b: f64, c: f64) -> Vec<f64> {
284 if a == 0.0 {
285 return linear_root(b, c).into_iter().collect();
286 }
287
288 let discriminant = b * b - 4.0 * a * c;
289
290 if discriminant.is_nan() || discriminant < 0.0 {
291 return Vec::new();
292 }
293
294 if discriminant == 0.0 {
295 return vec![-b / (2.0 * a)];
296 }
297
298 let square_root = discriminant.sqrt();
299 let first = (-b - square_root) / (2.0 * a);
300 let second = (-b + square_root) / (2.0 * a);
301
302 if first <= second {
303 vec![first, second]
304 } else {
305 vec![second, first]
306 }
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::{Polynomial, linear_root, quadratic_roots};
313
314 #[test]
315 fn constructor_trims_trailing_zeros() {
316 let polynomial = Polynomial::new(vec![1.0, 2.0, 0.0, 0.0]);
317
318 assert_eq!(polynomial.coefficients(), &[1.0, 2.0]);
319 }
320
321 #[test]
322 fn zero_polynomial_uses_empty_representation() {
323 let zero = Polynomial::new(vec![0.0, 0.0]);
324
325 assert!(zero.coefficients().is_empty());
326 assert_eq!(zero.degree(), None);
327 assert_eq!(zero.leading_coefficient(), None);
328 assert!(zero.is_zero());
329 }
330
331 #[test]
332 fn constant_constructors_work() {
333 assert_eq!(Polynomial::constant(4.0).coefficients(), &[4.0]);
334 assert!(Polynomial::zero().is_zero());
335 assert_eq!(Polynomial::one().coefficients(), &[1.0]);
336 }
337
338 #[test]
339 fn reports_degree_and_leading_coefficient() {
340 let polynomial = Polynomial::new(vec![3.0, 2.0, 1.0]);
341
342 assert_eq!(polynomial.degree(), Some(2));
343 assert_eq!(polynomial.leading_coefficient(), Some(1.0));
344 }
345
346 #[test]
347 fn evaluates_with_horner_method() {
348 let polynomial = Polynomial::new(vec![3.0, 2.0, 1.0]);
349
350 assert_eq!(polynomial.evaluate(2.0), 11.0);
351 assert_eq!(Polynomial::zero().evaluate(3.0), 0.0);
352 }
353
354 #[test]
355 fn derivative_handles_zero_constant_linear_and_quadratic() {
356 assert_eq!(Polynomial::zero().derivative(), Polynomial::zero());
357 assert_eq!(Polynomial::constant(5.0).derivative(), Polynomial::zero());
358 assert_eq!(
359 Polynomial::new(vec![3.0, 2.0]).derivative(),
360 Polynomial::constant(2.0)
361 );
362 assert_eq!(
363 Polynomial::new(vec![3.0, 2.0, 1.0]).derivative(),
364 Polynomial::new(vec![2.0, 2.0])
365 );
366 }
367
368 #[test]
369 fn nth_derivative_behaves_as_expected() {
370 let polynomial = Polynomial::new(vec![1.0, 2.0, 3.0, 4.0]);
371
372 assert_eq!(polynomial.nth_derivative(0), polynomial.clone());
373 assert_eq!(
374 polynomial.nth_derivative(1),
375 Polynomial::new(vec![2.0, 6.0, 12.0])
376 );
377 assert_eq!(
378 polynomial.nth_derivative(2),
379 Polynomial::new(vec![6.0, 24.0])
380 );
381 assert_eq!(polynomial.nth_derivative(4), Polynomial::zero());
382 }
383
384 #[test]
385 fn computes_integral_with_constant() {
386 let polynomial = Polynomial::new(vec![2.0, 6.0]);
387
388 assert_eq!(
389 polynomial.integral(5.0),
390 Polynomial::new(vec![5.0, 2.0, 3.0])
391 );
392 }
393
394 #[test]
395 fn adds_polynomials() {
396 let left = Polynomial::new(vec![1.0, 2.0, 3.0]);
397 let right = Polynomial::new(vec![4.0, -2.0]);
398
399 assert_eq!(left + right, Polynomial::new(vec![5.0, 0.0, 3.0]));
400 }
401
402 #[test]
403 fn subtracts_polynomials() {
404 let left = Polynomial::new(vec![5.0, 1.0]);
405 let right = Polynomial::new(vec![2.0, 3.0, 4.0]);
406
407 assert_eq!(left - right, Polynomial::new(vec![3.0, -2.0, -4.0]));
408 }
409
410 #[test]
411 fn multiplies_polynomials() {
412 let left = Polynomial::new(vec![1.0, 1.0]);
413 let right = Polynomial::new(vec![1.0, -1.0]);
414
415 assert_eq!(left * right, Polynomial::new(vec![1.0, 0.0, -1.0]));
416 }
417
418 #[test]
419 fn negates_polynomials() {
420 let polynomial = Polynomial::new(vec![1.0, -2.0, 3.0]);
421
422 assert_eq!(-polynomial, Polynomial::new(vec![-1.0, 2.0, -3.0]));
423 }
424
425 #[test]
426 fn multiplies_by_scalar() {
427 let polynomial = Polynomial::new(vec![1.0, -2.0, 3.0]);
428
429 assert_eq!(polynomial * 2.0, Polynomial::new(vec![2.0, -4.0, 6.0]));
430 assert_eq!(Polynomial::new(vec![1.0, 2.0]) * 0.0, Polynomial::zero());
431 }
432
433 #[test]
434 fn divides_by_scalar() {
435 let polynomial = Polynomial::new(vec![2.0, -4.0, 6.0]);
436
437 assert_eq!(polynomial / 2.0, Polynomial::new(vec![1.0, -2.0, 3.0]));
438 }
439
440 #[test]
441 fn solves_linear_roots() {
442 assert_eq!(linear_root(2.0, -4.0), Some(2.0));
443 assert_eq!(linear_root(0.0, 3.0), None);
444 }
445
446 #[test]
447 fn solves_quadratic_roots_with_two_real_roots() {
448 assert_eq!(quadratic_roots(1.0, -3.0, 2.0), vec![1.0, 2.0]);
449 }
450
451 #[test]
452 fn solves_quadratic_roots_with_one_repeated_root() {
453 assert_eq!(quadratic_roots(1.0, -2.0, 1.0), vec![1.0]);
454 }
455
456 #[test]
457 fn solves_quadratic_roots_with_no_real_roots() {
458 assert!(quadratic_roots(1.0, 0.0, 1.0).is_empty());
459 }
460
461 #[test]
462 fn quadratic_roots_fall_back_to_linear_case() {
463 assert_eq!(quadratic_roots(0.0, 2.0, -4.0), vec![2.0]);
464 assert!(quadratic_roots(0.0, 0.0, 1.0).is_empty());
465 }
466
467 #[test]
468 fn low_degree_real_root_helper_handles_supported_cases() {
469 assert_eq!(Polynomial::zero().real_roots_degree_1_or_2(), Some(vec![]));
470 assert_eq!(
471 Polynomial::constant(5.0).real_roots_degree_1_or_2(),
472 Some(vec![])
473 );
474 assert_eq!(
475 Polynomial::new(vec![-4.0, 2.0]).real_roots_degree_1_or_2(),
476 Some(vec![2.0])
477 );
478 assert_eq!(
479 Polynomial::new(vec![2.0, -3.0, 1.0]).real_roots_degree_1_or_2(),
480 Some(vec![1.0, 2.0])
481 );
482 assert_eq!(
483 Polynomial::new(vec![1.0, 0.0, 0.0, 1.0]).real_roots_degree_1_or_2(),
484 None
485 );
486 }
487}