1use arrayvec::ArrayVec;
2
3#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
23pub struct Poly<const N: usize> {
24 pub(crate) coeffs: [f64; N],
25}
26
27pub type Quadratic = Poly<3>;
29
30pub type Cubic = Poly<4>;
32
33pub type Quartic = Poly<5>;
35
36pub type Quintic = Poly<6>;
38
39impl<const N: usize> Poly<N> {
40 pub const fn new(coeffs: [f64; N]) -> Poly<N> {
46 Poly { coeffs }
47 }
48
49 pub fn coeffs(&self) -> &[f64; N] {
53 &self.coeffs
54 }
55
56 pub fn eval(&self, x: f64) -> f64 {
58 let mut acc = 0.0;
59 for c in self.coeffs.iter().rev() {
60 acc = acc * x + c;
63 }
64 acc
65 }
66
67 pub fn magnitude(&self) -> f64 {
71 let mut max = 0.0f64;
72 for c in &self.coeffs {
73 max = max.max(c.abs());
74 }
75 max
76 }
77
78 pub fn is_finite(&self) -> bool {
80 self.coeffs.iter().all(|c| c.is_finite())
81 }
82}
83
84macro_rules! impl_deriv_and_deflate {
85 ($N:literal, $N_MINUS_ONE:literal) => {
86 impl Poly<$N> {
87 pub fn deriv(&self) -> Poly<$N_MINUS_ONE> {
90 let mut coeffs = [0.0; $N_MINUS_ONE];
91 for (i, (d, c)) in coeffs.iter_mut().zip(&self.coeffs[1..]).enumerate() {
92 *d = (i + 1) as f64 * c;
93 }
94 Poly::new(coeffs)
95 }
96
97 pub fn deflate(&self, root: f64) -> Poly<$N_MINUS_ONE> {
106 let mut acc = 0.0;
107 let mut coeffs = [0.0; $N_MINUS_ONE];
108 for (d, c) in coeffs.iter_mut().zip(&self.coeffs[1..]).rev() {
109 acc = acc * root + c;
110 *d = acc;
111 }
112 Poly::new(coeffs)
113 }
114 }
115 };
116}
117
118macro_rules! impl_roots_between_recursive {
119 ($N:literal, $N_MINUS_ONE:literal) => {
120 impl Poly<$N> {
121 pub fn roots_between(
130 self,
131 lower: f64,
132 upper: f64,
133 x_error: f64,
134 ) -> ArrayVec<f64, $N_MINUS_ONE> {
135 let mut ret = ArrayVec::new();
136 let mut scratch = ArrayVec::new();
137 self.roots_between_with_buffer(lower, upper, x_error, &mut scratch, &mut ret);
138 ret
139 }
140
141 fn roots_between_with_buffer<const M: usize>(
145 self,
146 lower: f64,
147 upper: f64,
148 x_error: f64,
149 scratch: &mut ArrayVec<f64, M>,
150 out: &mut ArrayVec<f64, M>,
151 ) {
152 let deriv = self.deriv();
153 if !deriv.is_finite() {
154 return;
155 }
156 deriv.roots_between_with_buffer(lower, upper, x_error, out, scratch);
157 scratch.push(upper);
158 out.clear();
159 let mut last = lower;
160 let mut last_val = self.eval(last);
161
162 for &mut x in scratch {
166 let val = self.eval(x);
167 if $crate::different_signs(last_val, val) {
168 out.push($crate::yuksel::find_root(
169 |x| self.eval(x),
170 |x| deriv.eval(x),
171 last,
172 x,
173 last_val,
174 val,
175 x_error,
176 ));
177 }
178
179 last = x;
180 last_val = val;
181 }
182 }
183 }
184 };
185}
186
187impl_deriv_and_deflate!(3, 2);
188impl_deriv_and_deflate!(4, 3);
189impl_deriv_and_deflate!(5, 4);
190impl_deriv_and_deflate!(6, 5);
191impl_deriv_and_deflate!(7, 6);
192impl_deriv_and_deflate!(8, 7);
193impl_deriv_and_deflate!(9, 8);
194impl_deriv_and_deflate!(10, 9);
195
196impl_roots_between_recursive!(5, 4);
197impl_roots_between_recursive!(6, 5);
198impl_roots_between_recursive!(7, 6);
199impl_roots_between_recursive!(8, 7);
200impl_roots_between_recursive!(9, 8);
201impl_roots_between_recursive!(10, 9);
202
203impl<const N: usize> core::ops::Mul<f64> for Poly<N> {
204 type Output = Poly<N>;
205
206 fn mul(mut self, scale: f64) -> Poly<N> {
207 self *= scale;
208 self
209 }
210}
211
212impl<const N: usize> core::ops::MulAssign<f64> for Poly<N> {
213 fn mul_assign(&mut self, scale: f64) {
214 for c in &mut self.coeffs {
215 *c *= scale;
216 }
217 }
218}
219
220impl<const N: usize> core::ops::Mul<f64> for &Poly<N> {
221 type Output = Poly<N>;
222
223 fn mul(self, scale: f64) -> Poly<N> {
224 (*self) * scale
225 }
226}
227
228impl<const N: usize> core::ops::Div<f64> for Poly<N> {
229 type Output = Poly<N>;
230
231 fn div(mut self, scale: f64) -> Poly<N> {
232 self /= scale;
233 self
234 }
235}
236
237impl<const N: usize> core::ops::DivAssign<f64> for Poly<N> {
238 fn div_assign(&mut self, scale: f64) {
239 for c in &mut self.coeffs {
240 *c /= scale;
241 }
242 }
243}
244
245impl<const N: usize> core::ops::Div<f64> for &Poly<N> {
246 type Output = Poly<N>;
247
248 fn div(self, scale: f64) -> Poly<N> {
249 (*self) / scale
250 }
251}
252
253impl<const N: usize> core::ops::AddAssign<&Poly<N>> for Poly<N> {
254 fn add_assign(&mut self, rhs: &Poly<N>) {
255 for (c, d) in self.coeffs.iter_mut().zip(rhs.coeffs) {
256 *c += d;
257 }
258 }
259}
260
261impl<const N: usize> core::ops::AddAssign<Poly<N>> for Poly<N> {
262 fn add_assign(&mut self, rhs: Poly<N>) {
263 *self += &rhs;
264 }
265}
266
267impl<const N: usize> core::ops::Add<Poly<N>> for Poly<N> {
268 type Output = Poly<N>;
269
270 fn add(mut self, rhs: Poly<N>) -> Poly<N> {
271 self += rhs;
272 self
273 }
274}
275
276impl<const N: usize> core::ops::Add<&Poly<N>> for Poly<N> {
277 type Output = Poly<N>;
278
279 fn add(mut self, rhs: &Poly<N>) -> Poly<N> {
280 self += rhs;
281 self
282 }
283}
284
285impl<const N: usize> core::ops::Add<Poly<N>> for &Poly<N> {
286 type Output = Poly<N>;
287
288 fn add(self, mut rhs: Poly<N>) -> Poly<N> {
289 rhs += self;
290 rhs
291 }
292}
293
294impl<const N: usize> core::ops::SubAssign<&Poly<N>> for Poly<N> {
295 fn sub_assign(&mut self, rhs: &Poly<N>) {
296 for (c, d) in self.coeffs.iter_mut().zip(rhs.coeffs) {
297 *c -= d;
298 }
299 }
300}
301
302impl<const N: usize> core::ops::SubAssign<Poly<N>> for Poly<N> {
303 fn sub_assign(&mut self, rhs: Poly<N>) {
304 *self -= &rhs;
305 }
306}
307
308impl<const N: usize> core::ops::Sub<Poly<N>> for Poly<N> {
309 type Output = Poly<N>;
310
311 fn sub(mut self, rhs: Poly<N>) -> Poly<N> {
312 self -= rhs;
313 self
314 }
315}
316
317impl<const N: usize> core::ops::Sub<&Poly<N>> for Poly<N> {
318 type Output = Poly<N>;
319
320 fn sub(mut self, rhs: &Poly<N>) -> Poly<N> {
321 self -= rhs;
322 self
323 }
324}
325
326impl<const N: usize> core::ops::Sub<Poly<N>> for &Poly<N> {
327 type Output = Poly<N>;
328
329 fn sub(self, mut rhs: Poly<N>) -> Poly<N> {
330 rhs -= self;
331 rhs
332 }
333}
334
335#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn smoke() {
347 let p = Poly::new([-6.0, 11.0, -6.0, 1.0]);
348
349 let roots = p.roots_between(0.0, 5.0, 1e-6);
350 assert_eq!(roots.len(), 3);
351 assert!((roots[0] - 1.0).abs() <= 1e-6);
352 assert!((roots[1] - 2.0).abs() <= 1e-6);
353 assert!((roots[2] - 3.0).abs() <= 1e-6);
354
355 let p = Poly::new([24.0, -50.0, 35.0, -10.0, 1.0]);
356
357 let roots = p.roots_between(0.0, 5.0, 1e-6);
358 assert_eq!(roots.len(), 4);
359 assert!((roots[0] - 1.0).abs() <= 1e-6);
360 assert!((roots[1] - 2.0).abs() <= 1e-6);
361 assert!((roots[2] - 3.0).abs() <= 1e-6);
362 assert!((roots[3] - 4.0).abs() <= 1e-6);
363 }
364
365 fn check_root_values<const N: usize>(p: &Poly<N>, roots: &[f64]) {
369 let magnitude = p.magnitude().max(1.0);
372 let accuracy = magnitude * 1e-12;
373
374 for r in roots {
375 let accuracy = accuracy * r.abs().powi(N as i32 - 1).max(1.0);
379 let y = p.eval(*r);
380 if y.is_finite() {
381 assert!(
382 y.abs() <= accuracy,
383 "poly {p:?} had root {r} evaluate to {y:?}, but expected {accuracy:?}"
384 );
385 }
386 }
387 }
388
389 #[test]
390 fn root_evaluation_deg4() {
391 arbtest::arbtest(|u| {
392 let poly: Poly<5> = crate::arbitrary::poly(u)?;
393 let roots = poly.roots_between(-10.0, 10.0, 1e-13);
394 check_root_values(&poly, &roots);
395 Ok(())
396 })
397 .budget_ms(5_000);
398 }
399
400 #[test]
401 fn root_evaluation_deg9() {
402 arbtest::arbtest(|u| {
403 let poly: Poly<10> = crate::arbitrary::poly(u)?;
404 let roots = poly.roots_between(-10.0, 10.0, 1e-13);
405 check_root_values(&poly, &roots);
406 Ok(())
407 })
408 .budget_ms(5_000);
409 }
410
411 #[test]
412 fn planted_root_deg5() {
413 arbtest::arbtest(|u| {
414 let planted_root = crate::arbitrary::float_in_unit_interval(u)?;
415 let poly: Poly<6> = crate::arbitrary::poly_with_planted_root(u, planted_root, 1e-6)?;
416
417 if (poly.magnitude() * 1024.0).is_infinite() {
421 return Err(arbitrary::Error::IncorrectFormat);
422 }
423 let roots = poly.roots_between(-2.0, 2.0, 1e-13);
424
425 if roots.iter().all(|r| r.is_finite()) {
427 assert!(roots.is_sorted());
428 }
429
430 let error = poly.magnitude().max(1.0) * 1e-12;
433 assert!(roots.iter().any(|r| (r - planted_root).abs() <= error));
434 Ok(())
435 })
436 .budget_ms(5_000);
437 }
438}