Files
OneCAD/vendor/matext4cgmath/src/solver.rs
T
CypherNaugh_0xandClaude Opus 5.5 5249ae84ef Moving a body that a boolean made no longer hangs: a boolean between a cylinder and a tilted plane (an elliptical edge) froze the app at 100 % CPU, because its retries move both bodies and the result back
Moving a body transforms its intersection-curve edges, and truck-geometry 0.5.0 scales each curve's tolerance by the operator norm of the matrix. matext4cgmath 0.1.0 computes it from eigenvalues through a quartic whose Newton refinement loops while the residual is above a tolerance and never gives up. For a translation matrix all four eigenvalues are 1, and near that quadruple root the iteration can cycle forever (about one translation in eight of the boolean retries' shifts, by a scan of sizes 0.1-2000 mm). matext4cgmath is vendored with both Newton loops stopped after 100 steps (vendor/README.md, ATTRIBUTION.md).

truck master no longer computes the norm in transform_by, so the vendored copy can go once a truck release after 0.5.0 is adopted.

Tests: moving_a_body_with_an_intersection_curve_edge_ends (kernel) cuts a cylinder with a tilted box and moves the result by one of the shifts that hung (hangs on main). examples/regressions/oblique_cut_of_cylinder_hang.json is the variant of the oblique cut that hung the app; it hangs on main, and since the previous commit it passes without reaching the shifted retries, so it is replayed by the engine test as a scenario only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:57:34 -04:00

247 lines
8.0 KiB
Rust

use crate::*;
#[cfg_attr(doc, katexit::katexit)]
/// Solve equation: $x^2 + ax + b = 0$.
///
/// # Examples
///
/// ```
/// use matext4cgmath::solver;
/// use num_complex::Complex;
/// const EPS: f64 = 1.0e-10;
///
/// let res = solver::solve_quadratic(3.0, -4.0);
/// // Real solutions are always sorted in order of solution size.
/// assert!(Complex::norm(res[0] + 4.0) < EPS);
/// assert!(Complex::norm(res[1] - 1.0) < EPS);
/// ```
pub fn solve_quadratic<F: BaseFloat>(a: F, b: F) -> [Complex<F>; 2] {
let two = F::one() + F::one();
let four = two + two;
let det = a * a - four * b;
match det >= F::zero() {
true => {
let h = F::sqrt(det);
[
Complex::new((-a - h) / two, F::zero()),
Complex::new((-a + h) / two, F::zero()),
]
}
false => {
let h = F::sqrt(-det);
[Complex::new(-a, h) / two, Complex::new(-a, -h) / two]
}
}
}
#[cfg_attr(doc, katexit::katexit)]
/// Solve equation: $x^3 + px + q = 0$.
///
/// # Examples
///
/// ```
/// use matext4cgmath::solver;
/// use num_complex::Complex;
/// const EPS: f64 = 1.0e-10;
///
/// let mut res = solver::pre_solve_cubic(-7.0, -6.0);
/// // Even in the case of real solutions, the order in the array is not guaranteed.
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
/// let ans = [Complex::from(-2.0), Complex::from(-1.0), Complex::from(3.0)];
/// res.iter().zip(ans).for_each(|(x, y)| {
/// assert!(Complex::norm(x - y) < EPS);
/// });
/// ```
pub fn pre_solve_cubic<F: BaseFloat>(p: F, q: F) -> [Complex<F>; 3] {
let two = F::one() + F::one();
let three = two + F::one();
let sqrt3_2 = F::sqrt(three) / two;
let omega = Complex::new(-F::one() / two, sqrt3_2);
let omega2 = Complex::new(-F::one() / two, -sqrt3_2);
let eps_2 = F::sqrt(F::epsilon());
let p_3 = p / three;
let q_2 = q / two;
let alpha2 = q_2 * q_2 + p_3 * p_3 * p_3;
let (x, y) = match alpha2 >= F::zero() {
true => {
let alpha = F::sqrt(alpha2);
let tmpx = -q_2 - alpha;
let tmpy = -q_2 + alpha;
(
Complex::new(
F::signum(tmpx) * F::powf(F::abs(tmpx), F::one() / three),
F::zero(),
),
Complex::new(
F::signum(tmpy) * F::powf(F::abs(tmpy), F::one() / three),
F::zero(),
),
)
}
false => {
let alphai = F::sqrt(-alpha2);
(
Complex::powf(Complex::new(-q_2, alphai), F::one() / three),
Complex::powf(Complex::new(-q_2, -alphai), F::one() / three),
)
}
};
let mut res = [x + y, omega * x + omega2 * y, omega2 * x + omega * y];
// precision by Newton method
res.iter_mut().for_each(|x| {
let mut f = *x * *x * *x + *x * p + q;
let mut f_prime = *x * *x * three + p;
// SolveCraft: at most 100 steps. Near a double root (the eigenvalues of a
// translation matrix) the iteration can cycle forever without meeting the tolerance.
let mut steps = 0;
while f.norm() > eps_2 * f_prime.norm() && steps < 100 {
steps += 1;
if f_prime.norm() < eps_2 {
return;
}
*x -= f / f_prime;
f = *x * *x * *x + *x * p + q;
f_prime = *x * *x * three + p;
}
});
res
}
#[cfg_attr(doc, katexit::katexit)]
/// solve equation: $x^3 + ax^2 + bx + c = 0$.
///
/// # Examples
///
/// ```
/// use matext4cgmath::solver;
/// use num_complex::Complex;
/// const EPS: f64 = 1.0e-10;
///
/// let mut res = solver::solve_cubic(-3.0, 0.0, 4.0);
/// // Even in the case of real solutions, the order in the array is not guaranteed.
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
/// let ans = [Complex::from(-1.0), Complex::from(2.0), Complex::from(2.0)];
/// res.iter().zip(ans).for_each(|(x, y)| {
/// assert!(Complex::norm(x - y) < EPS);
/// });
/// ```
pub fn solve_cubic<F: BaseFloat>(a: F, b: F, c: F) -> [Complex<F>; 3] {
let two = F::one() + F::one();
let three = two + F::one();
let twenty_seven = three * three * three;
let p = b - a * a / three;
let q = c - a * b / three + two * a * a * a / twenty_seven;
let mut res = pre_solve_cubic(p, q);
res.iter_mut().for_each(|x| {
*x -= a / three;
});
res
}
#[cfg_attr(doc, katexit::katexit)]
/// solve equation: $x^4 + px^2 + qx + r = 0$.
///
/// # Examples
///
/// ```
/// use matext4cgmath::solver;
/// use num_complex::Complex;
/// const EPS: f64 = 1e-7;
///
/// let mut res = solver::pre_solve_quartic(-5.0, 0.0, 4.0);
/// // Even in the case of real solutions, the order in the array is not guaranteed.
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
/// let ans = [Complex::from(-2.0), Complex::from(-1.0), Complex::from(1.0), Complex::from(2.0)];
/// res.iter().zip(ans).for_each(|(x, y)| {
/// assert!(Complex::norm(x - y) < EPS);
/// });
/// ```
pub fn pre_solve_quartic<F: BaseFloat>(p: F, q: F, r: F) -> [Complex<F>; 4] {
let one = F::one();
let two = one + one;
let four = two + two;
let eps_2 = F::sqrt(F::epsilon());
let a = two * p;
let b = p * p - four * r;
let c = -q * q;
let f = solve_cubic(a, b, c);
let a = f[0].sqrt() / two;
let b = f[1].sqrt() / two;
let c = f[2].sqrt() / two;
let mut res = (0..8)
.map(|i| {
let a = a * F::powi(-F::one(), i % 2);
let b = b * F::powi(-F::one(), (i / 2) % 2);
let c = c * F::powi(-F::one(), (i / 4) % 2);
[-a - b - c, -a + b + c, a - b + c, a + b - c]
})
.map(|x| {
let f = x
.iter()
.map(|t| (t * t * t * t + t * t * p + t * q + r).norm_sqr())
.max_by(|x, y| x.partial_cmp(y).unwrap())
.unwrap();
(x, f)
})
.min_by(|x, y| x.1.partial_cmp(&y.1).unwrap())
.unwrap()
.0;
// refinement by Newton method
res.iter_mut().for_each(|x| {
let mut f = *x * *x * *x * *x + *x * *x * p + *x * q + r;
let mut f_prime = *x * *x * *x * four + *x * p * two + q;
// SolveCraft: at most 100 steps. Near a double root (the eigenvalues of a
// translation matrix) the iteration can cycle forever without meeting the tolerance.
let mut steps = 0;
while f.norm() > eps_2 * f_prime.norm() && steps < 100 {
steps += 1;
if f_prime.norm() < eps_2 {
return;
}
*x -= f / f_prime;
f_prime = *x * *x * *x * four + *x * p * two + q;
f = *x * *x * *x * *x + *x * *x * p + *x * q + r;
}
});
res
}
#[cfg_attr(doc, katexit::katexit)]
/// solve equation: $x^4 + ax^3 + bx^2 + cx + d = 0$.
///
/// # Examples
///
/// ```
/// use matext4cgmath::solver;
/// use num_complex::Complex;
/// const EPS: f64 = 1.0e-10;
///
/// let mut res = solver::solve_quartic(1.0, -7.0, -1.0, 6.0);
/// // Even in the case of real solutions, the order in the array is not guaranteed.
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
/// let ans = [Complex::from(-3.0), Complex::from(-1.0), Complex::from(1.0), Complex::from(2.0)];
/// res.iter().zip(ans).for_each(|(x, y)| {
/// assert!(Complex::norm(x * x * x * x + x * x * x - 7.0 * x * x - x + 6.0) < EPS);
/// assert!(Complex::norm(x - y) < EPS, "{x} {y}");
/// });
/// ```
pub fn solve_quartic<F: BaseFloat>(a: F, b: F, c: F, d: F) -> [Complex<F>; 4] {
let one = F::one();
let two = one + one;
let three = one + two;
let four = two + two;
let six = two * three;
let eight = four + four;
let a_4 = a / four;
let p = b - six * a_4 * a_4;
let q = c - two * b * a_4 + eight * a_4 * a_4 * a_4;
let r = d - c * a_4 + b * a_4 * a_4 - three * a_4 * a_4 * a_4 * a_4;
let mut res = pre_solve_quartic(p, q, r);
res.iter_mut().for_each(|x| *x -= a_4);
res
}