1use arrayvec::ArrayVec;
2
3use crate::{InputError, Quadratic, TerminationCondition, ValueError, different_signs};
4
5#[derive(Debug, Copy, Clone)]
6pub struct Cubic {
7 pub c0: f64,
8 pub c1: f64,
9 pub c2: f64,
10 pub c3: f64,
11}
12
13impl std::ops::Div<f64> for Cubic {
14 type Output = Cubic;
15
16 fn div(self, rhs: f64) -> Cubic {
17 Cubic {
18 c0: self.c0 / rhs,
19 c1: self.c1 / rhs,
20 c2: self.c2 / rhs,
21 c3: self.c3 / rhs,
22 }
23 }
24}
25
26impl std::ops::Mul<f64> for Cubic {
27 type Output = Cubic;
28
29 fn mul(self, rhs: f64) -> Cubic {
30 Cubic {
31 c0: self.c0 * rhs,
32 c1: self.c1 * rhs,
33 c2: self.c2 * rhs,
34 c3: self.c3 * rhs,
35 }
36 }
37}
38
39impl Cubic {
40 pub fn eval(&self, x: f64) -> f64 {
41 let xx = x * x;
42 let xxx = xx * x;
43 self.c0 + self.c1 * x + self.c2 * xx + self.c3 * xxx
44 }
45
46 pub fn deriv(&self) -> Quadratic {
47 Quadratic {
48 c0: self.c1,
49 c1: 2.0 * self.c2,
50 c2: 3.0 * self.c3,
51 }
52 }
53
54 pub fn max_coeff(&self) -> f64 {
55 self.c0
56 .abs()
57 .max(self.c1.abs())
58 .max(self.c2.abs())
59 .max(self.c3.abs())
60 }
61
62 fn deflate(&self, root: f64) -> Quadratic {
63 let a = self.c3;
64 let b = self.c2 + root * a;
65 let c = self.c1 + root * b;
66 Quadratic {
67 c2: a,
68 c1: b,
69 c0: c,
70 }
71 }
72
73 fn critical_points(&self) -> Option<(f64, f64)> {
84 let a = 3.0 * self.c3;
85 let b_2 = self.c2;
86 let c = self.c1;
87 let disc_4 = b_2 * b_2 - a * c;
88
89 if !disc_4.is_finite() {
90 return self.rescaled_critical_points();
91 }
92
93 if disc_4 > 0.0 {
94 let q = -(b_2 + disc_4.sqrt().copysign(b_2));
95 let r0 = q / a;
96 let r1 = c / q;
97 Some((r0.min(r1), r0.max(r1)))
98 } else {
99 None
100 }
101 }
102
103 #[cold]
104 fn rescaled_critical_points(&self) -> Option<(f64, f64)> {
105 let scale = 2.0f64.powi(-515);
106 (*self * scale).critical_points()
107 }
108
109 fn one_root<Term: TerminationCondition>(
110 &self,
111 mut lower: f64,
112 mut upper: f64,
113 term: Term,
114 ) -> f64 {
115 let val_lower = self.eval(lower);
116 let val_upper = self.eval(upper);
117 if !val_lower.is_finite() || !val_upper.is_finite() || !self.deriv().is_finite() {
118 return f64::NAN;
119 }
120 debug_assert!(different_signs(val_lower, val_upper));
121
122 let mut x = lower + (upper - lower) / 2.0;
123 let mut val_x = self.eval(x);
124 let mut step = (upper - lower) / 2.0;
125
126 while x.is_finite() && !term.stop(step, val_x) {
127 let root_in_first_half = different_signs(val_lower, val_x);
128 if root_in_first_half {
129 upper = x;
130 } else {
131 lower = x;
132 }
133
134 let deriv_x = self.deriv().eval(x);
135 debug_assert!(deriv_x.is_finite());
136 debug_assert!(val_x.is_finite());
137
138 step = -val_x / deriv_x;
139 let mut new_x = x + step;
140
141 if new_x <= lower || new_x >= upper {
142 new_x = lower + (upper - lower) / 2.0;
143
144 if new_x == upper || new_x == lower {
145 return new_x;
153 }
154 }
155 step = new_x - x;
156 x = new_x;
157 val_x = self.eval(x);
158 }
159 x
160 }
161
162 fn one_root_precomputed<Term: TerminationCondition>(
163 &self,
164 mut lower: f64,
165 mut upper: f64,
166 val_lower: f64,
167 val_upper: f64,
168 term: Term,
169 ) -> f64 {
170 if !val_lower.is_finite() || !val_upper.is_finite() || !self.deriv().is_finite() {
171 return f64::NAN;
172 }
173 debug_assert!(different_signs(val_lower, val_upper));
174
175 let mut x = lower + (upper - lower) / 2.0;
176 let mut val_x = self.eval(x);
177 let mut step = (upper - lower) / 2.0;
178
179 while x.is_finite() && !term.stop(step, val_x) {
180 let root_in_first_half = different_signs(val_lower, val_x);
181 if root_in_first_half {
182 upper = x;
183 } else {
184 lower = x;
185 }
186
187 let deriv_x = self.deriv().eval(x);
188 debug_assert!(deriv_x.is_finite());
189 debug_assert!(val_x.is_finite());
190
191 step = -val_x / deriv_x;
192 let mut new_x = x + step;
193
194 if new_x <= lower || new_x >= upper {
195 new_x = lower + (upper - lower) / 2.0;
196
197 if new_x == upper || new_x == lower {
198 return new_x;
206 }
207 }
208 step = new_x - x;
209 x = new_x;
210 val_x = self.eval(x);
211 }
212 x
213 }
214
215 pub fn root_between_with_output_error(self, lower: f64, upper: f64, y_error: f64) -> f64 {
216 self.one_root(lower, upper, ValueError(y_error))
217 }
218
219 pub fn root_between(self, lower: f64, upper: f64, x_error: f64) -> f64 {
220 self.one_root(lower, upper, InputError(x_error))
221 }
222
223 fn first_root<Term: TerminationCondition>(
224 self,
225 lower: f64,
226 upper: f64,
227 term: Term,
228 ) -> Option<f64> {
229 if let Some((x0, x1)) = self.critical_points() {
230 let possible_endpoints: [f64; 3] = [x0, x1, upper];
231 let mut last = lower;
232 let mut last_val = self.eval(last);
233 for x in possible_endpoints {
234 if x > last && x <= upper {
235 let val = self.eval(x);
236 if different_signs(last_val, val) {
237 return Some(self.one_root_precomputed(last, x, last_val, val, term));
238 }
239
240 last = x;
241 last_val = val;
242 }
243 }
244 None
245 } else {
246 let lower_val = self.eval(lower);
247 let upper_val = self.eval(upper);
248 if different_signs(lower_val, upper_val) {
249 Some(self.one_root_precomputed(lower, upper, lower_val, upper_val, term))
250 } else {
251 None
252 }
253 }
254 }
255
256 pub(crate) fn all_roots_term<Term: TerminationCondition>(
257 self,
258 lower: f64,
259 upper: f64,
260 term: Term,
261 ) -> ArrayVec<f64, 3> {
262 let mut ret = ArrayVec::new();
263 if let Some(r) = self.first_root(lower, upper, term) {
264 ret.push(r);
265 let quad = self.deflate(r);
266 if let Some((x0, x1)) = quad.positive_discriminant_roots() {
267 if lower <= x0 && x0 <= upper {
268 ret.push(x0);
269 }
270 if lower <= x1 && x1 <= upper {
271 ret.push(x1);
272 }
273 }
274 }
275 ret
276 }
277
278 pub fn all_roots(self, lower: f64, upper: f64, x_error: f64) -> ArrayVec<f64, 3> {
279 self.all_roots_term(lower, upper, InputError(x_error))
280 }
281
282 pub fn all_roots_with_output_error(
283 self,
284 lower: f64,
285 upper: f64,
286 y_error: f64,
287 ) -> ArrayVec<f64, 3> {
288 self.all_roots_term(lower, upper, ValueError(y_error))
289 }
290
291 pub fn roots_between_with_output_error(
304 self,
305 lower: f64,
306 upper: f64,
307 y_error: f64,
308 ) -> ArrayVec<f64, 3> {
309 let mut possible_endpoints = ArrayVec::<f64, 3>::new();
310 if let Some((x0, x1)) = self.critical_points() {
311 possible_endpoints.push(x0);
312 possible_endpoints.push(x1);
313 }
314 possible_endpoints.push(upper);
315
316 let mut last = lower;
317 let mut last_val = self.eval(last);
318 let mut ret = ArrayVec::new();
319
320 for x in possible_endpoints {
321 if x > last && x <= upper {
322 let val = self.eval(x);
323 if different_signs(last_val, val) {
324 ret.push(self.root_between_with_output_error(last, x, y_error));
325 }
326
327 last = x;
328 last_val = val;
329 }
330 }
331 ret
332 }
333
334 #[cold]
335 fn roots_blinn_renormalized(&self) -> ArrayVec<f64, 3> {
336 if !self.max_coeff().is_finite() {
337 ArrayVec::new()
338 } else {
339 (*self / 2.0f64.powi(128)).roots_blinn()
340 }
341 }
342
343 pub fn roots_blinn(&self) -> ArrayVec<f64, 3> {
344 let mut ret = ArrayVec::new();
345 let a = self.c3;
346 let b = self.c2 * (1.0 / 3.0);
347 let c = self.c1 * (1.0 / 3.0);
348 let d = self.c0;
349
350 let delta_1 = a * c - b * b;
351 let delta_2 = a * d - b * c;
352 let delta_3 = b * d - c * c;
353 let disc = 4.0 * delta_1 * delta_3 - delta_2 * delta_2;
354
355 if disc < 0.0 {
357 dbg!(disc);
358 let (tilde_a, tilde_c, tilde_d) = if b * b * b * d >= a * c * c * c {
359 (a, delta_1, -2.0 * b * delta_1 + a * delta_2)
360 } else {
361 (d, delta_3, -d * delta_2 + 2.0 * c * delta_3)
362 };
363 let t_0 = -tilde_a.copysign(tilde_d) * (-disc).sqrt();
364 let t_1 = -tilde_d + t_0;
365 let p = (t_1 / 2.0).cbrt();
366
367 let q = if t_0 == t_1 { -p } else { -tilde_c / p };
368 let tilde_x = if tilde_c <= 0.0 {
369 p + q
370 } else {
371 -tilde_d / (p * p + q * q + tilde_c)
372 };
373
374 let (x, w) = if b * b * b * d >= a * c * c * c {
375 (tilde_x - b, a)
376 } else {
377 (-d, tilde_x + c)
378 };
379
380 if !x.is_finite() || !w.is_finite() {
381 return self.roots_blinn_renormalized();
382 }
383
384 ret.push(x / w);
385 } else {
386 dbg!(disc);
387 fn one_root(a_or_d: f64, disc: f64, bar_c: f64, bar_d: f64) -> (f64, f64) {
388 let sqrt_c = (-bar_c).sqrt();
389 let theta = (1.0 / 3.0) * (a_or_d * disc.sqrt()).atan2(-bar_d).abs();
390 let (sin_theta, cos_theta) = theta.sin_cos();
391 dbg!(theta, cos_theta);
392 let tilde_x_1 = 2.0 * sqrt_c * cos_theta;
393 let tilde_x_3 = sqrt_c * (-theta.cos() - 3.0f64.sqrt() * sin_theta);
394 (tilde_x_1, tilde_x_3)
395 }
396
397 let bar_c_a = delta_1;
398 let bar_d_a = -2.0 * b * delta_1 + a * delta_2;
399 let (tilde_x_1_a, tilde_x_3_a) = one_root(a, disc, bar_c_a, bar_d_a);
400
401 let bar_c_d = delta_3;
402 let bar_d_d = -d * delta_2 + 2.0 * c * delta_3;
403 let (tilde_x_1_d, tilde_x_3_d) = one_root(d, disc, bar_c_d, bar_d_d);
404
405 let tilde_x_l = if tilde_x_1_a + tilde_x_3_a > 2.0 * b {
406 tilde_x_1_a
407 } else {
408 tilde_x_3_a
409 };
410 let tilde_x_s = if tilde_x_1_d + tilde_x_3_d < 2.0 * c {
411 tilde_x_1_d
412 } else {
413 tilde_x_3_d
414 };
415
416 let (x_l, w_l) = (tilde_x_l - b, a);
417 let (x_s, w_s) = (-d, tilde_x_s + c);
418
419 let e = w_l * w_s;
420 let f = -x_l * w_s - w_l * x_s;
421 let g = x_l * x_s;
422
423 let (x_m, w_m) = (c * f - b * g, c * e - b * f);
424
425 ret.push(x_l / w_l);
427 ret.push(x_s / w_s);
428 ret.push(x_m / w_m);
429 }
430 ret
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 #[test]
437 fn smoke() {
438 let poly = super::Cubic {
455 c0: -3.565233507454652,
456 c1: -3.565233507454643,
457 c2: -1.2298855640101194e-17,
458 c3: 9.133009604987547e-300,
459 };
460
461 let roots = poly.roots_blinn();
474 dbg!(&roots);
475 for r in roots {
476 dbg!(poly.eval(r));
477 }
478 }
479
480 #[test]
481 fn root_evaluation() {
482 arbtest::arbtest(|u| {
483 let c = crate::arbitrary::cubic(u)?;
484 let magnitude = c.max_coeff().max(1.0);
488 let accuracy = magnitude * 1e-12;
489
490 for r in c.roots_between_with_output_error(-10.0, 10.0, accuracy) {
494 let y = c.eval(r);
495 if y.is_finite() {
496 assert!(y.abs() <= accuracy);
497 }
498 }
499 for r in c.roots_blinn() {
500 let y = c.eval(r);
501 if y.is_finite() {
502 dbg!(c, r, y);
503 assert!(y.abs() <= accuracy);
504 }
505 }
506 for r in c.all_roots_with_output_error(-10.0, 10.0, accuracy) {
507 let y = c.eval(r);
508 if y.is_finite() {
509 assert!(y.abs() <= accuracy);
510 }
511 }
512 Ok(())
513 })
514 .budget_ms(5_000);
515 }
516
517 #[test]
518 #[ignore]
519 fn root_evaluation_kurbo() {
520 arbtest::arbtest(|u| {
521 let c = crate::arbitrary::cubic(u)?;
522 let magnitude = c.max_coeff().max(1.0);
525 let accuracy = magnitude * 1e-12;
526
527 for r in kurbo::common::solve_cubic(c.c0, c.c1, c.c2, c.c3) {
531 let y = c.eval(r);
532 if y.is_finite() {
533 assert!(y.abs() <= accuracy);
534 }
535 }
536 Ok(())
537 })
538 .budget_ms(5_000);
539 }
540}