PERF-2 P1: exact chunked polyline index for the O-grid's sliding outer ring — PolylineIndex::nearest returns nearest_on_polyline's point bit for bit (same segments, same order, same strict minimum; chunks skipped only when their box is farther than the best by more than 1e-12 relative), used by the Winslow sweep and the two ring projections; pin: 0 mismatches over 39,864 queries on a 3,166-point rounded outline including vertices, mid-points and ties
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build API Documentation (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
CI / Format Check (push) Failing after 22s
CI / Build (ubuntu-latest) (push) Failing after 2m34s
CI / Clippy Check (push) Failing after 3m6s
Performance Benchmarks / Run Benchmarks (push) Successful in 3m47s
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build API Documentation (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
CI / Format Check (push) Failing after 22s
CI / Build (ubuntu-latest) (push) Failing after 2m34s
CI / Clippy Check (push) Failing after 3m6s
Performance Benchmarks / Run Benchmarks (push) Successful in 3m47s
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
11f71b832c
commit
01830e5c8d
@@ -280,7 +280,88 @@ fn point_at_fraction(pts: &[[f64; 2]], cum: &[f64], f: f64) -> [f64; 2] {
|
||||
}
|
||||
|
||||
/// Nearest point on the closed polyline `pts` to `q` (segment projection).
|
||||
fn nearest_on_polyline(pts: &[[f64; 2]], q: [f64; 2]) -> [f64; 2] {
|
||||
/// Chunked bounding boxes over a closed polyline for EXACT nearest-point
|
||||
/// queries (PERF-2 P1, `docs/perf2_campaign.md`): [`Self::nearest`] returns
|
||||
/// the same point as [`nearest_on_polyline`] — the same segments are tested
|
||||
/// in the same order with the same strict-minimum rule — but skips every
|
||||
/// chunk of segments whose box is farther from the query than the best
|
||||
/// distance so far (with a 1e-12 relative margin, so a box's rounded
|
||||
/// lower bound can never hide a segment that could still win).
|
||||
pub struct PolylineIndex<'a> {
|
||||
pts: &'a [[f64; 2]],
|
||||
chunk: usize,
|
||||
/// Per chunk: `[xmin, xmax, ymin, ymax]` of its segments' end points.
|
||||
boxes: Vec<[f64; 4]>,
|
||||
}
|
||||
|
||||
impl<'a> PolylineIndex<'a> {
|
||||
/// Index `pts` (a closed polyline: segment `i` joins `pts[i]` and
|
||||
/// `pts[(i + 1) % n]`) in chunks of 32 segments.
|
||||
pub fn new(pts: &'a [[f64; 2]]) -> Self {
|
||||
let n = pts.len();
|
||||
let chunk = 32;
|
||||
let boxes = (0..n.div_ceil(chunk))
|
||||
.map(|c| {
|
||||
let mut b = [
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
];
|
||||
let start = c * chunk;
|
||||
let end = (start + chunk).min(n);
|
||||
// The chunk's segments' end points: `start..=end` (wrapping).
|
||||
for i in start..=end {
|
||||
let p = pts[i % n];
|
||||
b[0] = b[0].min(p[0]);
|
||||
b[1] = b[1].max(p[0]);
|
||||
b[2] = b[2].min(p[1]);
|
||||
b[3] = b[3].max(p[1]);
|
||||
}
|
||||
b
|
||||
})
|
||||
.collect();
|
||||
Self { pts, chunk, boxes }
|
||||
}
|
||||
|
||||
/// The nearest point of the polyline to `q`, bit for bit the answer of
|
||||
/// [`nearest_on_polyline`].
|
||||
pub fn nearest(&self, q: [f64; 2]) -> [f64; 2] {
|
||||
let pts = self.pts;
|
||||
let n = pts.len();
|
||||
let (mut best, mut best_d) = (pts[0], f64::INFINITY);
|
||||
for (c, b) in self.boxes.iter().enumerate() {
|
||||
let dx = (b[0] - q[0]).max(0.0).max(q[0] - b[1]);
|
||||
let dy = (b[2] - q[1]).max(0.0).max(q[1] - b[3]);
|
||||
if dx * dx + dy * dy > best_d * (1.0 + 1e-12) {
|
||||
continue;
|
||||
}
|
||||
let start = c * self.chunk;
|
||||
let end = (start + self.chunk).min(n);
|
||||
for i in start..end {
|
||||
let (a, b) = (pts[i], pts[(i + 1) % n]);
|
||||
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
|
||||
let l2 = dx * dx + dy * dy;
|
||||
let t = if l2 > 0.0 {
|
||||
(((q[0] - a[0]) * dx + (q[1] - a[1]) * dy) / l2).clamp(0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let p = [a[0] + t * dx, a[1] + t * dy];
|
||||
let d = (p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2);
|
||||
if d < best_d {
|
||||
best_d = d;
|
||||
best = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
}
|
||||
|
||||
/// The nearest point of the closed polyline `pts` to `q` by a scan of every
|
||||
/// segment (the reference for [`PolylineIndex`]).
|
||||
pub fn nearest_on_polyline(pts: &[[f64; 2]], q: [f64; 2]) -> [f64; 2] {
|
||||
let n = pts.len();
|
||||
let (mut best, mut best_d) = (pts[0], f64::INFINITY);
|
||||
for i in 0..n {
|
||||
@@ -489,10 +570,11 @@ pub fn winslow_smooth(
|
||||
sweeps += 1;
|
||||
moved = 0.0;
|
||||
if let Some(curve) = outer_curve {
|
||||
let index = PolylineIndex::new(curve);
|
||||
for i in 0..ns {
|
||||
let (a, b) = (x[nn - 2][i], x[nn - 1][i]);
|
||||
let cand = [2.0 * b[0] - a[0], 2.0 * b[1] - a[1]];
|
||||
let p = nearest_on_polyline(curve, cand);
|
||||
let p = index.nearest(cand);
|
||||
let d = ((p[0] - x[nn][i][0]).powi(2) + (p[1] - x[nn][i][1]).powi(2)).sqrt();
|
||||
moved = moved.max(d);
|
||||
x[nn][i] = p;
|
||||
@@ -1109,6 +1191,7 @@ fn o_grid_from_outline(
|
||||
let t0 = std::time::Instant::now();
|
||||
let hull = convex_hull(hull_src);
|
||||
let outer_poly = offset_convex_polygon(&hull, offset, 24);
|
||||
let outer_index = PolylineIndex::new(&outer_poly);
|
||||
regen_charge(1, t0);
|
||||
let t0 = std::time::Instant::now();
|
||||
// Initial outer ring: the inner point pushed along its outward normal
|
||||
@@ -1125,10 +1208,7 @@ fn o_grid_from_outline(
|
||||
let (dx, dy) = (next[0] - prev[0], next[1] - prev[1]);
|
||||
let l = (dx * dx + dy * dy).sqrt().max(1e-300);
|
||||
let normal = [-dy / l, dx / l];
|
||||
nearest_on_polyline(
|
||||
&outer_poly,
|
||||
[cur[0] + offset * normal[0], cur[1] + offset * normal[1]],
|
||||
)
|
||||
outer_index.nearest([cur[0] + offset * normal[0], cur[1] + offset * normal[1]])
|
||||
})
|
||||
.collect();
|
||||
regen_charge(2, t0);
|
||||
@@ -1146,10 +1226,7 @@ fn o_grid_from_outline(
|
||||
// rows' converged distribution (the crude normal push would
|
||||
// undo it every build — measured as the interior moving
|
||||
// 0.05 h with the wall at rest).
|
||||
rows[nn] = rows[nn]
|
||||
.iter()
|
||||
.map(|&p| nearest_on_polyline(&outer_poly, p))
|
||||
.collect();
|
||||
rows[nn] = rows[nn].iter().map(|&p| outer_index.nearest(p)).collect();
|
||||
rows
|
||||
}
|
||||
None => eta
|
||||
|
||||
Reference in New Issue
Block a user