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>
154 lines
5.2 KiB
Rust
154 lines
5.2 KiB
Rust
use crate::*;
|
|
|
|
impl<F: BaseFloat> EigenValues for Matrix2<F> {
|
|
type EigenValues = [Complex<F>; 2];
|
|
fn eigenvalues(self) -> [Complex<F>; 2] {
|
|
solver::solve_quadratic(-self.trace(), self.determinant())
|
|
}
|
|
}
|
|
|
|
impl<F: BaseFloat> EigenValues for Matrix3<F> {
|
|
type EigenValues = [Complex<F>; 3];
|
|
fn eigenvalues(self) -> [Complex<F>; 3] {
|
|
let poly2 = self[0][0] * self[1][1] + self[0][0] * self[2][2] + self[1][1] * self[2][2]
|
|
- self[0][1] * self[1][0]
|
|
- self[1][2] * self[2][1]
|
|
- self[0][2] * self[2][0];
|
|
solver::solve_cubic(-self.trace(), poly2, -self.determinant())
|
|
}
|
|
}
|
|
|
|
impl<F: BaseFloat> EigenValues for Matrix4<F> {
|
|
type EigenValues = [Complex<F>; 4];
|
|
fn eigenvalues(self) -> [Complex<F>; 4] {
|
|
let poly2 = self[0][0] * self[1][1]
|
|
+ self[0][0] * self[2][2]
|
|
+ self[0][0] * self[3][3]
|
|
+ self[1][1] * self[2][2]
|
|
+ self[1][1] * self[3][3]
|
|
+ self[2][2] * self[3][3]
|
|
- self[0][1] * self[1][0]
|
|
- self[0][2] * self[2][0]
|
|
- self[0][3] * self[3][0]
|
|
- self[1][2] * self[2][1]
|
|
- self[1][3] * self[3][1]
|
|
- self[2][3] * self[3][2];
|
|
let poly3 = (self[0][0] * self[1][1] + self[0][0] * self[2][2] + self[1][1] * self[2][2]
|
|
- self[0][1] * self[1][0]
|
|
- self[0][2] * self[2][0]
|
|
- self[1][2] * self[2][1])
|
|
* self[3][3]
|
|
+ (self[2][0] * self[3][2] - self[2][2] * self[3][0] + self[1][0] * self[3][1]
|
|
- self[1][1] * self[3][0])
|
|
* self[0][3]
|
|
+ (self[2][1] * self[3][2] - self[2][2] * self[3][1] + self[3][0] * self[0][1]
|
|
- self[3][1] * self[0][0])
|
|
* self[1][3]
|
|
+ (self[3][1] * self[1][2] - self[3][2] * self[1][1] + self[3][0] * self[0][2]
|
|
- self[3][2] * self[0][0])
|
|
* self[2][3]
|
|
+ Matrix3::from_cols(self[0].truncate(), self[1].truncate(), self[2].truncate())
|
|
.determinant();
|
|
solver::solve_quartic(-self.trace(), poly2, -poly3, self.determinant())
|
|
}
|
|
}
|
|
|
|
impl<F: BaseFloat> OperatorNorm for Matrix2<F> {
|
|
#[inline]
|
|
fn norm_l1(self) -> F {
|
|
F::max(
|
|
F::abs(self[0][0]) + F::abs(self[0][1]),
|
|
F::abs(self[1][0]) + F::abs(self[1][1]),
|
|
)
|
|
}
|
|
#[inline]
|
|
fn norm_linf(self) -> F {
|
|
F::max(
|
|
F::abs(self[0][0]) + F::abs(self[1][0]),
|
|
F::abs(self[0][1]) + F::abs(self[1][1]),
|
|
)
|
|
}
|
|
#[inline]
|
|
fn norm_l2(self) -> F {
|
|
let eigens = (self.transpose() * self).eigenvalues();
|
|
F::sqrt(F::max(eigens[0].re, eigens[1].re))
|
|
}
|
|
}
|
|
|
|
impl<F: BaseFloat> OperatorNorm for Matrix3<F> {
|
|
#[inline]
|
|
fn norm_l1(self) -> F {
|
|
F::max(
|
|
F::max(
|
|
F::abs(self[0][0]) + F::abs(self[0][1]) + F::abs(self[0][2]),
|
|
F::abs(self[1][0]) + F::abs(self[1][1]) + F::abs(self[1][2]),
|
|
),
|
|
F::abs(self[2][0]) + F::abs(self[2][1]) + F::abs(self[2][2]),
|
|
)
|
|
}
|
|
#[inline]
|
|
fn norm_linf(self) -> F {
|
|
F::max(
|
|
F::max(
|
|
F::abs(self[0][0]) + F::abs(self[1][0]) + F::abs(self[2][0]),
|
|
F::abs(self[0][1]) + F::abs(self[1][1]) + F::abs(self[2][1]),
|
|
),
|
|
F::abs(self[0][2]) + F::abs(self[1][2]) + F::abs(self[2][2]),
|
|
)
|
|
}
|
|
#[inline]
|
|
fn norm_l2(self) -> F {
|
|
let eigens = (self.transpose() * self).eigenvalues();
|
|
F::sqrt(F::max(F::max(eigens[0].re, eigens[1].re), eigens[2].re))
|
|
}
|
|
}
|
|
|
|
impl<F: BaseFloat> OperatorNorm for Matrix4<F> {
|
|
#[inline]
|
|
fn norm_l1(self) -> F {
|
|
F::max(
|
|
F::max(
|
|
F::max(
|
|
F::abs(self[0][0])
|
|
+ F::abs(self[0][1])
|
|
+ F::abs(self[0][2])
|
|
+ F::abs(self[0][3]),
|
|
F::abs(self[1][0])
|
|
+ F::abs(self[1][1])
|
|
+ F::abs(self[1][2])
|
|
+ F::abs(self[1][3]),
|
|
),
|
|
F::abs(self[2][0]) + F::abs(self[2][1]) + F::abs(self[2][2]) + F::abs(self[2][3]),
|
|
),
|
|
F::abs(self[3][0]) + F::abs(self[3][1]) + F::abs(self[3][2]) + F::abs(self[3][3]),
|
|
)
|
|
}
|
|
#[inline]
|
|
fn norm_linf(self) -> F {
|
|
F::max(
|
|
F::max(
|
|
F::max(
|
|
F::abs(self[0][0])
|
|
+ F::abs(self[1][0])
|
|
+ F::abs(self[2][0])
|
|
+ F::abs(self[3][0]),
|
|
F::abs(self[0][1])
|
|
+ F::abs(self[1][1])
|
|
+ F::abs(self[2][1])
|
|
+ F::abs(self[3][1]),
|
|
),
|
|
F::abs(self[0][2]) + F::abs(self[1][2]) + F::abs(self[2][2]) + F::abs(self[3][2]),
|
|
),
|
|
F::abs(self[0][3]) + F::abs(self[1][3]) + F::abs(self[2][3]) + F::abs(self[3][3]),
|
|
)
|
|
}
|
|
#[inline]
|
|
fn norm_l2(self) -> F {
|
|
let eigens = (self.transpose() * self).eigenvalues();
|
|
F::sqrt(F::max(
|
|
F::max(F::max(eigens[0].re, eigens[1].re), eigens[2].re),
|
|
eigens[3].re,
|
|
))
|
|
}
|
|
}
|