Initial commit
This commit is contained in:
@@ -0,0 +1,701 @@
|
||||
// Copyright (c) 2024 RustyTorch++ Team
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
//! Mesh connectivity and topological operations.
|
||||
//!
|
||||
//! This module provides efficient algorithms for mesh connectivity analysis,
|
||||
//! neighbor finding, and topological queries with GPU acceleration.
|
||||
|
||||
use crate::error::FeaResult;
|
||||
use crate::mesh::{ElementId, ElementType, NodeId};
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Edge defined by two node IDs (always ordered: min, max).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Edge(pub NodeId, pub NodeId);
|
||||
|
||||
impl Edge {
|
||||
/// Create a new edge with ordered node IDs.
|
||||
pub fn new(node1: NodeId, node2: NodeId) -> Self {
|
||||
if node1.0 < node2.0 {
|
||||
Self(node1, node2)
|
||||
} else {
|
||||
Self(node2, node1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first node.
|
||||
pub fn node1(&self) -> NodeId {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Get the second node.
|
||||
pub fn node2(&self) -> NodeId {
|
||||
self.1
|
||||
}
|
||||
|
||||
/// Check if the edge contains a specific node.
|
||||
pub fn contains_node(&self, node_id: NodeId) -> bool {
|
||||
self.0 == node_id || self.1 == node_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Face defined by node IDs (ordered consistently).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Face(pub Vec<NodeId>);
|
||||
|
||||
impl Face {
|
||||
/// Create a new face with ordered node IDs.
|
||||
pub fn new(mut nodes: Vec<NodeId>) -> Self {
|
||||
// Find the minimum node and rotate to start with it
|
||||
if let Some(min_pos) = nodes
|
||||
.iter()
|
||||
.position(|&n| n == *nodes.iter().min().unwrap())
|
||||
{
|
||||
nodes.rotate_left(min_pos);
|
||||
}
|
||||
Self(nodes)
|
||||
}
|
||||
|
||||
/// Create a triangular face.
|
||||
pub fn triangle(n1: NodeId, n2: NodeId, n3: NodeId) -> Self {
|
||||
Self::new(vec![n1, n2, n3])
|
||||
}
|
||||
|
||||
/// Create a quadrilateral face.
|
||||
pub fn quad(n1: NodeId, n2: NodeId, n3: NodeId, n4: NodeId) -> Self {
|
||||
Self::new(vec![n1, n2, n3, n4])
|
||||
}
|
||||
|
||||
/// Get the nodes of this face.
|
||||
pub fn nodes(&self) -> &[NodeId] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Get the number of nodes in this face.
|
||||
pub fn node_count(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Check if the face contains a specific node.
|
||||
pub fn contains_node(&self, node_id: NodeId) -> bool {
|
||||
self.0.contains(&node_id)
|
||||
}
|
||||
|
||||
/// Get the edges of this face.
|
||||
pub fn edges(&self) -> Vec<Edge> {
|
||||
let mut edges = Vec::new();
|
||||
let n = self.0.len();
|
||||
for i in 0..n {
|
||||
let next = (i + 1) % n;
|
||||
edges.push(Edge::new(self.0[i], self.0[next]));
|
||||
}
|
||||
edges
|
||||
}
|
||||
}
|
||||
|
||||
/// Connectivity information for efficient mesh queries.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectivityInfo {
|
||||
/// Node to elements mapping
|
||||
pub node_to_elements: HashMap<NodeId, IndexSet<ElementId>>,
|
||||
/// Edge to elements mapping
|
||||
pub edge_to_elements: HashMap<Edge, IndexSet<ElementId>>,
|
||||
/// Face to elements mapping (for 3D meshes)
|
||||
pub face_to_elements: HashMap<Face, IndexSet<ElementId>>,
|
||||
/// Element to neighbors mapping
|
||||
pub element_neighbors: HashMap<ElementId, IndexSet<ElementId>>,
|
||||
/// All edges in the mesh
|
||||
pub edges: IndexSet<Edge>,
|
||||
/// All faces in the mesh
|
||||
pub faces: IndexSet<Face>,
|
||||
/// Boundary edges (only connected to one element)
|
||||
pub boundary_edges: IndexSet<Edge>,
|
||||
/// Boundary faces (only connected to one element)
|
||||
pub boundary_faces: IndexSet<Face>,
|
||||
}
|
||||
|
||||
impl ConnectivityInfo {
|
||||
/// Build connectivity information from mesh elements.
|
||||
pub fn build(elements: &IndexMap<ElementId, super::Element>) -> FeaResult<Self> {
|
||||
let mut node_to_elements: HashMap<NodeId, IndexSet<ElementId>> = HashMap::new();
|
||||
let mut edge_to_elements: HashMap<Edge, IndexSet<ElementId>> = HashMap::new();
|
||||
let mut face_to_elements: HashMap<Face, IndexSet<ElementId>> = HashMap::new();
|
||||
let mut edges = IndexSet::new();
|
||||
let mut faces = IndexSet::new();
|
||||
|
||||
// Process each element
|
||||
for (&element_id, element) in elements {
|
||||
// Add node connections
|
||||
for &node_id in &element.nodes {
|
||||
node_to_elements
|
||||
.entry(node_id)
|
||||
.or_default()
|
||||
.insert(element_id);
|
||||
}
|
||||
|
||||
// Add edge connections
|
||||
let element_edges = Self::get_element_edges(element.element_type, &element.nodes)?;
|
||||
for edge in element_edges {
|
||||
edges.insert(edge);
|
||||
edge_to_elements.entry(edge).or_default().insert(element_id);
|
||||
}
|
||||
|
||||
// Add face connections (for 3D elements)
|
||||
if element.element_type.spatial_dimension() == 3 {
|
||||
let element_faces = Self::get_element_faces(element.element_type, &element.nodes)?;
|
||||
for face in element_faces {
|
||||
faces.insert(face.clone());
|
||||
face_to_elements.entry(face).or_default().insert(element_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build element neighbors
|
||||
let mut element_neighbors: HashMap<ElementId, IndexSet<ElementId>> = HashMap::new();
|
||||
for (&element_id, element) in elements {
|
||||
let mut neighbors = IndexSet::new();
|
||||
|
||||
// For 2D meshes, neighbors share edges
|
||||
if element.element_type.spatial_dimension() == 2 {
|
||||
let element_edges = Self::get_element_edges(element.element_type, &element.nodes)?;
|
||||
for edge in element_edges {
|
||||
if let Some(edge_elements) = edge_to_elements.get(&edge) {
|
||||
for &neighbor_id in edge_elements {
|
||||
if neighbor_id != element_id {
|
||||
neighbors.insert(neighbor_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For 3D meshes, neighbors share faces
|
||||
let element_faces = Self::get_element_faces(element.element_type, &element.nodes)?;
|
||||
for face in element_faces {
|
||||
if let Some(face_elements) = face_to_elements.get(&face) {
|
||||
for &neighbor_id in face_elements {
|
||||
if neighbor_id != element_id {
|
||||
neighbors.insert(neighbor_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element_neighbors.insert(element_id, neighbors);
|
||||
}
|
||||
|
||||
// Find boundary edges and faces
|
||||
let boundary_edges: IndexSet<Edge> = edge_to_elements
|
||||
.iter()
|
||||
.filter_map(|(edge, elements)| {
|
||||
if elements.len() == 1 {
|
||||
Some(*edge)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let boundary_faces: IndexSet<Face> = face_to_elements
|
||||
.iter()
|
||||
.filter_map(|(face, elements)| {
|
||||
if elements.len() == 1 {
|
||||
Some(face.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
node_to_elements,
|
||||
edge_to_elements,
|
||||
face_to_elements,
|
||||
element_neighbors,
|
||||
edges,
|
||||
faces,
|
||||
boundary_edges,
|
||||
boundary_faces,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get edges for an element based on its type and connectivity.
|
||||
fn get_element_edges(element_type: ElementType, nodes: &[NodeId]) -> FeaResult<Vec<Edge>> {
|
||||
let edges = match element_type {
|
||||
ElementType::Tri3 | ElementType::Tri6 => {
|
||||
vec![
|
||||
Edge::new(nodes[0], nodes[1]),
|
||||
Edge::new(nodes[1], nodes[2]),
|
||||
Edge::new(nodes[2], nodes[0]),
|
||||
]
|
||||
}
|
||||
ElementType::Quad4 | ElementType::Quad8 | ElementType::Quad9 => {
|
||||
vec![
|
||||
Edge::new(nodes[0], nodes[1]),
|
||||
Edge::new(nodes[1], nodes[2]),
|
||||
Edge::new(nodes[2], nodes[3]),
|
||||
Edge::new(nodes[3], nodes[0]),
|
||||
]
|
||||
}
|
||||
ElementType::Tet4 | ElementType::Tet10 => {
|
||||
vec![
|
||||
Edge::new(nodes[0], nodes[1]),
|
||||
Edge::new(nodes[1], nodes[2]),
|
||||
Edge::new(nodes[2], nodes[0]),
|
||||
Edge::new(nodes[0], nodes[3]),
|
||||
Edge::new(nodes[1], nodes[3]),
|
||||
Edge::new(nodes[2], nodes[3]),
|
||||
]
|
||||
}
|
||||
ElementType::Hex8 | ElementType::Hex20 | ElementType::Hex27 => {
|
||||
vec![
|
||||
// Bottom face edges
|
||||
Edge::new(nodes[0], nodes[1]),
|
||||
Edge::new(nodes[1], nodes[2]),
|
||||
Edge::new(nodes[2], nodes[3]),
|
||||
Edge::new(nodes[3], nodes[0]),
|
||||
// Top face edges
|
||||
Edge::new(nodes[4], nodes[5]),
|
||||
Edge::new(nodes[5], nodes[6]),
|
||||
Edge::new(nodes[6], nodes[7]),
|
||||
Edge::new(nodes[7], nodes[4]),
|
||||
// Vertical edges
|
||||
Edge::new(nodes[0], nodes[4]),
|
||||
Edge::new(nodes[1], nodes[5]),
|
||||
Edge::new(nodes[2], nodes[6]),
|
||||
Edge::new(nodes[3], nodes[7]),
|
||||
]
|
||||
}
|
||||
ElementType::Wedge6 | ElementType::Wedge15 => {
|
||||
vec![
|
||||
// Bottom triangle edges
|
||||
Edge::new(nodes[0], nodes[1]),
|
||||
Edge::new(nodes[1], nodes[2]),
|
||||
Edge::new(nodes[2], nodes[0]),
|
||||
// Top triangle edges
|
||||
Edge::new(nodes[3], nodes[4]),
|
||||
Edge::new(nodes[4], nodes[5]),
|
||||
Edge::new(nodes[5], nodes[3]),
|
||||
// Vertical edges
|
||||
Edge::new(nodes[0], nodes[3]),
|
||||
Edge::new(nodes[1], nodes[4]),
|
||||
Edge::new(nodes[2], nodes[5]),
|
||||
]
|
||||
}
|
||||
ElementType::Pyramid5 | ElementType::Pyramid13 => {
|
||||
vec![
|
||||
// Base edges
|
||||
Edge::new(nodes[0], nodes[1]),
|
||||
Edge::new(nodes[1], nodes[2]),
|
||||
Edge::new(nodes[2], nodes[3]),
|
||||
Edge::new(nodes[3], nodes[0]),
|
||||
// Apex edges
|
||||
Edge::new(nodes[0], nodes[4]),
|
||||
Edge::new(nodes[1], nodes[4]),
|
||||
Edge::new(nodes[2], nodes[4]),
|
||||
Edge::new(nodes[3], nodes[4]),
|
||||
]
|
||||
}
|
||||
ElementType::Line2 | ElementType::Line3 => {
|
||||
// Line elements have one edge connecting the endpoints
|
||||
vec![Edge::new(nodes[0], nodes[1])]
|
||||
}
|
||||
ElementType::Point => {
|
||||
// Point elements have no edges
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
|
||||
Ok(edges)
|
||||
}
|
||||
|
||||
/// Get faces for a 3D element based on its type and connectivity.
|
||||
fn get_element_faces(element_type: ElementType, nodes: &[NodeId]) -> FeaResult<Vec<Face>> {
|
||||
if element_type.spatial_dimension() != 3 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let faces = match element_type {
|
||||
ElementType::Tet4 | ElementType::Tet10 => {
|
||||
vec![
|
||||
Face::triangle(nodes[0], nodes[1], nodes[2]),
|
||||
Face::triangle(nodes[0], nodes[1], nodes[3]),
|
||||
Face::triangle(nodes[1], nodes[2], nodes[3]),
|
||||
Face::triangle(nodes[2], nodes[0], nodes[3]),
|
||||
]
|
||||
}
|
||||
ElementType::Hex8 | ElementType::Hex20 | ElementType::Hex27 => {
|
||||
vec![
|
||||
Face::quad(nodes[0], nodes[1], nodes[2], nodes[3]), // bottom
|
||||
Face::quad(nodes[4], nodes[7], nodes[6], nodes[5]), // top (reversed for outward normal)
|
||||
Face::quad(nodes[0], nodes[4], nodes[5], nodes[1]), // front
|
||||
Face::quad(nodes[2], nodes[6], nodes[7], nodes[3]), // back
|
||||
Face::quad(nodes[0], nodes[3], nodes[7], nodes[4]), // left
|
||||
Face::quad(nodes[1], nodes[5], nodes[6], nodes[2]), // right
|
||||
]
|
||||
}
|
||||
ElementType::Wedge6 | ElementType::Wedge15 => {
|
||||
vec![
|
||||
Face::triangle(nodes[0], nodes[1], nodes[2]), // bottom triangle
|
||||
Face::triangle(nodes[3], nodes[5], nodes[4]), // top triangle (reversed)
|
||||
Face::quad(nodes[0], nodes[3], nodes[4], nodes[1]), // front
|
||||
Face::quad(nodes[1], nodes[4], nodes[5], nodes[2]), // right
|
||||
Face::quad(nodes[2], nodes[5], nodes[3], nodes[0]), // left
|
||||
]
|
||||
}
|
||||
ElementType::Pyramid5 | ElementType::Pyramid13 => {
|
||||
vec![
|
||||
Face::quad(nodes[0], nodes[3], nodes[2], nodes[1]), // base (reversed)
|
||||
Face::triangle(nodes[0], nodes[1], nodes[4]), // side 1
|
||||
Face::triangle(nodes[1], nodes[2], nodes[4]), // side 2
|
||||
Face::triangle(nodes[2], nodes[3], nodes[4]), // side 3
|
||||
Face::triangle(nodes[3], nodes[0], nodes[4]), // side 4
|
||||
]
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
Ok(faces)
|
||||
}
|
||||
|
||||
/// Get all elements connected to a node.
|
||||
pub fn elements_for_node(&self, node_id: NodeId) -> &IndexSet<ElementId> {
|
||||
use std::sync::OnceLock;
|
||||
static EMPTY: OnceLock<IndexSet<ElementId>> = OnceLock::new();
|
||||
self.node_to_elements
|
||||
.get(&node_id)
|
||||
.unwrap_or_else(|| EMPTY.get_or_init(IndexSet::new))
|
||||
}
|
||||
|
||||
/// Get all elements sharing an edge.
|
||||
pub fn elements_for_edge(&self, edge: &Edge) -> &IndexSet<ElementId> {
|
||||
use std::sync::OnceLock;
|
||||
static EMPTY: OnceLock<IndexSet<ElementId>> = OnceLock::new();
|
||||
self.edge_to_elements
|
||||
.get(edge)
|
||||
.unwrap_or_else(|| EMPTY.get_or_init(IndexSet::new))
|
||||
}
|
||||
|
||||
/// Get all elements sharing a face.
|
||||
pub fn elements_for_face(&self, face: &Face) -> &IndexSet<ElementId> {
|
||||
use std::sync::OnceLock;
|
||||
static EMPTY: OnceLock<IndexSet<ElementId>> = OnceLock::new();
|
||||
self.face_to_elements
|
||||
.get(face)
|
||||
.unwrap_or_else(|| EMPTY.get_or_init(IndexSet::new))
|
||||
}
|
||||
|
||||
/// Get neighboring elements for a given element.
|
||||
pub fn neighbors(&self, element_id: ElementId) -> &IndexSet<ElementId> {
|
||||
use std::sync::OnceLock;
|
||||
static EMPTY: OnceLock<IndexSet<ElementId>> = OnceLock::new();
|
||||
self.element_neighbors
|
||||
.get(&element_id)
|
||||
.unwrap_or_else(|| EMPTY.get_or_init(IndexSet::new))
|
||||
}
|
||||
|
||||
/// Check if an edge is on the boundary.
|
||||
pub fn is_boundary_edge(&self, edge: &Edge) -> bool {
|
||||
self.boundary_edges.contains(edge)
|
||||
}
|
||||
|
||||
/// Check if a face is on the boundary.
|
||||
pub fn is_boundary_face(&self, face: &Face) -> bool {
|
||||
self.boundary_faces.contains(face)
|
||||
}
|
||||
|
||||
/// Get all boundary nodes.
|
||||
pub fn boundary_nodes(&self) -> IndexSet<NodeId> {
|
||||
let mut boundary_nodes = IndexSet::new();
|
||||
|
||||
// Add nodes from boundary edges
|
||||
for edge in &self.boundary_edges {
|
||||
boundary_nodes.insert(edge.node1());
|
||||
boundary_nodes.insert(edge.node2());
|
||||
}
|
||||
|
||||
// Add nodes from boundary faces
|
||||
for face in &self.boundary_faces {
|
||||
for &node_id in face.nodes() {
|
||||
boundary_nodes.insert(node_id);
|
||||
}
|
||||
}
|
||||
|
||||
boundary_nodes
|
||||
}
|
||||
|
||||
/// Find the shortest path between two nodes using Dijkstra's algorithm.
|
||||
pub fn shortest_path(&self, start: NodeId, end: NodeId) -> Option<Vec<NodeId>> {
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
cost: usize,
|
||||
node: NodeId,
|
||||
}
|
||||
|
||||
impl Eq for State {}
|
||||
impl PartialEq for State {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cost == other.cost
|
||||
}
|
||||
}
|
||||
impl Ord for State {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
other.cost.cmp(&self.cost) // Reverse for min-heap
|
||||
}
|
||||
}
|
||||
impl PartialOrd for State {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
let mut heap = BinaryHeap::new();
|
||||
let mut distances: HashMap<NodeId, usize> = HashMap::new();
|
||||
let mut previous: HashMap<NodeId, NodeId> = HashMap::new();
|
||||
|
||||
distances.insert(start, 0);
|
||||
heap.push(State {
|
||||
cost: 0,
|
||||
node: start,
|
||||
});
|
||||
|
||||
while let Some(State { cost, node }) = heap.pop() {
|
||||
if node == end {
|
||||
break;
|
||||
}
|
||||
|
||||
if cost > *distances.get(&node).unwrap_or(&usize::MAX) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find neighbors through shared edges
|
||||
for edge in &self.edges {
|
||||
let neighbor = if edge.node1() == node {
|
||||
edge.node2()
|
||||
} else if edge.node2() == node {
|
||||
edge.node1()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let new_cost = cost + 1;
|
||||
if new_cost < *distances.get(&neighbor).unwrap_or(&usize::MAX) {
|
||||
distances.insert(neighbor, new_cost);
|
||||
previous.insert(neighbor, node);
|
||||
heap.push(State {
|
||||
cost: new_cost,
|
||||
node: neighbor,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct path
|
||||
if !distances.contains_key(&end) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut path = Vec::new();
|
||||
let mut current = end;
|
||||
path.push(current);
|
||||
|
||||
while let Some(&prev) = previous.get(¤t) {
|
||||
path.push(prev);
|
||||
current = prev;
|
||||
}
|
||||
|
||||
path.reverse();
|
||||
Some(path)
|
||||
}
|
||||
|
||||
/// Get mesh quality metrics.
|
||||
pub fn quality_metrics(&self) -> ConnectivityMetrics {
|
||||
let num_nodes = self.node_to_elements.len();
|
||||
let num_elements = self.element_neighbors.len();
|
||||
let num_edges = self.edges.len();
|
||||
let num_faces = self.faces.len();
|
||||
let num_boundary_edges = self.boundary_edges.len();
|
||||
let num_boundary_faces = self.boundary_faces.len();
|
||||
|
||||
// Calculate node degree statistics
|
||||
let node_degrees: Vec<usize> = self
|
||||
.node_to_elements
|
||||
.values()
|
||||
.map(indexmap::IndexSet::len)
|
||||
.collect();
|
||||
|
||||
let min_node_degree = node_degrees.iter().min().copied().unwrap_or(0);
|
||||
let max_node_degree = node_degrees.iter().max().copied().unwrap_or(0);
|
||||
let avg_node_degree = if !node_degrees.is_empty() {
|
||||
node_degrees.iter().sum::<usize>() as f64 / node_degrees.len() as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
// Calculate element neighbor statistics
|
||||
let neighbor_counts: Vec<usize> = self
|
||||
.element_neighbors
|
||||
.values()
|
||||
.map(indexmap::IndexSet::len)
|
||||
.collect();
|
||||
|
||||
let min_element_neighbors = neighbor_counts.iter().min().copied().unwrap_or(0);
|
||||
let max_element_neighbors = neighbor_counts.iter().max().copied().unwrap_or(0);
|
||||
let avg_element_neighbors = if !neighbor_counts.is_empty() {
|
||||
neighbor_counts.iter().sum::<usize>() as f64 / neighbor_counts.len() as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
ConnectivityMetrics {
|
||||
num_nodes,
|
||||
num_elements,
|
||||
num_edges,
|
||||
num_faces,
|
||||
num_boundary_edges,
|
||||
num_boundary_faces,
|
||||
min_node_degree,
|
||||
max_node_degree,
|
||||
avg_node_degree,
|
||||
min_element_neighbors,
|
||||
max_element_neighbors,
|
||||
avg_element_neighbors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connectivity quality metrics.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectivityMetrics {
|
||||
pub num_nodes: usize,
|
||||
pub num_elements: usize,
|
||||
pub num_edges: usize,
|
||||
pub num_faces: usize,
|
||||
pub num_boundary_edges: usize,
|
||||
pub num_boundary_faces: usize,
|
||||
pub min_node_degree: usize,
|
||||
pub max_node_degree: usize,
|
||||
pub avg_node_degree: f64,
|
||||
pub min_element_neighbors: usize,
|
||||
pub max_element_neighbors: usize,
|
||||
pub avg_element_neighbors: f64,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConnectivityMetrics {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Connectivity Metrics:")?;
|
||||
writeln!(f, " Nodes: {}", self.num_nodes)?;
|
||||
writeln!(f, " Elements: {}", self.num_elements)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Edges: {} ({} boundary)",
|
||||
self.num_edges, self.num_boundary_edges
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Faces: {} ({} boundary)",
|
||||
self.num_faces, self.num_boundary_faces
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Node degree: {} - {} (avg: {:.1})",
|
||||
self.min_node_degree, self.max_node_degree, self.avg_node_degree
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" Element neighbors: {} - {} (avg: {:.1})",
|
||||
self.min_element_neighbors, self.max_element_neighbors, self.avg_element_neighbors
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(disabled)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mesh::{Element, MaterialId};
|
||||
|
||||
#[test]
|
||||
fn test_edge_creation() {
|
||||
let n1 = NodeId(1);
|
||||
let n2 = NodeId(3);
|
||||
let edge1 = Edge::new(n1, n2);
|
||||
let edge2 = Edge::new(n2, n1);
|
||||
|
||||
assert_eq!(edge1, edge2); // Should be the same due to ordering
|
||||
assert_eq!(edge1.node1(), NodeId(1));
|
||||
assert_eq!(edge1.node2(), NodeId(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_face_creation() {
|
||||
let face = Face::triangle(NodeId(2), NodeId(1), NodeId(3));
|
||||
// Should start with minimum node
|
||||
assert_eq!(face.nodes()[0], NodeId(1));
|
||||
assert_eq!(face.node_count(), 3);
|
||||
assert!(face.contains_node(NodeId(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connectivity_build() {
|
||||
use indexmap::IndexMap;
|
||||
|
||||
let mut elements = IndexMap::new();
|
||||
|
||||
// Create a simple triangle
|
||||
let element = Element::new(
|
||||
ElementType::Tri3,
|
||||
vec![NodeId(0), NodeId(1), NodeId(2)],
|
||||
MaterialId(0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
elements.insert(ElementId(0), element);
|
||||
|
||||
let connectivity = ConnectivityInfo::build(&elements).unwrap();
|
||||
|
||||
assert_eq!(connectivity.node_to_elements.len(), 3);
|
||||
assert_eq!(connectivity.edges.len(), 3);
|
||||
assert_eq!(connectivity.boundary_edges.len(), 3); // All edges are boundary
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_element_neighbors() {
|
||||
use indexmap::IndexMap;
|
||||
|
||||
let mut elements = IndexMap::new();
|
||||
|
||||
// Create two adjacent triangles
|
||||
let elem1 = Element::new(
|
||||
ElementType::Tri3,
|
||||
vec![NodeId(0), NodeId(1), NodeId(2)],
|
||||
MaterialId(0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let elem2 = Element::new(
|
||||
ElementType::Tri3,
|
||||
vec![NodeId(1), NodeId(3), NodeId(2)], // Shares edge with elem1
|
||||
MaterialId(0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
elements.insert(ElementId(0), elem1);
|
||||
elements.insert(ElementId(1), elem2);
|
||||
|
||||
let connectivity = ConnectivityInfo::build(&elements).unwrap();
|
||||
|
||||
// Elements should be neighbors
|
||||
assert!(connectivity.neighbors(ElementId(0)).contains(&ElementId(1)));
|
||||
assert!(connectivity.neighbors(ElementId(1)).contains(&ElementId(0)));
|
||||
|
||||
// Shared edge should not be boundary
|
||||
let shared_edge = Edge::new(NodeId(1), NodeId(2));
|
||||
assert!(!connectivity.is_boundary_edge(&shared_edge));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user