use std::collections::{BTreeMap, BTreeSet};
use super::{Error, Result};
#[derive(Debug, Clone, PartialEq)]
pub struct Point {
pub id: u64,
pub position: [f64; 3],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CellKind {
Line2,
Triangle3,
Quad4,
Tet4,
Hex8,
Wedge6,
Pyramid5,
}
impl CellKind {
#[must_use]
pub fn node_count(self) -> usize {
match self {
Self::Line2 => 2,
Self::Triangle3 => 3,
Self::Quad4 | Self::Tet4 => 4,
Self::Pyramid5 => 5,
Self::Wedge6 => 6,
Self::Hex8 => 8,
}
}
#[must_use]
pub fn dimension(self) -> u8 {
match self {
Self::Line2 => 1,
Self::Triangle3 | Self::Quad4 => 2,
Self::Tet4 | Self::Hex8 | Self::Wedge6 | Self::Pyramid5 => 3,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub id: u64,
pub kind: CellKind,
pub connectivity: Vec<usize>,
pub property_id: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeSet {
pub name: String,
pub point_ids: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CellSet {
pub name: String,
pub dimension: u8,
pub cell_ids: Vec<u64>,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Mesh {
pub points: Vec<Point>,
pub cells: Vec<Cell>,
pub node_sets: Vec<NodeSet>,
pub cell_sets: Vec<CellSet>,
}
impl Mesh {
pub(crate) fn require_no_sets(&self, code: &'static str) -> Result<()> {
if !self.node_sets.is_empty() || !self.cell_sets.is_empty() {
return Err(Error::new(
code,
"named node/cell sets have no mapping in this writer",
));
}
Ok(())
}
pub fn validate(&self) -> Result<()> {
let mut points = BTreeSet::new();
for point in &self.points {
if point.id == 0 || !points.insert(point.id) {
return Err(Error::new(
"E_POINT_ID",
format!("invalid/duplicate point ID {}", point.id),
));
}
if !point.position.iter().all(|x| x.is_finite()) {
return Err(Error::new(
"E_NONFINITE",
format!("point {} is not finite", point.id),
));
}
}
let mut cells = BTreeMap::new();
for cell in &self.cells {
if cell.id == 0 || cells.insert(cell.id, cell.kind.dimension()).is_some() {
return Err(Error::new(
"E_CELL_ID",
format!("invalid/duplicate cell ID {}", cell.id),
));
}
if cell.connectivity.len() != cell.kind.node_count() {
return Err(Error::new(
"E_CONNECTIVITY",
format!("cell {} has the wrong node count", cell.id),
));
}
let mut used = BTreeSet::new();
for &index in &cell.connectivity {
if index >= self.points.len() {
return Err(Error::new(
"E_CONNECTIVITY",
format!("cell {} references absent point index {index}", cell.id),
));
}
if !used.insert(index) {
return Err(Error::new(
"E_DEGENERATE_CONNECTIVITY",
format!("cell {} repeats a point", cell.id),
));
}
}
}
let mut set_names = BTreeSet::new();
for set in &self.node_sets {
if set.name.is_empty() || !set_names.insert(set.name.as_str()) {
return Err(Error::new(
"E_SET",
"node set names must be nonempty and unique",
));
}
let mut members = BTreeSet::new();
for &id in &set.point_ids {
if !points.contains(&id) || !members.insert(id) {
return Err(Error::new(
"E_SET",
format!(
"node set {} has invalid or repeated point ID {id}",
set.name
),
));
}
}
}
let mut cell_set_names = BTreeSet::new();
for set in &self.cell_sets {
if set.name.is_empty()
|| !(1..=3).contains(&set.dimension)
|| !cell_set_names.insert((set.dimension, set.name.as_str()))
{
return Err(Error::new(
"E_SET",
"cell set name/dimension must be unique and valid",
));
}
let mut members = BTreeSet::new();
for &id in &set.cell_ids {
if cells.get(&id) != Some(&set.dimension) || !members.insert(id) {
return Err(Error::new(
"E_SET",
format!(
"cell set {} has invalid, repeated, or wrong-dimension cell ID {id}",
set.name
),
));
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_mesh_is_structurally_valid() {
Mesh::default().validate().unwrap();
}
#[test]
fn rejects_duplicate_point_ids() {
let p = Point {
id: 1,
position: [0.0; 3],
};
assert_eq!(
Mesh {
points: vec![p.clone(), p],
cells: vec![],
..Mesh::default()
}
.validate()
.unwrap_err()
.code,
"E_POINT_ID"
);
}
#[test]
fn rejects_nonfinite_points() {
let mesh = Mesh {
points: vec![Point {
id: 1,
position: [f64::NAN, 0.0, 0.0],
}],
cells: vec![],
..Mesh::default()
};
assert_eq!(mesh.validate().unwrap_err().code, "E_NONFINITE");
}
#[test]
fn rejects_bad_indices() {
let mesh = Mesh {
points: vec![],
cells: vec![Cell {
id: 1,
kind: CellKind::Line2,
connectivity: vec![0, 1],
property_id: None,
}],
..Mesh::default()
};
assert_eq!(mesh.validate().unwrap_err().code, "E_CONNECTIVITY");
}
#[test]
fn named_sets_allow_overlap_but_require_real_matching_ids() {
let mut mesh = Mesh {
points: vec![
Point {
id: 10,
position: [0.0; 3],
},
Point {
id: 20,
position: [1.0, 0.0, 0.0],
},
],
cells: vec![Cell {
id: 30,
kind: CellKind::Line2,
connectivity: vec![0, 1],
property_id: None,
}],
node_sets: vec![NodeSet {
name: "fixed".into(),
point_ids: vec![10],
}],
cell_sets: vec![
CellSet {
name: "edge-a".into(),
dimension: 1,
cell_ids: vec![30],
},
CellSet {
name: "edge-b".into(),
dimension: 1,
cell_ids: vec![30],
},
],
};
mesh.validate().unwrap();
mesh.cell_sets[1].dimension = 2;
assert_eq!(mesh.validate().unwrap_err().code, "E_SET");
mesh.cell_sets[1].dimension = 1;
mesh.node_sets[0].point_ids.push(999);
assert_eq!(mesh.validate().unwrap_err().code, "E_SET");
}
#[test]
fn cell_and_set_invariants_reject_ambiguous_identity() {
let mut mesh = Mesh {
points: vec![
Point {
id: 10,
position: [0.0; 3],
},
Point {
id: 20,
position: [1.0, 0.0, 0.0],
},
],
cells: vec![Cell {
id: 30,
kind: CellKind::Line2,
connectivity: vec![0, 1],
property_id: None,
}],
..Mesh::default()
};
mesh.validate().unwrap();
mesh.cells.push(mesh.cells[0].clone());
assert_eq!(mesh.validate().unwrap_err().code, "E_CELL_ID");
mesh.cells.pop();
mesh.cells[0].connectivity.pop();
assert_eq!(mesh.validate().unwrap_err().code, "E_CONNECTIVITY");
mesh.cells[0].connectivity = vec![0, 0];
assert_eq!(
mesh.validate().unwrap_err().code,
"E_DEGENERATE_CONNECTIVITY"
);
mesh.cells[0].connectivity = vec![0, 1];
mesh.node_sets = vec![
NodeSet {
name: "fixed".into(),
point_ids: vec![10],
},
NodeSet {
name: "fixed".into(),
point_ids: vec![20],
},
];
assert_eq!(mesh.validate().unwrap_err().code, "E_SET");
mesh.node_sets.pop();
mesh.cell_sets = vec![
CellSet {
name: "edge".into(),
dimension: 1,
cell_ids: vec![30],
},
CellSet {
name: "edge".into(),
dimension: 1,
cell_ids: vec![30],
},
];
assert_eq!(mesh.validate().unwrap_err().code, "E_SET");
}
}