test: failing regression test for #261: Rectangular pattern of a hole cut scales super-linearly (#276)

* test: reproduce #261 (Rectangular pattern of a hole cut scales super-linearly)

* Patterns of many hole cuts scale near-linearly (#261)

Two quadratic costs:
- Meshing a face tested every point against every segment of every boundary
  loop, so a plate face with hundreds of holes meshed in quadratic time. The
  vendored truck-meshalgo inside test now skips loops whose parameter box is
  clear of the point (they wind zero times around it).
- A pattern cut each copy in turn, rebuilding and re-checking an ever more
  complex target per copy. Copies whose boxes are clear of each other are now
  one tool of many pieces (kernel::union_apart, no intersection work), cut once.

512 holes now take ~7 s instead of not finishing. The test's volume tolerance is
widened from 1e-6 relative to 0.1 % of each hole: the measure meshes the bores
and reads each hole ~0.06 % small, on main too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hole-pattern test: a budget CI can meet; coarser apart-box meshes (#261)

CI runs every test binary at once and is about ten times slower than a
machine running this test alone (15 tests passed 60 s in the last run), so the
512-hole pattern (about 6 s alone) hit the 60 s timeout there. The budget now
only has to tell linear from the quadratic hang: 300 s timeout, 120 s budget.
union_apart meshes each copy coarsely (2 % of its size, box grown by twice that).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Hole-pattern test checks the scaling, not a wall-clock time (#261)

CI runs every test binary at once: the 512-hole pattern (about 6 s alone, 10 s
CPU single-threaded) took 177 s there, so no fixed time is reliable. The test
now patterns 128 and then 512 holes and requires the second to take under 8x
the first (linear is 4x, measured 3.5-4.2x here; the old quadratic cost was 16x
or more), each with the volume check and a 900 s hang guard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: bherbruck <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-12 11:18:28 -04:00
1 parent f525704192
commit c7f0139ad4
6 files changed
+109 -4

No files matched your search

+3
View File
@@ -37,6 +37,9 @@ Changes:
## truck-meshalgo 0.4.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
Changes:
- `tessellation/triangulation.rs`: the inside test of a face's boundary skips loops whose
parameter box is clear of the point (it tested every segment of every loop, so a face with
hundreds of holes meshed in quadratic time).
- `tessellation/triangulation.rs`: meshed faces and edges are built unchecked: faces whose loop
runs along a seam edge both ways and closed edges (a full circle on one vertex) are valid,
but the debug-build checks rejected them (debug builds could not mesh them).
+15 -3
View File
@@ -344,8 +344,10 @@ fn get_mindiff(u: f64, u0: f64, up: f64) -> f64 {
(-2..=2).map(closure).min_by(abs_diff(u0)).unwrap()
}
/// SolveCraft: the second field holds each loop's parameter box, so `include` skips loops that
/// cannot hold the point (a face with hundreds of holes meshed in quadratic time).
#[derive(Debug, Default, Clone)]
struct PolyBoundary(Vec<Vec<Point2>>);
struct PolyBoundary(Vec<Vec<Point2>>, Vec<BoundingBox<Point2>>);
fn normalize_range(curve: &mut Vec<Point2>, compidx: usize, (u0, u1): (f64, f64)) {
let p = curve[0];
@@ -485,16 +487,26 @@ impl PolyBoundary {
closed.push(connect_edges([vec0, vec1, vec2, vec3]));
}
}
Self(closed)
let boxes = closed.iter().map(|curve| curve.iter().collect()).collect();
Self(closed, boxes)
}
/// whether `c` is included in the domain with boundary = `self`.
fn include(&self, c: Point2) -> bool {
let t = 2.0 * std::f64::consts::PI * HashGen::hash1(c);
let r = Vector2::new(f64::cos(t), f64::sin(t));
// SolveCraft: a closed loop whose box is clear of `c` winds zero times around it, and
// no crossing with it lies within the tolerance of `c`: it changes nothing here.
let margin = 2.0 * TOLERANCE;
let near = |bdb: &BoundingBox<Point2>| {
let (min, max) = (bdb.min(), bdb.max());
!(c.x < min.x - margin || c.x > max.x + margin || c.y < min.y - margin || c.y > max.y + margin)
};
self.0
.iter()
.flat_map(|vec| vec.iter().circular_tuple_windows())
.zip(&self.1)
.filter(|(_, bdb)| near(bdb))
.flat_map(|(vec, _)| vec.iter().circular_tuple_windows())
.try_fold(0_i32, move |counter, (p0, p1)| {
let a = p0 - c;
let b = p1 - c;