#![forbid(unsafe_code)]
pub mod pl_manifold {
use crate::core::algorithms::pl_manifold_repair::{
PlManifoldRepairConfig, PlManifoldRepairError, PlManifoldRepairStage,
PlManifoldRepairStats, manifold_error_matches_repair_stage, repair_facet_oversharing,
repair_pl_manifold_topology,
};
use crate::core::collections::SimplexVertexKeyBuffer;
use crate::core::simplex::{Simplex, SimplexValidationError};
use crate::core::tds::{Tds, TdsConstructionError, TdsError, VertexKey};
use crate::core::vertex::Vertex;
use crate::geometry::traits::coordinate::CoordinateConversionError;
use crate::geometry::util::safe_usize_to_scalar;
use crate::topology::manifold::{
ManifoldError, ValidatedFacetDegreeMap, validate_closed_boundary_from_validated_facet_map,
validate_ridge_links, validate_vertex_links_from_validated_facet_map,
};
use crate::topology::traits::topological_space::GlobalTopology;
use thiserror::Error;
#[derive(Clone, Debug)]
#[must_use]
pub struct OversharedFacetOrphanCleanupFixture3d {
tds: Tds<(), (), 3>,
cluster_count: usize,
}
impl OversharedFacetOrphanCleanupFixture3d {
#[must_use]
pub const fn repair_input_storage(&self) -> &Tds<(), (), 3> {
&self.tds
}
#[must_use]
pub const fn cluster_count(&self) -> usize {
self.cluster_count
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct TargetedTopologyRepairFixture<const D: usize> {
tds: Tds<(), (), D>,
cluster_count: usize,
stage: PlManifoldRepairStage,
}
impl<const D: usize> TargetedTopologyRepairFixture<D> {
#[must_use]
pub const fn repair_input_storage(&self) -> &Tds<(), (), D> {
&self.tds
}
#[must_use]
pub const fn cluster_count(&self) -> usize {
self.cluster_count
}
#[must_use]
pub const fn stage(&self) -> PlManifoldRepairStage {
self.stage
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum PlManifoldRepairFixtureError {
#[error("PL-manifold repair fixture requires at least one cluster")]
EmptyClusterCount,
#[error("failed to convert cluster index {cluster_index} to a coordinate offset: {source}")]
ClusterIndexConversion {
cluster_index: usize,
source: CoordinateConversionError,
},
#[error(
"failed to convert grid index {grid_index} for cluster {cluster_index} to a coordinate offset: {source}"
)]
GridIndexConversion {
cluster_index: usize,
grid_index: usize,
source: CoordinateConversionError,
},
#[error("failed to create vertex for cluster {cluster_index}: {source}")]
Vertex {
cluster_index: usize,
source: CoordinateConversionError,
},
#[error("failed to insert vertex for cluster {cluster_index}: {source}")]
VertexInsert {
cluster_index: usize,
source: Box<TdsConstructionError>,
},
#[error("failed to create simplex for cluster {cluster_index}: {source}")]
Simplex {
cluster_index: usize,
source: SimplexValidationError,
},
#[error("failed to insert simplex for cluster {cluster_index}: {source}")]
SimplexInsert {
cluster_index: usize,
source: Box<TdsConstructionError>,
},
#[error("PL-manifold repair fixture has invalid structural TDS state: {source}")]
StructuralValidation {
source: TdsError,
},
#[error(
"PL-manifold repair fixture with {cluster_count} clusters did not over-share facets"
)]
MissingOversharedFacet {
cluster_count: usize,
},
#[error(
"PL-manifold repair fixture with {cluster_count} clusters did not produce a {stage} violation"
)]
MissingTargetedViolation {
stage: PlManifoldRepairStage,
cluster_count: usize,
},
#[error("expected {stage} fixture violation, but validation reported: {source}")]
UnexpectedTargetedViolation {
stage: PlManifoldRepairStage,
source: ManifoldError,
},
#[error("PL-manifold repair fixture validation failed: {source}")]
Repair {
source: PlManifoldRepairError,
},
#[error(
"PL-manifold repair fixture removed {actual_simplices} simplices and {actual_vertices} vertices; expected {expected_simplices} simplices and {expected_vertices} vertices"
)]
UnexpectedRepairStats {
expected_simplices: usize,
actual_simplices: usize,
expected_vertices: usize,
actual_vertices: usize,
},
#[error(
"{stage} fixture removed {actual_simplices} simplices; expected at least {expected_simplices_at_least}"
)]
UnexpectedTargetedRepairStats {
stage: PlManifoldRepairStage,
expected_simplices_at_least: usize,
actual_simplices: usize,
},
}
fn overshared_facet_orphan_cleanup_3d(
cluster_count: usize,
) -> Result<OversharedFacetOrphanCleanupFixture3d, PlManifoldRepairFixtureError> {
if cluster_count == 0 {
return Err(PlManifoldRepairFixtureError::EmptyClusterCount);
}
let mut tds = Tds::empty();
for cluster_index in 0..cluster_count {
insert_overshared_cluster(&mut tds, cluster_index)?;
}
validate_structural_fixture_state(&tds)?;
let facet_map = tds
.build_facet_to_simplices_map()
.map_err(|source| PlManifoldRepairFixtureError::StructuralValidation { source })?;
if ValidatedFacetDegreeMap::try_from_facet_map(&facet_map).is_ok() {
return Err(PlManifoldRepairFixtureError::MissingOversharedFacet { cluster_count });
}
Ok(OversharedFacetOrphanCleanupFixture3d { tds, cluster_count })
}
pub fn validated_overshared_facet_orphan_cleanup_3d(
cluster_count: usize,
) -> Result<OversharedFacetOrphanCleanupFixture3d, PlManifoldRepairFixtureError> {
let fixture = overshared_facet_orphan_cleanup_3d(cluster_count)?;
let mut repaired = fixture.tds.clone();
let stats = repair_overshared_facet_orphan_cleanup_3d(&mut repaired)
.map_err(|source| PlManifoldRepairFixtureError::Repair { source })?;
if stats.simplices_removed != fixture.cluster_count
|| stats.removed_vertices.len() != fixture.cluster_count
{
return Err(PlManifoldRepairFixtureError::UnexpectedRepairStats {
expected_simplices: fixture.cluster_count,
actual_simplices: stats.simplices_removed,
expected_vertices: fixture.cluster_count,
actual_vertices: stats.removed_vertices.len(),
});
}
Ok(fixture)
}
pub fn validated_boundary_ridge_multiplicity_repair_3d(
cluster_count: usize,
) -> Result<TargetedTopologyRepairFixture<3>, PlManifoldRepairFixtureError> {
validate_targeted_fixture(targeted_topology_fixture(
cluster_count,
PlManifoldRepairStage::BoundaryRidgeMultiplicity,
insert_boundary_ridge_multiplicity_cluster,
)?)
}
pub fn validated_ridge_link_repair_2d(
cluster_count: usize,
) -> Result<TargetedTopologyRepairFixture<2>, PlManifoldRepairFixtureError> {
validate_targeted_fixture(targeted_topology_fixture(
cluster_count,
PlManifoldRepairStage::RidgeLink,
insert_ridge_link_cluster,
)?)
}
pub fn validated_vertex_link_repair_3d(
cluster_count: usize,
) -> Result<TargetedTopologyRepairFixture<3>, PlManifoldRepairFixtureError> {
validate_targeted_fixture(targeted_topology_fixture(
cluster_count,
PlManifoldRepairStage::VertexLink,
insert_vertex_link_cluster,
)?)
}
pub fn repair_overshared_facet_orphan_cleanup_3d(
tds: &mut Tds<(), (), 3>,
) -> Result<PlManifoldRepairStats<(), (), 3>, PlManifoldRepairError> {
repair_facet_oversharing(tds, &PlManifoldRepairConfig::default())
}
pub fn repair_targeted_pl_manifold_topology<const D: usize>(
tds: &mut Tds<(), (), D>,
) -> Result<PlManifoldRepairStats<(), (), D>, PlManifoldRepairError> {
repair_pl_manifold_topology(
tds,
GlobalTopology::Euclidean,
&PlManifoldRepairConfig::default(),
)
}
fn insert_overshared_cluster(
tds: &mut Tds<(), (), 3>,
cluster_index: usize,
) -> Result<(), PlManifoldRepairFixtureError> {
let offset = cluster_offset(cluster_index, 4.0)?;
let vertices = [
insert_vertex(tds, cluster_index, [offset, 0.0, 0.0])?,
insert_vertex(tds, cluster_index, [offset + 1.0, 0.0, 0.0])?,
insert_vertex(tds, cluster_index, [offset, 1.0, 0.0])?,
insert_vertex(tds, cluster_index, [offset, 0.0, 1.0])?,
insert_vertex(tds, cluster_index, [offset, 0.0, -1.0])?,
insert_vertex(tds, cluster_index, [offset + 1.0e-6, 1.0e-6, 1.0e-6])?,
];
insert_checked_simplex(
tds,
cluster_index,
[vertices[0], vertices[1], vertices[2], vertices[3]],
)?;
insert_checked_simplex(
tds,
cluster_index,
[vertices[0], vertices[1], vertices[2], vertices[4]],
)?;
insert_prechecked_simplex(
tds,
cluster_index,
[vertices[0], vertices[1], vertices[2], vertices[5]],
)?;
Ok(())
}
fn targeted_topology_fixture<const D: usize>(
cluster_count: usize,
stage: PlManifoldRepairStage,
mut insert_cluster: impl FnMut(
&mut Tds<(), (), D>,
usize,
) -> Result<(), PlManifoldRepairFixtureError>,
) -> Result<TargetedTopologyRepairFixture<D>, PlManifoldRepairFixtureError> {
if cluster_count == 0 {
return Err(PlManifoldRepairFixtureError::EmptyClusterCount);
}
let mut tds = Tds::empty();
for cluster_index in 0..cluster_count {
insert_cluster(&mut tds, cluster_index)?;
}
validate_structural_fixture_state(&tds)?;
validate_expected_targeted_violation(&tds, stage, cluster_count)?;
Ok(TargetedTopologyRepairFixture {
tds,
cluster_count,
stage,
})
}
fn validate_targeted_fixture<const D: usize>(
fixture: TargetedTopologyRepairFixture<D>,
) -> Result<TargetedTopologyRepairFixture<D>, PlManifoldRepairFixtureError> {
let mut repaired = fixture.tds.clone();
let stats = repair_targeted_pl_manifold_topology(&mut repaired)
.map_err(|source| PlManifoldRepairFixtureError::Repair { source })?;
if !stats.succeeded || stats.simplices_removed < fixture.cluster_count {
return Err(
PlManifoldRepairFixtureError::UnexpectedTargetedRepairStats {
stage: fixture.stage,
expected_simplices_at_least: fixture.cluster_count,
actual_simplices: stats.simplices_removed,
},
);
}
Ok(fixture)
}
fn validate_expected_targeted_violation<const D: usize>(
tds: &Tds<(), (), D>,
stage: PlManifoldRepairStage,
cluster_count: usize,
) -> Result<(), PlManifoldRepairFixtureError> {
match validate_targeted_stage(tds, stage) {
Ok(()) => Err(PlManifoldRepairFixtureError::MissingTargetedViolation {
stage,
cluster_count,
}),
Err(source) if manifold_error_matches_repair_stage(&source, stage) => Ok(()),
Err(source) => {
Err(PlManifoldRepairFixtureError::UnexpectedTargetedViolation { stage, source })
}
}
}
fn validate_targeted_stage<const D: usize>(
tds: &Tds<(), (), D>,
stage: PlManifoldRepairStage,
) -> Result<(), ManifoldError> {
match stage {
PlManifoldRepairStage::BoundaryRidgeMultiplicity => {
validate_boundary_ridge_multiplicity_stage(tds)
}
PlManifoldRepairStage::RidgeLink => validate_ridge_links(tds),
PlManifoldRepairStage::VertexLink => validate_vertex_link_stage(tds),
}
}
fn validate_boundary_ridge_multiplicity_stage<const D: usize>(
tds: &Tds<(), (), D>,
) -> Result<(), ManifoldError> {
let facet_to_simplices = tds.build_facet_to_simplices_map()?;
let facet_to_simplices = ValidatedFacetDegreeMap::try_from_facet_map(&facet_to_simplices)?;
validate_closed_boundary_from_validated_facet_map(
tds,
facet_to_simplices,
GlobalTopology::Euclidean,
)
}
fn validate_vertex_link_stage<const D: usize>(
tds: &Tds<(), (), D>,
) -> Result<(), ManifoldError> {
let facet_to_simplices = tds.build_facet_to_simplices_map()?;
let facet_to_simplices = ValidatedFacetDegreeMap::try_from_facet_map(&facet_to_simplices)?;
validate_vertex_links_from_validated_facet_map(
tds,
facet_to_simplices,
GlobalTopology::Euclidean,
)
}
fn insert_boundary_ridge_multiplicity_cluster(
tds: &mut Tds<(), (), 3>,
cluster_index: usize,
) -> Result<(), PlManifoldRepairFixtureError> {
let offset = cluster_offset(cluster_index, 6.0)?;
let shared_v0 = insert_vertex(tds, cluster_index, [offset, 0.0, 0.0])?;
let shared_v1 = insert_vertex(tds, cluster_index, [offset + 2.0, 0.0, 0.0])?;
let tet1_v2 = insert_vertex(tds, cluster_index, [offset + 0.1, 1.0, 0.2])?;
let tet1_v3 = insert_vertex(tds, cluster_index, [offset + 0.2, 0.3, 1.3])?;
let tet2_v2 = insert_vertex(tds, cluster_index, [offset + 0.4, -1.1, 0.7])?;
let tet2_v3 = insert_vertex(tds, cluster_index, [offset + 0.6, 0.2, -1.4])?;
for vertices in [
[shared_v0, shared_v1, tet1_v2, tet1_v3],
[shared_v0, shared_v1, tet2_v2, tet2_v3],
] {
insert_checked_simplex(tds, cluster_index, vertices)?;
}
Ok(())
}
fn insert_ridge_link_cluster(
tds: &mut Tds<(), (), 2>,
cluster_index: usize,
) -> Result<(), PlManifoldRepairFixtureError> {
let offset = cluster_offset(cluster_index, 16.0)?;
let v0 = insert_vertex(tds, cluster_index, [offset, 0.0])?;
let v1 = insert_vertex(tds, cluster_index, [offset + 1.0, 0.0])?;
let v2 = insert_vertex(tds, cluster_index, [offset, 1.0])?;
let v3 = insert_vertex(tds, cluster_index, [offset + 1.0, 1.0])?;
let v4 = insert_vertex(tds, cluster_index, [offset + 8.0, 8.0])?;
let v5 = insert_vertex(tds, cluster_index, [offset + 9.0, 8.0])?;
let v6 = insert_vertex(tds, cluster_index, [offset + 8.0, 9.0])?;
for vertices in [
[v0, v1, v2],
[v0, v1, v3],
[v0, v2, v3],
[v1, v2, v3],
[v0, v4, v5],
[v0, v4, v6],
[v0, v5, v6],
[v4, v5, v6],
] {
insert_checked_simplex(tds, cluster_index, vertices)?;
}
Ok(())
}
fn insert_vertex_link_cluster(
tds: &mut Tds<(), (), 3>,
cluster_index: usize,
) -> Result<(), PlManifoldRepairFixtureError> {
const N: usize = 3;
const M: usize = 3;
let offset = cluster_offset(cluster_index, 8.0)?;
let mut grid: Vec<Vec<VertexKey>> = Vec::with_capacity(N);
for i in 0..N {
let mut row = Vec::with_capacity(M);
for j in 0..M {
let x = offset + grid_coordinate(cluster_index, i)?;
let y = grid_coordinate(cluster_index, j)?;
row.push(insert_vertex(tds, cluster_index, [x, y, 0.0])?);
}
grid.push(row);
}
let apex = insert_vertex(tds, cluster_index, [offset + 0.5, 0.5, 1.0])?;
for i in 0..N {
for j in 0..M {
let i1 = (i + 1) % N;
let j1 = (j + 1) % M;
let v00 = grid[i][j];
let v10 = grid[i1][j];
let v01 = grid[i][j1];
let v11 = grid[i1][j1];
for vertices in [[v00, v10, v01, apex], [v10, v11, v01, apex]] {
insert_checked_simplex(tds, cluster_index, vertices)?;
}
}
}
Ok(())
}
fn cluster_offset(
cluster_index: usize,
spacing: f64,
) -> Result<f64, PlManifoldRepairFixtureError> {
Ok(safe_usize_to_scalar(cluster_index).map_err(|source| {
PlManifoldRepairFixtureError::ClusterIndexConversion {
cluster_index,
source,
}
})? * spacing)
}
fn grid_coordinate(
cluster_index: usize,
grid_index: usize,
) -> Result<f64, PlManifoldRepairFixtureError> {
safe_usize_to_scalar(grid_index).map_err(|source| {
PlManifoldRepairFixtureError::GridIndexConversion {
cluster_index,
grid_index,
source,
}
})
}
fn insert_vertex<const D: usize>(
tds: &mut Tds<(), (), D>,
cluster_index: usize,
coords: [f64; D],
) -> Result<VertexKey, PlManifoldRepairFixtureError> {
let vertex =
Vertex::try_new(coords).map_err(|source| PlManifoldRepairFixtureError::Vertex {
cluster_index,
source,
})?;
tds.insert_vertex_with_mapping(vertex).map_err(|source| {
PlManifoldRepairFixtureError::VertexInsert {
cluster_index,
source: Box::new(source),
}
})
}
fn insert_checked_simplex<const D: usize, const N: usize>(
tds: &mut Tds<(), (), D>,
cluster_index: usize,
vertices: [VertexKey; N],
) -> Result<(), PlManifoldRepairFixtureError> {
let simplex = Simplex::try_new_with_data(
vertices.into_iter().collect::<SimplexVertexKeyBuffer>(),
None,
)
.map_err(|source| PlManifoldRepairFixtureError::Simplex {
cluster_index,
source,
})?;
tds.insert_simplex_with_mapping(simplex)
.map(|_| ())
.map_err(|source| PlManifoldRepairFixtureError::SimplexInsert {
cluster_index,
source: Box::new(source),
})
}
fn insert_prechecked_simplex<const D: usize, const N: usize>(
tds: &mut Tds<(), (), D>,
cluster_index: usize,
vertices: [VertexKey; N],
) -> Result<(), PlManifoldRepairFixtureError> {
let simplex = Simplex::try_new_with_data(
vertices.into_iter().collect::<SimplexVertexKeyBuffer>(),
None,
)
.map_err(|source| PlManifoldRepairFixtureError::Simplex {
cluster_index,
source,
})?;
tds.insert_simplex_with_mapping_prechecked_topology(simplex)
.map(|_| ())
.map_err(|source| PlManifoldRepairFixtureError::SimplexInsert {
cluster_index,
source: Box::new(source),
})
}
fn validate_structural_fixture_state<const D: usize>(
tds: &Tds<(), (), D>,
) -> Result<(), PlManifoldRepairFixtureError> {
tds.validate_vertex_mappings()
.map_err(|source| PlManifoldRepairFixtureError::StructuralValidation { source })?;
tds.validate_simplex_mappings()
.map_err(|source| PlManifoldRepairFixtureError::StructuralValidation { source })?;
tds.validate_simplex_vertex_keys()
.map_err(|source| PlManifoldRepairFixtureError::StructuralValidation { source })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overshared_fixture_repairs_expected_orphans() {
let fixture = validated_overshared_facet_orphan_cleanup_3d(3)
.expect("fixture should repair deterministically");
assert_eq!(fixture.cluster_count(), 3);
}
#[test]
fn targeted_fixtures_repair_expected_stages() {
let boundary = validated_boundary_ridge_multiplicity_repair_3d(2)
.expect("boundary-ridge fixture should repair deterministically");
let ridge = validated_ridge_link_repair_2d(2)
.expect("ridge-link fixture should repair deterministically");
let vertex = validated_vertex_link_repair_3d(2)
.expect("vertex-link fixture should repair deterministically");
assert_eq!(
boundary.stage(),
PlManifoldRepairStage::BoundaryRidgeMultiplicity
);
assert_eq!(ridge.stage(), PlManifoldRepairStage::RidgeLink);
assert_eq!(vertex.stage(), PlManifoldRepairStage::VertexLink);
}
}
}