#![forbid(unsafe_code)]
use crate::core::tds::Tds;
use crate::geometry::kernel::Kernel;
use crate::refinement::RefinementError;
use crate::topology::traits::topological_space::GlobalTopology;
use crate::triangulation::Triangulation;
use crate::triangulation::builder::{
TriangulationBuildFailure, TriangulationBuildMode, TriangulationBuildWithTopologyEvidence,
TriangulationBuilderError,
};
use crate::triangulation::model::UnverifiedTriangulation;
use crate::triangulation::realization::{
TriangulationCertificationError, TriangulationRealizationValidationError,
};
use crate::triangulation::validation::{
TopologyCertificationEvidence, TopologyConstructionProvenance, TopologyGuarantee,
ValidationPolicy,
};
#[derive(Clone, Debug)]
pub struct TriangulationDraft<K, U, V, const D: usize> {
triangulation: UnverifiedTriangulation<K, U, V, D>,
selected_validation_policy: Option<ValidationPolicy>,
}
impl<K, U, V, const D: usize> TriangulationDraft<K, U, V, D> {
#[must_use]
pub(crate) const fn with_topology_context(
tds: Tds<U, V, D>,
kernel: K,
topology_guarantee: TopologyGuarantee,
global_topology: GlobalTopology<D>,
) -> Self {
Self {
triangulation: UnverifiedTriangulation::with_topology_context(
tds,
kernel,
topology_guarantee,
global_topology,
TopologyConstructionProvenance::Unproven,
),
selected_validation_policy: None,
}
}
#[must_use]
pub(crate) const fn validation_policy(mut self, validation_policy: ValidationPolicy) -> Self {
self.selected_validation_policy = Some(validation_policy);
self
}
#[must_use]
pub(crate) const fn construction_provenance(
mut self,
provenance: TopologyConstructionProvenance,
) -> Self {
self.triangulation.storage.topology_construction_provenance = provenance;
self
}
}
impl<K, U, V, const D: usize> TriangulationDraft<K, U, V, D>
where
K: Kernel<D, Scalar = f64>,
U: Clone,
V: Clone,
{
pub(crate) fn finish_strict(
self,
) -> Result<Triangulation<K, U, V, D>, TriangulationBuildFailure<U, V, D>> {
self.finish(TriangulationBuildMode::Strict)
}
pub(crate) fn finish_canonicalizing_in_transaction(
mut self,
) -> Result<Triangulation<K, U, V, D>, TriangulationBuildFailure<U, V, D>> {
let validation_policy = self.selected_validation_policy.unwrap_or_else(|| {
self.triangulation
.topology_guarantee
.default_validation_policy()
});
if let Err(source) = self
.triangulation
.try_set_validation_policy(validation_policy)
{
return Err(RefinementError::new(
self.triangulation.into_tds(),
TriangulationBuilderError::ValidationConfiguration { source },
));
}
let publication = (|| {
self.triangulation
.normalize_and_promote_positive_orientation()
.map_err(
|source| TriangulationBuilderError::OrientationNormalization {
source: Box::new(source),
},
)?;
self.triangulation
.validate_geometric_nondegeneracy()
.map_err(|source| TriangulationBuilderError::GeometricNondegeneracy {
source: Box::new(source),
})?;
self.triangulation
.certify_levels_three_four()
.map_err(map_certification_error)?;
Ok(())
})();
if let Err(source) = publication {
return Err(RefinementError::new(self.triangulation.into_tds(), source));
}
Ok(self.triangulation.into_verified())
}
pub(super) fn finish(
self,
build_mode: TriangulationBuildMode,
) -> Result<Triangulation<K, U, V, D>, TriangulationBuildFailure<U, V, D>> {
self.finish_with_topology_evidence(build_mode)
.map(|(triangulation, _evidence)| triangulation)
}
pub(super) fn finish_with_topology_evidence(
mut self,
build_mode: TriangulationBuildMode,
) -> TriangulationBuildWithTopologyEvidence<K, U, V, D> {
let validation_policy = self.selected_validation_policy.unwrap_or_else(|| {
self.triangulation
.topology_guarantee
.default_validation_policy()
});
if let Err(source) = self
.triangulation
.try_set_validation_policy(validation_policy)
{
return Err(RefinementError::new(
self.triangulation.into_tds(),
TriangulationBuilderError::ValidationConfiguration { source },
));
}
if build_mode == TriangulationBuildMode::Strict {
let topology_evidence = match self
.triangulation
.certify_levels_three_four()
.map_err(map_certification_error)
{
Ok(evidence) => evidence,
Err(source) => {
return Err(RefinementError::new(self.triangulation.into_tds(), source));
}
};
return Ok((self.triangulation.into_verified(), topology_evidence));
}
let original_tds = self.triangulation.tds.clone_for_rollback();
let publication: Result<TopologyCertificationEvidence, TriangulationBuilderError> =
(|| {
self.triangulation
.normalize_and_promote_positive_orientation()
.map_err(
|source| TriangulationBuilderError::OrientationNormalization {
source: Box::new(source),
},
)?;
self.triangulation
.validate_geometric_nondegeneracy()
.map_err(|source| TriangulationBuilderError::GeometricNondegeneracy {
source: Box::new(source),
})?;
let topology_evidence = self
.triangulation
.certify_levels_three_four()
.map_err(map_certification_error)?;
Ok(topology_evidence)
})();
let topology_evidence =
publication.map_err(|source| RefinementError::new(original_tds, source))?;
Ok((self.triangulation.into_verified(), topology_evidence))
}
}
fn map_certification_error(source: TriangulationCertificationError) -> TriangulationBuilderError {
match source {
TriangulationCertificationError::IncompleteConstruction { vertex_count } => {
TriangulationBuilderError::RealizationValidation {
source: Box::new(
TriangulationRealizationValidationError::IncompleteConstruction {
vertex_count,
},
),
}
}
TriangulationCertificationError::Topology { source } => {
TriangulationBuilderError::TopologyValidation {
source: Box::new(source),
}
}
TriangulationCertificationError::Realization { source } => {
TriangulationBuilderError::RealizationValidation {
source: Box::new(source),
}
}
}
}
#[cfg(test)]
mod test_support {
use super::*;
impl<K, U, V, const D: usize> TriangulationDraft<K, U, V, D> {
#[must_use]
pub(in crate::triangulation) fn into_unpublished_triangulation(
self,
) -> UnverifiedTriangulation<K, U, V, D> {
self.triangulation
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::tds::TdsBuilder;
use crate::geometry::kernel::AdaptiveKernel;
use crate::topology::traits::topological_space::TopologyKind;
use crate::triangulation::validation::TriangulationValidationError;
use crate::validation_report::InvariantError;
use crate::vertex;
use std::assert_matches;
#[test]
fn explicit_connectivity_publishes_without_selecting_delaunay_connectivity() {
let vertices = [
vertex![0.0, 0.0].unwrap(),
vertex![1.0, 0.0].unwrap(),
vertex![0.0, 1.0].unwrap(),
];
let simplices = [vec![0, 1, 2]];
let tds = TdsBuilder::new(&vertices, &simplices).build().unwrap();
let draft = TriangulationDraft::with_topology_context(
tds,
AdaptiveKernel::new(),
TopologyGuarantee::DEFAULT,
GlobalTopology::DEFAULT,
);
let triangulation = draft
.finish(TriangulationBuildMode::Canonicalizing)
.unwrap();
assert!(triangulation.validate_realization().is_ok());
}
#[test]
fn empty_draft_publishes_as_the_verified_empty_triangulation() {
let draft: TriangulationDraft<AdaptiveKernel<f64>, (), (), 2> =
TriangulationDraft::with_topology_context(
Tds::empty(),
AdaptiveKernel::new(),
TopologyGuarantee::DEFAULT,
GlobalTopology::DEFAULT,
);
let triangulation = draft
.finish(TriangulationBuildMode::Canonicalizing)
.unwrap();
assert_eq!(triangulation.number_of_vertices(), 0);
assert!(triangulation.validate_realization().is_ok());
}
#[test]
fn geometrically_degenerate_connectivity_cannot_publish() {
let vertices = [
vertex![0.0, 0.0].unwrap(),
vertex![1.0, 0.0].unwrap(),
vertex![2.0, 0.0].unwrap(),
];
let simplices = [vec![0, 1, 2]];
let tds = TdsBuilder::new(&vertices, &simplices).build().unwrap();
let draft = TriangulationDraft::with_topology_context(
tds,
AdaptiveKernel::new(),
TopologyGuarantee::DEFAULT,
GlobalTopology::DEFAULT,
);
assert!(
draft
.finish(TriangulationBuildMode::Canonicalizing)
.is_err()
);
}
#[test]
fn transactional_publication_rejects_degeneracy_and_retains_the_journal() {
let vertices = [
vertex![0.0, 0.0].unwrap(),
vertex![1.0, 0.0].unwrap(),
vertex![2.0, 0.0].unwrap(),
];
let mut tds = TdsBuilder::new(&vertices, &[vec![0, 1, 2]])
.build()
.unwrap();
let expected_snapshot = serde_json::to_value(&tds).unwrap();
let expected_owner = tds.topology_owner_id();
let expected_generation = tds.generation();
let savepoint = tds.begin_rollback_savepoint();
let failure = TriangulationDraft::with_topology_context(
tds,
AdaptiveKernel::new(),
TopologyGuarantee::DEFAULT,
GlobalTopology::DEFAULT,
)
.finish_canonicalizing_in_transaction()
.expect_err("a zero-area simplex must fail geometric publication");
assert_matches!(
failure.reason(),
TriangulationBuilderError::GeometricNondegeneracy { source }
if matches!(source.as_ref(), TriangulationRealizationValidationError::DegenerateSimplex { dimension: 2, .. })
);
let mut recovered = failure.into_owner();
recovered.rollback_savepoint(savepoint);
assert_eq!(recovered.topology_owner_id(), expected_owner);
assert_eq!(recovered.generation(), expected_generation);
assert_eq!(serde_json::to_value(&recovered).unwrap(), expected_snapshot);
recovered.validate().unwrap();
}
#[test]
fn transactional_publication_rolls_back_late_failure_and_can_retry() {
let vertices = [
vertex![0.0, 0.0].unwrap(),
vertex![1.0, 0.0].unwrap(),
vertex![0.0, 1.0].unwrap(),
];
let mut tds = TdsBuilder::new(&vertices, &[vec![0, 2, 1]])
.build()
.unwrap();
let expected_snapshot = serde_json::to_value(&tds).unwrap();
let expected_owner = tds.topology_owner_id();
let expected_generation = tds.generation();
let expected_vertex_keys: Vec<_> = tds.vertex_keys().collect();
let (simplex_key, simplex) = tds.simplices().next().unwrap();
let simplex_uuid = simplex.uuid();
let savepoint = tds.begin_rollback_savepoint();
let failure = TriangulationDraft::with_topology_context(
tds,
AdaptiveKernel::new(),
TopologyGuarantee::DEFAULT,
GlobalTopology::Spherical,
)
.finish_canonicalizing_in_transaction()
.expect_err("an open triangle cannot publish as a closed sphere");
assert_matches!(
failure.reason(),
TriangulationBuilderError::TopologyValidation { source }
if matches!(source.as_ref(), InvariantError::Triangulation {
source: TriangulationValidationError::BoundaryFacetInClosedTopology {
topology: TopologyKind::Spherical,
simplex_key: key,
simplex_uuid: uuid,
..
},
} if *key == simplex_key && *uuid == simplex_uuid)
);
let mut recovered = failure.into_owner();
assert_ne!(recovered.generation(), expected_generation);
assert_ne!(serde_json::to_value(&recovered).unwrap(), expected_snapshot);
recovered.rollback_savepoint(savepoint);
assert_eq!(recovered.topology_owner_id(), expected_owner);
assert_eq!(recovered.generation(), expected_generation);
assert_eq!(
recovered.vertex_keys().collect::<Vec<_>>(),
expected_vertex_keys
);
assert_eq!(recovered.simplex_keys().collect::<Vec<_>>(), [simplex_key]);
assert_eq!(serde_json::to_value(&recovered).unwrap(), expected_snapshot);
recovered.validate().unwrap();
let retry_savepoint = recovered.begin_rollback_savepoint();
let triangulation = TriangulationDraft::with_topology_context(
recovered,
AdaptiveKernel::new(),
TopologyGuarantee::DEFAULT,
GlobalTopology::Euclidean,
)
.finish_canonicalizing_in_transaction()
.expect("the recovered triangle should publish with Euclidean topology");
assert_eq!(
triangulation.validation_policy(),
TopologyGuarantee::DEFAULT.default_validation_policy()
);
triangulation.validate_realization().unwrap();
let mut published = triangulation.into_tds();
published.commit_savepoint(retry_savepoint);
assert_eq!(published.topology_owner_id(), expected_owner);
assert_eq!(
published.vertex_keys().collect::<Vec<_>>(),
expected_vertex_keys
);
assert_eq!(published.simplex_keys().collect::<Vec<_>>(), [simplex_key]);
assert_eq!(published.simplex(simplex_key).unwrap().uuid(), simplex_uuid);
}
}