#![allow(clippy::many_single_char_names)]
use crate::{epga1d::*, GeometricProduct, GeometricQuotient, Powf, Reversal, SquaredMagnitude};
#[derive(Debug, Clone, Copy)]
pub struct Root {
pub numerator: ComplexNumber,
pub denominator: f32,
}
impl Root {
pub fn new(numerator: [f32; 2], denominator: f32) -> Self {
Self {
numerator: numerator.into(),
denominator,
}
}
}
pub fn solve_linear(coefficients: [f32; 2], error_margin: f32) -> (f32, Vec<Root>) {
if coefficients[1].abs() <= error_margin {
(0.0, vec![])
} else {
(
1.0,
vec![Root {
numerator: ComplexNumber::new(-coefficients[0], 0.0),
denominator: coefficients[1],
}],
)
}
}
pub fn solve_quadratic(coefficients: [f32; 3], error_margin: f32) -> (f32, Vec<Root>) {
if coefficients[2].abs() <= error_margin {
return solve_linear([coefficients[0], coefficients[1]], error_margin);
}
let discriminant = coefficients[1].powi(2) - 4.0 * coefficients[2] * coefficients[0];
let q = discriminant.sqrt();
let mut solutions = Vec::with_capacity(3);
for s in [-q, q] {
let numerator = s - ComplexNumber::new(coefficients[1], 0.0);
solutions.push(Root {
numerator,
denominator: 2.0 * coefficients[2],
});
}
(discriminant, solutions)
}
const ROOTS_OF_UNITY_3: [ComplexNumber; 3] = [
ComplexNumber::new(-0.5, -0.8660254),
ComplexNumber::new(-0.5, 0.8660254),
ComplexNumber::new(1.0, 0.0),
];
pub fn solve_cubic(coefficients: [f32; 4], error_margin: f32) -> (f32, Vec<Root>, usize) {
if coefficients[3].abs() <= error_margin {
let (discriminant, roots) = solve_quadratic(
[coefficients[0], coefficients[1], coefficients[2]],
error_margin,
);
return (discriminant, roots, 2);
}
let d = [
coefficients[2].powi(2) - 3.0 * coefficients[3] * coefficients[1],
2.0 * coefficients[2].powi(3) - 9.0 * coefficients[3] * coefficients[2] * coefficients[1]
+ 27.0 * coefficients[3].powi(2) * coefficients[0],
];
let mut solutions = Vec::with_capacity(3);
let discriminant = d[1].powi(2) - 4.0 * d[0].powi(3);
let c = discriminant.sqrt();
let c = ((c + ComplexNumber::new(if c + d[1] == 0.0 { -d[1] } else { d[1] }, 0.0)) * 0.5)
.powf(1.0 / 3.0);
for root_of_unity in &ROOTS_OF_UNITY_3 {
let ci = c.geometric_product(*root_of_unity);
let denominator = ci * (3.0 * coefficients[3]);
let numerator =
(ci * -coefficients[2] - ci.geometric_product(ci) - ComplexNumber::new(d[0], 0.0))
.geometric_product(denominator.reversal());
solutions.push(Root {
numerator,
denominator: denominator.squared_magnitude(),
});
}
let real_root =
(((std::f32::consts::PI - c.arg()) / (std::f32::consts::PI * 2.0 / 3.0)) as usize + 1) % 3;
(discriminant, solutions, real_root)
}
pub fn solve_quartic(coefficients: [f32; 5], error_margin: f32) -> (f32, Vec<Root>) {
if coefficients[4].abs() <= error_margin {
let (discriminant, roots, _real_root) = solve_cubic(
[
coefficients[0],
coefficients[1],
coefficients[2],
coefficients[3],
],
error_margin,
);
return (discriminant, roots);
}
let p = (8.0 * coefficients[4] * coefficients[2] - 3.0 * coefficients[3].powi(2))
/ (8.0 * coefficients[4].powi(2));
let q = (coefficients[3].powi(3) - 4.0 * coefficients[4] * coefficients[3] * coefficients[2]
+ 8.0 * coefficients[4].powi(2) * coefficients[1])
/ (8.0 * coefficients[4].powi(3));
let d = [
coefficients[2].powi(2) - 3.0 * coefficients[3] * coefficients[1]
+ 12.0 * coefficients[4] * coefficients[0],
2.0 * coefficients[2].powi(3) - 9.0 * coefficients[3] * coefficients[2] * coefficients[1]
+ 27.0 * coefficients[3].powi(2) * coefficients[0]
+ 27.0 * coefficients[4] * coefficients[1].powi(2)
- 72.0 * coefficients[4] * coefficients[2] * coefficients[0],
];
let discriminant = d[1].powi(2) - 4.0 * d[0].powi(3);
let c = discriminant.sqrt();
let c = ((c + ComplexNumber::new(if c + d[1] == 0.0 { -d[1] } else { d[1] }, 0.0)) * 0.5)
.powf(1.0 / 3.0);
let e = ((c + ComplexNumber::new(d[0], 0.0).geometric_quotient(c))
* (1.0 / (3.0 * coefficients[4]))
- ComplexNumber::new(p * 2.0 / 3.0, 0.0))
.powf(0.5)
* 0.5;
let mut solutions = Vec::with_capacity(4);
for i in 0..4 {
let f = (e.geometric_product(e) * -4.0 - ComplexNumber::new(2.0 * p, 0.0)
+ ComplexNumber::new(if i & 2 == 0 { q } else { -q }, 0.0).geometric_quotient(e))
.powf(0.5)
* 0.5;
let g = ComplexNumber::new(-coefficients[3] / (4.0 * coefficients[4]), 0.0)
+ if i & 2 == 0 { -e } else { e }
+ if i & 1 == 0 { -f } else { f };
solutions.push(Root {
numerator: g,
denominator: 1.0,
});
}
(discriminant / -27.0, solutions)
}