Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -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(&current) {
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));
}
}