#![forbid(unsafe_code)]
use crate::core::collections::NeighborBuffer;
use crate::core::simplex::NeighborSlot;
use crate::core::tds::{SimplexKey, Tds, VertexKey};
use crate::core::validation::TopologyGuarantee;
use crate::core::vertex::Vertex;
use crate::geometry::traits::coordinate::InvalidCoordinateValue;
use crate::topology::traits::topological_space::TopologyKind;
use crate::triangulation::DelaunayTriangulation;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{
collections::{HashMap, HashSet},
fmt,
};
use thiserror::Error;
use uuid::Uuid;
const fn no_attributes<Attributes>() -> Option<Attributes> {
None
}
pub const VISUALIZATION_SCHEMA: &str = "delaunay.simplicial_complex";
pub const VISUALIZATION_SCHEMA_VERSION: u32 = 1;
pub const MESH_EXPORT_SCHEMA: &str = VISUALIZATION_SCHEMA;
pub const MESH_EXPORT_SCHEMA_VERSION: u32 = VISUALIZATION_SCHEMA_VERSION;
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum VisualizationTopologyKind {
Euclidean,
Toroidal,
Spherical,
Hyperbolic,
Unknown {
actual: String,
},
}
impl VisualizationTopologyKind {
fn schema_name(&self) -> &str {
match self {
Self::Euclidean => "Euclidean",
Self::Toroidal => "Toroidal",
Self::Spherical => "Spherical",
Self::Hyperbolic => "Hyperbolic",
Self::Unknown { actual } => actual,
}
}
const fn is_supported(&self) -> bool {
match self {
Self::Euclidean | Self::Toroidal | Self::Spherical | Self::Hyperbolic => true,
Self::Unknown { .. } => false,
}
}
}
impl From<TopologyKind> for VisualizationTopologyKind {
fn from(kind: TopologyKind) -> Self {
match kind {
TopologyKind::Euclidean => Self::Euclidean,
TopologyKind::Toroidal => Self::Toroidal,
TopologyKind::Spherical => Self::Spherical,
TopologyKind::Hyperbolic => Self::Hyperbolic,
}
}
}
impl fmt::Display for VisualizationTopologyKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.schema_name())
}
}
impl Serialize for VisualizationTopologyKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.schema_name())
}
}
impl<'de> Deserialize<'de> for VisualizationTopologyKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let actual = String::deserialize(deserializer)?;
Ok(match actual.as_str() {
"Euclidean" => Self::Euclidean,
"Toroidal" => Self::Toroidal,
"Spherical" => Self::Spherical,
"Hyperbolic" => Self::Hyperbolic,
_ => Self::Unknown { actual },
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum VisualizationTopologyGuarantee {
Pseudomanifold,
PLManifold,
PLManifoldStrict,
Unknown {
actual: String,
},
}
impl VisualizationTopologyGuarantee {
fn schema_name(&self) -> &str {
match self {
Self::Pseudomanifold => "Pseudomanifold",
Self::PLManifold => "PLManifold",
Self::PLManifoldStrict => "PLManifoldStrict",
Self::Unknown { actual } => actual,
}
}
const fn is_supported(&self) -> bool {
match self {
Self::Pseudomanifold | Self::PLManifold | Self::PLManifoldStrict => true,
Self::Unknown { .. } => false,
}
}
}
impl From<TopologyGuarantee> for VisualizationTopologyGuarantee {
fn from(guarantee: TopologyGuarantee) -> Self {
match guarantee {
TopologyGuarantee::Pseudomanifold => Self::Pseudomanifold,
TopologyGuarantee::PLManifold => Self::PLManifold,
TopologyGuarantee::PLManifoldStrict => Self::PLManifoldStrict,
}
}
}
impl fmt::Display for VisualizationTopologyGuarantee {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.schema_name())
}
}
impl Serialize for VisualizationTopologyGuarantee {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.schema_name())
}
}
impl<'de> Deserialize<'de> for VisualizationTopologyGuarantee {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let actual = String::deserialize(deserializer)?;
Ok(match actual.as_str() {
"Pseudomanifold" => Self::Pseudomanifold,
"PLManifold" => Self::PLManifold,
"PLManifoldStrict" => Self::PLManifoldStrict,
_ => Self::Unknown { actual },
})
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct VisualizationData<
const D: usize,
VertexAttributes = (),
SimplexAttributes = (),
AdjacencyAttributes = (),
GlobalAttributes = (),
> {
pub metadata: VisualizationMetadata<GlobalAttributes>,
pub vertices: Vec<VertexRecord<D, VertexAttributes>>,
pub simplices: Vec<SimplexRecord<SimplexAttributes>>,
pub adjacency: Vec<AdjacencyRecord<AdjacencyAttributes>>,
}
impl<const D: usize, VertexAttributes, SimplexAttributes, AdjacencyAttributes, GlobalAttributes>
VisualizationData<D, VertexAttributes, SimplexAttributes, AdjacencyAttributes, GlobalAttributes>
{
pub fn into_validated(
self,
) -> Result<
ValidatedVisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>,
VisualizationDataValidationError,
> {
ValidatedVisualizationData::try_from_raw(self)
}
pub fn validate(&self) -> Result<(), VisualizationDataValidationError> {
validate_metadata::<D, GlobalAttributes>(
&self.metadata,
self.vertices.len(),
self.simplices.len(),
)?;
let mut vertex_ids = HashSet::with_capacity(self.vertices.len());
for (vertex_index, vertex) in self.vertices.iter().enumerate() {
if vertex.id.is_nil() {
return Err(VisualizationDataValidationError::NilVertexId { vertex_index });
}
if vertex.coordinates.len() != D {
return Err(
VisualizationDataValidationError::InvalidVertexCoordinateCount {
vertex_id: vertex.id,
expected: D,
actual: vertex.coordinates.len(),
},
);
}
for (coordinate_index, coordinate) in vertex.coordinates.iter().enumerate() {
if !coordinate.is_finite() {
return Err(
VisualizationDataValidationError::InvalidVertexCoordinateValue {
vertex_id: vertex.id,
coordinate_index,
value: InvalidCoordinateValue::from_debug(coordinate),
},
);
}
}
if !vertex_ids.insert(vertex.id) {
return Err(VisualizationDataValidationError::DuplicateVertexId {
vertex_id: vertex.id,
});
}
}
let expected_simplex_vertices = D + 1;
let mut simplex_ids = HashSet::with_capacity(self.simplices.len());
for (simplex_index, simplex) in self.simplices.iter().enumerate() {
if simplex.id.is_nil() {
return Err(VisualizationDataValidationError::NilSimplexId { simplex_index });
}
if !simplex_ids.insert(simplex.id) {
return Err(VisualizationDataValidationError::DuplicateSimplexId {
simplex_id: simplex.id,
});
}
if simplex.vertex_ids.len() != expected_simplex_vertices {
return Err(
VisualizationDataValidationError::InvalidSimplexVertexCount {
simplex_id: simplex.id,
expected: expected_simplex_vertices,
actual: simplex.vertex_ids.len(),
},
);
}
let mut local_vertex_ids = HashSet::with_capacity(simplex.vertex_ids.len());
for vertex_id in &simplex.vertex_ids {
if !vertex_ids.contains(vertex_id) {
return Err(VisualizationDataValidationError::MissingSimplexVertex {
simplex_id: simplex.id,
vertex_id: *vertex_id,
});
}
if !local_vertex_ids.insert(*vertex_id) {
return Err(VisualizationDataValidationError::DuplicateSimplexVertex {
simplex_id: simplex.id,
vertex_id: *vertex_id,
});
}
}
}
validate_adjacency::<D, _, _>(&self.adjacency, &self.simplices)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(transparent)]
pub struct ValidatedVisualizationData<
const D: usize,
VertexAttributes = (),
SimplexAttributes = (),
AdjacencyAttributes = (),
GlobalAttributes = (),
> {
inner: VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>,
}
impl<const D: usize, VertexAttributes, SimplexAttributes, AdjacencyAttributes, GlobalAttributes>
ValidatedVisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>
{
pub fn try_from_raw(
mut raw: VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>,
) -> Result<Self, VisualizationDataValidationError> {
raw.validate()?;
canonicalize_record_order(&mut raw);
Ok(Self { inner: raw })
}
pub const fn as_raw(
&self,
) -> &VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
> {
&self.inner
}
pub fn into_raw(
self,
) -> VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
> {
self.inner
}
pub const fn metadata(&self) -> &VisualizationMetadata<GlobalAttributes> {
&self.inner.metadata
}
pub fn vertices(&self) -> &[VertexRecord<D, VertexAttributes>] {
&self.inner.vertices
}
pub fn simplices(&self) -> &[SimplexRecord<SimplexAttributes>] {
&self.inner.simplices
}
pub fn adjacency(&self) -> &[AdjacencyRecord<AdjacencyAttributes>] {
&self.inner.adjacency
}
}
impl<const D: usize, VertexAttributes, SimplexAttributes, AdjacencyAttributes, GlobalAttributes>
TryFrom<
VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>,
>
for ValidatedVisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>
{
type Error = VisualizationDataValidationError;
fn try_from(
raw: VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>,
) -> Result<Self, Self::Error> {
Self::try_from_raw(raw)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct VisualizationMetadata<Attributes = ()> {
pub schema: String,
pub schema_version: u32,
pub producer: String,
pub dimension: usize,
pub vertex_count: usize,
pub simplex_count: usize,
pub topology_kind: VisualizationTopologyKind,
pub topology_guarantee: VisualizationTopologyGuarantee,
#[serde(default = "no_attributes", skip_serializing_if = "Option::is_none")]
pub attributes: Option<Attributes>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct VertexRecord<const D: usize, Attributes = ()> {
pub id: Uuid,
pub coordinates: Vec<f64>,
#[serde(default = "no_attributes", skip_serializing_if = "Option::is_none")]
pub attributes: Option<Attributes>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SimplexRecord<Attributes = ()> {
pub id: Uuid,
pub vertex_ids: Vec<Uuid>,
#[serde(default = "no_attributes", skip_serializing_if = "Option::is_none")]
pub attributes: Option<Attributes>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AdjacencyRecord<Attributes = ()> {
pub simplex_id: Uuid,
pub facet_index: usize,
pub neighbor_simplex_id: Option<Uuid>,
#[serde(default = "no_attributes", skip_serializing_if = "Option::is_none")]
pub attributes: Option<Attributes>,
}
pub type MeshExport<const D: usize> = VisualizationData<D>;
pub type ValidatedMeshExport<const D: usize> = ValidatedVisualizationData<D>;
pub type MeshVertexRecord<const D: usize> = VertexRecord<D>;
pub type MeshSimplexRecord = SimplexRecord;
pub type MeshAdjacencyRecord = AdjacencyRecord;
pub type MeshExportError = VisualizationExportError;
pub type MeshExportValidationError = VisualizationDataValidationError;
#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum VisualizationExportError {
#[error("simplex {simplex_id} references missing vertex key {vertex_key:?}")]
MissingVertex {
simplex_id: Uuid,
vertex_key: VertexKey,
},
#[error("simplex {simplex_id} has no assigned neighbor buffer")]
UnassignedNeighborBuffer {
simplex_id: Uuid,
},
#[error("simplex {simplex_id} has {actual} neighbor slots; expected {expected}")]
InvalidNeighborCount {
simplex_id: Uuid,
expected: usize,
actual: usize,
},
#[error("simplex {simplex_id} has an unassigned neighbor slot at facet {facet_index}")]
UnassignedNeighborSlot {
simplex_id: Uuid,
facet_index: usize,
},
#[error(
"simplex {simplex_id} facet {facet_index} references missing neighbor key {neighbor_key:?}"
)]
MissingNeighbor {
simplex_id: Uuid,
facet_index: usize,
neighbor_key: SimplexKey,
},
}
#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum VisualizationDataValidationError {
#[error("unsupported visualization schema {actual:?}; expected {expected:?}")]
InvalidSchema {
expected: &'static str,
actual: String,
},
#[error("unsupported visualization schema version {actual}; expected {expected}")]
InvalidSchemaVersion {
expected: u32,
actual: u32,
},
#[error("metadata dimension {actual} does not match export dimension {expected}")]
DimensionMismatch {
expected: usize,
actual: usize,
},
#[error("metadata vertex_count {expected} does not match {actual} vertex records")]
VertexCountMismatch {
expected: usize,
actual: usize,
},
#[error("metadata simplex_count {expected} does not match {actual} simplex records")]
SimplexCountMismatch {
expected: usize,
actual: usize,
},
#[error(
"unsupported topology kind {actual}; expected one of Euclidean, Toroidal, Spherical, Hyperbolic"
)]
InvalidTopologyKind {
actual: VisualizationTopologyKind,
},
#[error(
"unsupported topology guarantee {actual}; expected one of Pseudomanifold, PLManifold, PLManifoldStrict"
)]
InvalidTopologyGuarantee {
actual: VisualizationTopologyGuarantee,
},
#[error("vertex record {vertex_index} uses nil UUID")]
NilVertexId {
vertex_index: usize,
},
#[error("vertex {vertex_id} has {actual} coordinates; expected {expected}")]
InvalidVertexCoordinateCount {
vertex_id: Uuid,
expected: usize,
actual: usize,
},
#[error("vertex {vertex_id} coordinate {coordinate_index} is non-finite: {value}")]
InvalidVertexCoordinateValue {
vertex_id: Uuid,
coordinate_index: usize,
value: InvalidCoordinateValue,
},
#[error("duplicate vertex id {vertex_id}")]
DuplicateVertexId {
vertex_id: Uuid,
},
#[error("simplex record {simplex_index} uses nil UUID")]
NilSimplexId {
simplex_index: usize,
},
#[error("simplex {simplex_id} has {actual} vertex ids; expected {expected}")]
InvalidSimplexVertexCount {
simplex_id: Uuid,
expected: usize,
actual: usize,
},
#[error("duplicate simplex id {simplex_id}")]
DuplicateSimplexId {
simplex_id: Uuid,
},
#[error("simplex {simplex_id} references missing vertex id {vertex_id}")]
MissingSimplexVertex {
simplex_id: Uuid,
vertex_id: Uuid,
},
#[error("simplex {simplex_id} repeats vertex id {vertex_id}")]
DuplicateSimplexVertex {
simplex_id: Uuid,
vertex_id: Uuid,
},
#[error("adjacency references missing source simplex id {simplex_id}")]
MissingAdjacencySimplex {
simplex_id: Uuid,
},
#[error(
"adjacency for simplex {simplex_id} has facet index {facet_index}; expected less than {max_exclusive}"
)]
InvalidAdjacencyFacetIndex {
simplex_id: Uuid,
facet_index: usize,
max_exclusive: usize,
},
#[error(
"adjacency for simplex {simplex_id} facet {facet_index} references missing neighbor simplex id {neighbor_simplex_id}"
)]
MissingAdjacencyNeighbor {
simplex_id: Uuid,
facet_index: usize,
neighbor_simplex_id: Uuid,
},
#[error(
"adjacency for simplex {simplex_id} facet {facet_index} references neighbor {neighbor_simplex_id}, but source facet vertex {missing_vertex_id} is absent from the neighbor"
)]
InvalidAdjacencyFacetSharing {
simplex_id: Uuid,
facet_index: usize,
neighbor_simplex_id: Uuid,
missing_vertex_id: Uuid,
},
#[error("duplicate adjacency record for simplex {simplex_id} facet {facet_index}")]
DuplicateAdjacency {
simplex_id: Uuid,
facet_index: usize,
},
#[error("missing adjacency record for simplex {simplex_id} facet {facet_index}")]
MissingAdjacency {
simplex_id: Uuid,
facet_index: usize,
},
#[error(
"adjacency for simplex {simplex_id} facet {facet_index} references neighbor {neighbor_simplex_id}, but the neighbor does not reference it back"
)]
AsymmetricAdjacency {
simplex_id: Uuid,
facet_index: usize,
neighbor_simplex_id: Uuid,
},
}
impl<K, U, V, const D: usize> DelaunayTriangulation<K, U, V, D> {
pub fn to_visualization_data(&self) -> Result<VisualizationData<D>, VisualizationExportError> {
let tds = self.tds();
let mut vertices: Vec<_> = tds
.vertices()
.map(|(_, vertex)| VertexRecord {
id: vertex.uuid(),
coordinates: vertex.point().coords().to_vec(),
attributes: None,
})
.collect();
vertices.sort_by_key(|record| record.id);
let mut simplices = Vec::with_capacity(tds.number_of_simplices());
let mut adjacency = Vec::with_capacity(tds.number_of_simplices().saturating_mul(D + 1));
for (_, simplex) in tds.simplices() {
let simplex_id = simplex.uuid();
let vertex_ids = simplex
.vertices()
.iter()
.copied()
.map(|vertex_key| {
tds.vertex(vertex_key).map(Vertex::uuid).ok_or(
VisualizationExportError::MissingVertex {
simplex_id,
vertex_key,
},
)
})
.collect::<Result<Vec<_>, _>>()?;
simplices.push(SimplexRecord {
id: simplex_id,
vertex_ids,
attributes: None,
});
push_adjacency_records(
tds,
simplex_id,
simplex.neighbor_slots().map(NeighborBuffer::as_slice),
&mut adjacency,
)?;
}
simplices.sort_by_key(|record| record.id);
adjacency.sort_by_key(|record| (record.simplex_id, record.facet_index));
Ok(VisualizationData {
metadata: VisualizationMetadata {
schema: VISUALIZATION_SCHEMA.to_owned(),
schema_version: VISUALIZATION_SCHEMA_VERSION,
producer: env!("CARGO_PKG_NAME").to_owned(),
dimension: D,
vertex_count: vertices.len(),
simplex_count: simplices.len(),
topology_kind: VisualizationTopologyKind::from(self.topology_kind()),
topology_guarantee: VisualizationTopologyGuarantee::from(self.topology_guarantee()),
attributes: None,
},
vertices,
simplices,
adjacency,
})
}
pub fn to_mesh_export(&self) -> Result<MeshExport<D>, MeshExportError> {
self.to_visualization_data()
}
}
fn canonicalize_record_order<
const D: usize,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>(
data: &mut VisualizationData<
D,
VertexAttributes,
SimplexAttributes,
AdjacencyAttributes,
GlobalAttributes,
>,
) {
data.vertices.sort_by_key(|record| record.id);
data.simplices.sort_by_key(|record| record.id);
data.adjacency
.sort_by_key(|record| (record.simplex_id, record.facet_index));
}
fn validate_metadata<const D: usize, Attributes>(
metadata: &VisualizationMetadata<Attributes>,
vertex_count: usize,
simplex_count: usize,
) -> Result<(), VisualizationDataValidationError> {
if metadata.schema != VISUALIZATION_SCHEMA {
return Err(VisualizationDataValidationError::InvalidSchema {
expected: VISUALIZATION_SCHEMA,
actual: metadata.schema.clone(),
});
}
if metadata.schema_version != VISUALIZATION_SCHEMA_VERSION {
return Err(VisualizationDataValidationError::InvalidSchemaVersion {
expected: VISUALIZATION_SCHEMA_VERSION,
actual: metadata.schema_version,
});
}
if metadata.dimension != D {
return Err(VisualizationDataValidationError::DimensionMismatch {
expected: D,
actual: metadata.dimension,
});
}
if metadata.vertex_count != vertex_count {
return Err(VisualizationDataValidationError::VertexCountMismatch {
expected: metadata.vertex_count,
actual: vertex_count,
});
}
if metadata.simplex_count != simplex_count {
return Err(VisualizationDataValidationError::SimplexCountMismatch {
expected: metadata.simplex_count,
actual: simplex_count,
});
}
if !metadata.topology_kind.is_supported() {
return Err(VisualizationDataValidationError::InvalidTopologyKind {
actual: metadata.topology_kind.clone(),
});
}
if !metadata.topology_guarantee.is_supported() {
return Err(VisualizationDataValidationError::InvalidTopologyGuarantee {
actual: metadata.topology_guarantee.clone(),
});
}
Ok(())
}
fn validate_adjacency<const D: usize, SimplexAttributes, AdjacencyAttributes>(
adjacency: &[AdjacencyRecord<AdjacencyAttributes>],
simplices: &[SimplexRecord<SimplexAttributes>],
) -> Result<(), VisualizationDataValidationError> {
let simplex_by_id: HashMap<_, _> = simplices
.iter()
.map(|simplex| (simplex.id, simplex))
.collect();
let max_exclusive = D + 1;
let mut adjacency_slots = HashSet::with_capacity(adjacency.len());
let mut neighbor_edge_counts = HashMap::with_capacity(adjacency.len());
for record in adjacency {
let Some(source_simplex) = simplex_by_id.get(&record.simplex_id) else {
return Err(VisualizationDataValidationError::MissingAdjacencySimplex {
simplex_id: record.simplex_id,
});
};
if record.facet_index >= max_exclusive {
return Err(
VisualizationDataValidationError::InvalidAdjacencyFacetIndex {
simplex_id: record.simplex_id,
facet_index: record.facet_index,
max_exclusive,
},
);
}
if let Some(neighbor_simplex_id) = record.neighbor_simplex_id {
let Some(neighbor_simplex) = simplex_by_id.get(&neighbor_simplex_id) else {
return Err(VisualizationDataValidationError::MissingAdjacencyNeighbor {
simplex_id: record.simplex_id,
facet_index: record.facet_index,
neighbor_simplex_id,
});
};
if let Some(missing_vertex_id) =
missing_source_facet_vertex(source_simplex, neighbor_simplex, record.facet_index)
{
return Err(
VisualizationDataValidationError::InvalidAdjacencyFacetSharing {
simplex_id: record.simplex_id,
facet_index: record.facet_index,
neighbor_simplex_id,
missing_vertex_id,
},
);
}
*neighbor_edge_counts
.entry((record.simplex_id, neighbor_simplex_id))
.or_insert(0) += 1;
}
if !adjacency_slots.insert((record.simplex_id, record.facet_index)) {
return Err(VisualizationDataValidationError::DuplicateAdjacency {
simplex_id: record.simplex_id,
facet_index: record.facet_index,
});
}
}
for simplex in simplices {
for facet_index in 0..max_exclusive {
if !adjacency_slots.contains(&(simplex.id, facet_index)) {
return Err(VisualizationDataValidationError::MissingAdjacency {
simplex_id: simplex.id,
facet_index,
});
}
}
}
for record in adjacency {
if let Some(neighbor_simplex_id) = record.neighbor_simplex_id
&& !has_reciprocal_adjacency(
&neighbor_edge_counts,
record.simplex_id,
neighbor_simplex_id,
)
{
return Err(VisualizationDataValidationError::AsymmetricAdjacency {
simplex_id: record.simplex_id,
facet_index: record.facet_index,
neighbor_simplex_id,
});
}
}
Ok(())
}
fn has_reciprocal_adjacency(
neighbor_edge_counts: &HashMap<(Uuid, Uuid), usize>,
simplex_id: Uuid,
neighbor_simplex_id: Uuid,
) -> bool {
let reciprocal_count = neighbor_edge_counts
.get(&(neighbor_simplex_id, simplex_id))
.copied()
.unwrap_or(0);
if neighbor_simplex_id == simplex_id {
reciprocal_count > 1
} else {
reciprocal_count > 0
}
}
fn missing_source_facet_vertex<SimplexAttributes>(
source_simplex: &SimplexRecord<SimplexAttributes>,
neighbor_simplex: &SimplexRecord<SimplexAttributes>,
facet_index: usize,
) -> Option<Uuid> {
source_simplex
.vertex_ids
.iter()
.enumerate()
.filter(|(vertex_index, _)| *vertex_index != facet_index)
.map(|(_, vertex_id)| *vertex_id)
.find(|vertex_id| !neighbor_simplex.vertex_ids.contains(vertex_id))
}
fn push_adjacency_records<U, V, const D: usize>(
tds: &Tds<U, V, D>,
simplex_id: Uuid,
neighbor_slots: Option<&[NeighborSlot]>,
adjacency: &mut Vec<AdjacencyRecord>,
) -> Result<(), VisualizationExportError> {
let slots =
neighbor_slots.ok_or(VisualizationExportError::UnassignedNeighborBuffer { simplex_id })?;
if slots.len() != D + 1 {
return Err(VisualizationExportError::InvalidNeighborCount {
simplex_id,
expected: D + 1,
actual: slots.len(),
});
}
for (facet_index, slot) in slots.iter().copied().enumerate() {
let record = match slot {
NeighborSlot::Boundary => AdjacencyRecord {
simplex_id,
facet_index,
neighbor_simplex_id: None,
attributes: None,
},
NeighborSlot::Neighbor(neighbor_key) => tds
.simplex(neighbor_key)
.map(|neighbor| AdjacencyRecord {
simplex_id,
facet_index,
neighbor_simplex_id: Some(neighbor.uuid()),
attributes: None,
})
.ok_or(VisualizationExportError::MissingNeighbor {
simplex_id,
facet_index,
neighbor_key,
})?,
NeighborSlot::Unassigned => {
return Err(VisualizationExportError::UnassignedNeighborSlot {
simplex_id,
facet_index,
});
}
};
adjacency.push(record);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use slotmap::KeyData;
#[test]
fn push_adjacency_records_rejects_wrong_neighbor_arity() {
let tds: Tds<(), (), 2> = Tds::empty();
let simplex_id = Uuid::from_u128(0x3000_0000_0000_0000_0000_0000_0000_0001);
let mut adjacency = Vec::new();
let error = push_adjacency_records(
&tds,
simplex_id,
Some(&[NeighborSlot::Boundary]),
&mut adjacency,
)
.expect_err("neighbor buffers must have one slot per simplex facet");
assert_eq!(
error,
VisualizationExportError::InvalidNeighborCount {
simplex_id,
expected: 3,
actual: 1,
}
);
assert!(adjacency.is_empty());
}
#[test]
fn push_adjacency_records_rejects_unassigned_neighbor_slot() {
let tds: Tds<(), (), 2> = Tds::empty();
let simplex_id = Uuid::from_u128(0x3000_0000_0000_0000_0000_0000_0000_0002);
let mut adjacency = Vec::new();
let error = push_adjacency_records(
&tds,
simplex_id,
Some(&[
NeighborSlot::Unassigned,
NeighborSlot::Boundary,
NeighborSlot::Boundary,
]),
&mut adjacency,
)
.expect_err("export must reject explicit unassigned neighbor slots");
assert_eq!(
error,
VisualizationExportError::UnassignedNeighborSlot {
simplex_id,
facet_index: 0,
}
);
assert!(adjacency.is_empty());
}
#[test]
fn push_adjacency_records_rejects_dangling_neighbor_key() {
let tds: Tds<(), (), 2> = Tds::empty();
let simplex_id = Uuid::from_u128(0x3000_0000_0000_0000_0000_0000_0000_0003);
let neighbor_key = SimplexKey::from(KeyData::from_ffi(1));
let mut adjacency = Vec::new();
let error = push_adjacency_records(
&tds,
simplex_id,
Some(&[
NeighborSlot::Neighbor(neighbor_key),
NeighborSlot::Boundary,
NeighborSlot::Boundary,
]),
&mut adjacency,
)
.expect_err("export must reject neighbor keys absent from the TDS");
assert_eq!(
error,
VisualizationExportError::MissingNeighbor {
simplex_id,
facet_index: 0,
neighbor_key,
}
);
assert!(adjacency.is_empty());
}
}