Skip to content

Commit 6a68633

Browse files
authored
Readability refactor: kill the error-conversion boilerplate and wrapped chains (#8)
* refactor: let ? convert TriangulationError in the PyO3 layer Readability only; no behavior change (the full suite, stress sweeps, and benchmarks are identical). - impl From<TriangulationError> for PyErr replaces the inherent into_pyerr method, so the 31 call sites that wrapped every core call in .map_err(TriangulationError::into_pyerr) now just use ?; most of the awkward multi-line method chains in the bindings collapse back to single lines - given() collapses the repeated three-arm 'absent argument or explicit Python None' matches to one helper - the eight SimplicesProxy set operators delegate through a shared set_op/set_rop pair instead of repeating the snapshot dance * refactor: shared helpers for the robust-predicate plumbing Readability only; no behavior change. - coord_2d/coord_3d replace the robust::Coord construction closures that were redefined inline in every predicate - sign() replaces the repeated three-way if/else blocks - validate_simplex() dedupes the 'dim + 1 points of equal dimension' check that circumsphere, volume, precise_volume, and robust_in_circumsphere each spelled out * refactor: flatten the hard-to-read chains in the core Readability only; no behavior change. - TriangulationError::value() collapses the three-line Err(TriangulationError::Value("...".to_string())) returns - containing() uses plain loops instead of nested map/filter/collect chains wrapped across a dozen lines - facet_neighbour() names the 'other simplex across this facet' lookup shared by the walk and opposing_vertices - detach_vertex() dedupes the failed-insertion rollback that extend_hull and add_point both spelled out - summed_volume uses a for loop instead of a try_fold with a turbofish
1 parent 4b44e56 commit 6a68633

3 files changed

Lines changed: 251 additions & 359 deletions

File tree

‎src/geometry.rs‎

Lines changed: 83 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,7 @@ pub fn fast_3d_circumsphere(points: &[[f64; 3]; 4]) -> ([f64; 3], f64) {
303303
/// `|x_i|^2 - |x_0|^2` form cancels catastrophically for small simplices far
304304
/// from the origin). Returns NaNs for degenerate input, matching numpy.
305305
pub fn circumsphere(pts: &[Vec<f64>]) -> Result<(Vec<f64>, f64), GeometryError> {
306-
let dim = validate_points(pts)?;
307-
if pts.len() != dim + 1 {
308-
return Err(GeometryError::InvalidDimensions(format!(
309-
"Expected {} points for a {}-dimensional simplex",
310-
dim + 1,
311-
dim
312-
)));
313-
}
306+
let dim = validate_simplex(pts)?;
314307

315308
if dim == 2 {
316309
let points = [
@@ -441,6 +434,47 @@ pub fn orientation(face: &[Vec<f64>], origin: &[f64]) -> Result<i32, GeometryErr
441434
}
442435
}
443436

437+
/// `robust::Coord` from the first two coordinates of a point.
438+
fn coord_2d(point: &[f64]) -> robust::Coord<f64> {
439+
robust::Coord {
440+
x: point[0],
441+
y: point[1],
442+
}
443+
}
444+
445+
/// `robust::Coord3D` from the first three coordinates of a point.
446+
fn coord_3d(point: &[f64]) -> robust::Coord3D<f64> {
447+
robust::Coord3D {
448+
x: point[0],
449+
y: point[1],
450+
z: point[2],
451+
}
452+
}
453+
454+
fn sign(value: f64) -> i32 {
455+
if value > 0.0 {
456+
1
457+
} else if value < 0.0 {
458+
-1
459+
} else {
460+
0
461+
}
462+
}
463+
464+
/// Validates that `points` are a full simplex (`dim + 1` points of equal
465+
/// dimension) and returns `dim`.
466+
fn validate_simplex(points: &[Vec<f64>]) -> Result<usize, GeometryError> {
467+
let dim = validate_points(points)?;
468+
if points.len() != dim + 1 {
469+
return Err(GeometryError::InvalidDimensions(format!(
470+
"Expected {} points for a {}-dimensional simplex",
471+
dim + 1,
472+
dim
473+
)));
474+
}
475+
Ok(dim)
476+
}
477+
444478
/// Like [`orientation`], but exact in 2D and 3D: the sign is computed with
445479
/// Shewchuk's adaptive-precision predicates (via the `robust` crate), so it
446480
/// is correct even for slivers whose floating-point determinant rounds to
@@ -460,54 +494,21 @@ pub fn robust_orientation(face: &[Vec<f64>], origin: &[f64]) -> Result<i32, Geom
460494
));
461495
}
462496

497+
// orient2d(a, b, c) = det [[a - c], [b - c]] and orient3d(a, b, c, d) =
498+
// det [[a - d], [b - d], [c - d]]: exactly the determinants whose sign
499+
// `orientation` computes.
463500
match dim {
464-
// orient2d(a, b, c) = det [[a - c], [b - c]], exactly the
465-
// determinant `orientation` takes the sign of.
466-
2 => {
467-
let det = robust::orient2d(
468-
robust::Coord {
469-
x: face[0][0],
470-
y: face[0][1],
471-
},
472-
robust::Coord {
473-
x: face[1][0],
474-
y: face[1][1],
475-
},
476-
robust::Coord {
477-
x: origin[0],
478-
y: origin[1],
479-
},
480-
);
481-
Ok(if det > 0.0 {
482-
1
483-
} else if det < 0.0 {
484-
-1
485-
} else {
486-
0
487-
})
488-
}
489-
// orient3d(a, b, c, d) = det [[a - d], [b - d], [c - d]], exactly the
490-
// determinant `orientation` takes the sign of.
491-
3 => {
492-
let coord = |point: &[f64]| robust::Coord3D {
493-
x: point[0],
494-
y: point[1],
495-
z: point[2],
496-
};
497-
let det = robust::orient3d(
498-
coord(&face[0]),
499-
coord(&face[1]),
500-
coord(&face[2]),
501-
coord(origin),
502-
);
503-
Ok(if det > 0.0 {
504-
1
505-
} else if det < 0.0 {
506-
-1
507-
} else {
508-
0
509-
})
510-
}
501+
2 => Ok(sign(robust::orient2d(
502+
coord_2d(&face[0]),
503+
coord_2d(&face[1]),
504+
coord_2d(origin),
505+
))),
506+
3 => Ok(sign(robust::orient3d(
507+
coord_3d(&face[0]),
508+
coord_3d(&face[1]),
509+
coord_3d(&face[2]),
510+
coord_3d(origin),
511+
))),
511512
_ => orientation(face, origin),
512513
}
513514
}
@@ -527,56 +528,36 @@ pub fn robust_in_circumsphere(
527528
simplex: &[Vec<f64>],
528529
point: &[f64],
529530
) -> Result<Option<bool>, GeometryError> {
530-
let dim = validate_points(simplex)?;
531-
if simplex.len() != dim + 1 || point.len() != dim {
532-
return Err(GeometryError::InvalidDimensions(format!(
533-
"Expected {} points for a {}-dimensional simplex",
534-
dim + 1,
535-
dim
536-
)));
531+
let dim = validate_simplex(simplex)?;
532+
if point.len() != dim {
533+
return Err(GeometryError::InvalidDimensions(
534+
"Coordinates dimension mismatch".to_string(),
535+
));
537536
}
538537

538+
// incircle/insphere's sign follows the simplex's orientation;
539+
// multiplying by it makes the test orientation-independent.
539540
match dim {
540-
// incircle's sign follows the triangle's orientation; multiplying
541-
// by orient2d makes the test orientation-independent.
542541
2 => {
543-
let coord = |p: &[f64]| robust::Coord { x: p[0], y: p[1] };
544-
let orient =
545-
robust::orient2d(coord(&simplex[0]), coord(&simplex[1]), coord(&simplex[2]));
542+
let [a, b, c] = [&simplex[0], &simplex[1], &simplex[2]].map(|p| coord_2d(p));
543+
let orient = robust::orient2d(a, b, c);
546544
if orient == 0.0 {
547545
return Ok(Some(false));
548546
}
549-
let incircle = robust::incircle(
550-
coord(&simplex[0]),
551-
coord(&simplex[1]),
552-
coord(&simplex[2]),
553-
coord(point),
554-
);
555-
Ok(Some(incircle * orient > 0.0))
547+
Ok(Some(
548+
robust::incircle(a, b, c, coord_2d(point)) * orient > 0.0,
549+
))
556550
}
557551
3 => {
558-
let coord = |p: &[f64]| robust::Coord3D {
559-
x: p[0],
560-
y: p[1],
561-
z: p[2],
562-
};
563-
let orient = robust::orient3d(
564-
coord(&simplex[0]),
565-
coord(&simplex[1]),
566-
coord(&simplex[2]),
567-
coord(&simplex[3]),
568-
);
552+
let [a, b, c, d] =
553+
[&simplex[0], &simplex[1], &simplex[2], &simplex[3]].map(|p| coord_3d(p));
554+
let orient = robust::orient3d(a, b, c, d);
569555
if orient == 0.0 {
570556
return Ok(Some(false));
571557
}
572-
let insphere = robust::insphere(
573-
coord(&simplex[0]),
574-
coord(&simplex[1]),
575-
coord(&simplex[2]),
576-
coord(&simplex[3]),
577-
coord(point),
578-
);
579-
Ok(Some(insphere * orient > 0.0))
558+
Ok(Some(
559+
robust::insphere(a, b, c, d, coord_3d(point)) * orient > 0.0,
560+
))
580561
}
581562
_ => Ok(None),
582563
}
@@ -592,39 +573,21 @@ pub fn robust_in_circumsphere(
592573
/// floating-point determinants); use it where an accurate value matters,
593574
/// e.g. the volume-conservation check in Bowyer-Watson.
594575
pub fn precise_volume(vertices: &[Vec<f64>]) -> Result<f64, GeometryError> {
595-
let dim = validate_points(vertices)?;
596-
if vertices.len() != dim + 1 {
597-
return Err(GeometryError::InvalidDimensions(format!(
598-
"Expected {} points for a {}-dimensional simplex",
599-
dim + 1,
600-
dim
601-
)));
602-
}
603-
604-
match dim {
576+
match validate_simplex(vertices)? {
605577
2 => {
606-
let coord = |point: &[f64]| robust::Coord {
607-
x: point[0],
608-
y: point[1],
609-
};
610578
let det = robust::orient2d(
611-
coord(&vertices[1]),
612-
coord(&vertices[2]),
613-
coord(&vertices[0]),
579+
coord_2d(&vertices[1]),
580+
coord_2d(&vertices[2]),
581+
coord_2d(&vertices[0]),
614582
);
615583
Ok(det.abs() / 2.0)
616584
}
617585
3 => {
618-
let coord = |point: &[f64]| robust::Coord3D {
619-
x: point[0],
620-
y: point[1],
621-
z: point[2],
622-
};
623586
let det = robust::orient3d(
624-
coord(&vertices[1]),
625-
coord(&vertices[2]),
626-
coord(&vertices[3]),
627-
coord(&vertices[0]),
587+
coord_3d(&vertices[1]),
588+
coord_3d(&vertices[2]),
589+
coord_3d(&vertices[3]),
590+
coord_3d(&vertices[0]),
628591
);
629592
Ok(det.abs() / 6.0)
630593
}
@@ -635,14 +598,7 @@ pub fn precise_volume(vertices: &[Vec<f64>]) -> Result<f64, GeometryError> {
635598
/// Volume of a `dim`-simplex given its `dim + 1` vertices
636599
/// (`|det| / dim!` of the edge matrix).
637600
pub fn volume(vertices: &[Vec<f64>]) -> Result<f64, GeometryError> {
638-
let dim = validate_points(vertices)?;
639-
if vertices.len() != dim + 1 {
640-
return Err(GeometryError::InvalidDimensions(format!(
641-
"Expected {} points for a {}-dimensional simplex",
642-
dim + 1,
643-
dim
644-
)));
645-
}
601+
let dim = validate_simplex(vertices)?;
646602

647603
let mut matrix = vec![vec![0.0; dim]; dim];
648604
let x0 = &vertices[0];

0 commit comments

Comments
 (0)