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,167 @@
// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! Mesh partitioning for parallel processing.
pub mod partitioning_algorithms;
pub mod partitioning_types;
pub use partitioning_algorithms::*;
pub use partitioning_types::*;
#[cfg(disabled)]
mod tests {
use super::*;
use crate::mesh::{MaterialId, geometry::Rectangle};
#[test]
fn test_single_partition() {
let rect = Rectangle::new(1.0, 1.0);
let mesh = rect.generate_quad_mesh(2, 2, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 1, PartitioningStrategy::Coordinate)
.unwrap();
assert_eq!(partitions.len(), 1);
assert_eq!(partitions[0].element_count(), 4);
assert_eq!(partitions[0].node_count(), 9);
}
#[test]
fn test_coordinate_partitioning() {
let rect = Rectangle::new(2.0, 1.0);
let mesh = rect.generate_quad_mesh(4, 2, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 2, PartitioningStrategy::Coordinate)
.unwrap();
assert_eq!(partitions.len(), 2);
// Each partition should have roughly half the elements
assert!(partitions[0].element_count() > 0);
assert!(partitions[1].element_count() > 0);
}
#[test]
fn test_load_balance() {
let rect = Rectangle::new(3.0, 3.0);
let mesh = rect.generate_quad_mesh(6, 6, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 4, PartitioningStrategy::LoadBalanced)
.unwrap();
let stats = PartitioningStats::from_partitions(&partitions);
assert!(stats.is_well_balanced());
assert!(stats.load_imbalance < 0.2);
}
#[test]
fn test_boundary_node_detection() {
let rect = Rectangle::new(1.0, 1.0);
let mesh = rect.generate_quad_mesh(2, 2, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 2, PartitioningStrategy::Coordinate)
.unwrap();
// Check that boundary nodes are correctly identified
for partition in &partitions {
assert!(!partition.boundary_nodes.is_empty());
assert!(!partition.internal_nodes.is_empty());
}
}
#[test]
fn test_partition_statistics() {
let rect = Rectangle::new(2.0, 2.0);
let mesh = rect.generate_quad_mesh(4, 4, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 4, PartitioningStrategy::Graph)
.unwrap();
let stats = PartitioningStats::from_partitions(&partitions);
assert_eq!(stats.num_partitions, 4);
assert_eq!(stats.total_elements, 16);
assert!(stats.efficiency() > 0.5);
}
#[test]
fn test_single_element_per_partition() {
let rect = Rectangle::new(0.5, 0.5);
let mesh = rect.generate_quad_mesh(2, 2, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
// Request more partitions than elements
let partitions = partitioner
.partition(&mesh, 8, PartitioningStrategy::LoadBalanced)
.unwrap();
// Should create only as many partitions as there are elements
assert_eq!(partitions.len(), 4);
for partition in &partitions {
assert_eq!(partition.element_count(), 1);
}
}
#[test]
fn test_partition_direction_auto() {
// Create a mesh that's wider than tall
let rect = Rectangle::new(4.0, 1.0);
let mesh = rect.generate_quad_mesh(8, 2, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
partitioner.set_direction(PartitionDirection::Auto);
let partitions = partitioner
.partition(&mesh, 2, PartitioningStrategy::Coordinate)
.unwrap();
// Auto should choose X direction for this wide mesh
assert_eq!(partitions.len(), 2);
// Elements should be divided roughly equally
let diff =
(partitions[0].element_count() as i32 - partitions[1].element_count() as i32).abs();
assert!(diff <= 2);
}
#[test]
fn test_interface_elements() {
let rect = Rectangle::new(1.0, 1.0);
let mesh = rect.generate_quad_mesh(3, 3, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 2, PartitioningStrategy::Graph)
.unwrap();
// Check that interface elements are correctly identified
let total_interface: usize = partitions.iter().map(|p| p.interface_elements.len()).sum();
assert!(total_interface > 0);
}
#[test]
fn test_neighbor_detection() {
let rect = Rectangle::new(2.0, 2.0);
let mesh = rect.generate_quad_mesh(4, 4, MaterialId(0)).unwrap();
let partitioner = MeshPartitioner::new();
let partitions = partitioner
.partition(&mesh, 4, PartitioningStrategy::Coordinate)
.unwrap();
// Each partition should have neighbors
for partition in &partitions {
if partitions.len() > 1 {
assert!(!partition.neighbors.is_empty());
}
}
}
}
@@ -0,0 +1,624 @@
// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! Partitioning algorithms implementation.
use super::partitioning_types::{
MeshPartition, PartitionDirection, PartitioningStats, PartitioningStrategy,
};
use crate::error::{FeaResult, MeshError};
use crate::mesh::{Element, ElementId, ElementType, Mesh, NodeId, connectivity::ConnectivityInfo};
use indexmap::IndexSet;
use nalgebra::Vector3;
use std::collections::HashMap;
/// Result of mesh partitioning.
#[derive(Debug, Clone)]
pub struct PartitioningResult {
pub partitions: Vec<MeshPartition>,
pub stats: PartitioningStats,
}
/// Mesh partitioning algorithms.
pub struct MeshPartitioner;
impl MeshPartitioner {
/// Partition mesh using specified strategy.
pub fn partition(
mesh: &Mesh,
num_partitions: usize,
strategy: PartitioningStrategy,
) -> FeaResult<PartitioningResult> {
if num_partitions == 0 {
return Err(MeshError::PartitioningFailed {
reason: "Number of partitions must be positive".to_string(),
}
.into());
}
if num_partitions == 1 {
return Self::create_single_partition(mesh);
}
match strategy {
PartitioningStrategy::Coordinate => {
Self::coordinate_partitioning(mesh, num_partitions, PartitionDirection::Auto)
}
PartitioningStrategy::Graph => Self::graph_partitioning(mesh, num_partitions),
PartitioningStrategy::LoadBalanced => {
Self::load_balanced_partitioning(mesh, num_partitions)
}
}
}
/// Create single partition containing entire mesh.
fn create_single_partition(mesh: &Mesh) -> FeaResult<PartitioningResult> {
let mut partition = MeshPartition::new(0);
// Add all elements
for &element_id in mesh.elements.keys() {
partition.add_element(element_id);
}
// Add all nodes as internal nodes
for &node_id in mesh.nodes.keys() {
partition.add_internal_node(node_id);
}
let stats = PartitioningStats::from_partitions(&[partition.clone()]);
Ok(PartitioningResult {
partitions: vec![partition],
stats,
})
}
/// Coordinate-based partitioning along specified direction.
pub fn coordinate_partitioning(
mesh: &Mesh,
num_partitions: usize,
direction: PartitionDirection,
) -> FeaResult<PartitioningResult> {
// Determine partitioning direction
let direction = match direction {
PartitionDirection::Auto => Self::determine_best_direction(mesh)?,
_ => direction,
};
// Get element centroids
let centroids = Self::compute_element_centroids(mesh)?;
// Sort elements by centroid coordinate along partitioning direction
let mut element_coords: Vec<(ElementId, f64)> = centroids
.iter()
.map(|(&elem_id, centroid)| {
let coord = match direction {
PartitionDirection::X | PartitionDirection::Auto => centroid.x,
PartitionDirection::Y => centroid.y,
PartitionDirection::Z => centroid.z,
};
(elem_id, coord)
})
.collect();
element_coords.sort_by(|a, b| a.1.total_cmp(&b.1));
// Distribute elements to partitions
let elements_per_partition = mesh.num_elements() / num_partitions;
let mut partitions = Vec::new();
for partition_id in 0..num_partitions {
let mut partition = MeshPartition::new(partition_id);
let start_idx = partition_id * elements_per_partition;
let end_idx = if partition_id == num_partitions - 1 {
mesh.num_elements()
} else {
(partition_id + 1) * elements_per_partition
};
for idx in start_idx..end_idx {
if idx < element_coords.len() {
partition.add_element(element_coords[idx].0);
}
}
partitions.push(partition);
}
// Update node information
Self::update_node_information(mesh, &mut partitions)?;
let stats = PartitioningStats::from_partitions(&partitions);
Ok(PartitioningResult { partitions, stats })
}
/// Graph-based partitioning using connectivity information.
fn graph_partitioning(mesh: &Mesh, num_partitions: usize) -> FeaResult<PartitioningResult> {
// Build connectivity graph
let connectivity = ConnectivityInfo::build(&mesh.elements)?;
// Use multilevel graph partitioning approach
let partitions = Self::multilevel_partition(mesh, &connectivity, num_partitions)?;
let stats = PartitioningStats::from_partitions(&partitions);
Ok(PartitioningResult { partitions, stats })
}
/// Load-balanced partitioning considering element computational costs.
fn load_balanced_partitioning(
mesh: &Mesh,
num_partitions: usize,
) -> FeaResult<PartitioningResult> {
// Compute element weights based on type and quality
let weights = Self::compute_element_weights(mesh)?;
// Sort elements by weight
let mut weighted_elements: Vec<(ElementId, f64)> = weights.into_iter().collect();
weighted_elements.sort_by(|a, b| b.1.total_cmp(&a.1));
// Distribute using bin packing algorithm
let mut partitions = Vec::new();
let mut partition_weights: Vec<f64> = vec![0.0; num_partitions];
for _ in 0..num_partitions {
partitions.push(MeshPartition::new(partitions.len()));
}
// Greedily assign elements to least loaded partition
for (element_id, weight) in weighted_elements {
let min_partition = partition_weights
.iter()
.enumerate()
.min_by(|a, b| (*a.1).total_cmp(b.1))
.map(|(idx, _)| idx)
.unwrap();
partitions[min_partition].add_element(element_id);
partition_weights[min_partition] += weight;
}
// Update node information
Self::update_node_information(mesh, &mut partitions)?;
let stats = PartitioningStats::from_partitions(&partitions);
Ok(PartitioningResult { partitions, stats })
}
/// Multilevel graph partitioning.
fn multilevel_partition(
mesh: &Mesh,
connectivity: &ConnectivityInfo,
num_partitions: usize,
) -> FeaResult<Vec<MeshPartition>> {
// Simple recursive bisection for now
let element_ids: Vec<ElementId> = mesh.elements.keys().copied().collect();
let mut partition_map = HashMap::new();
Self::recursive_bisection(
mesh,
connectivity,
&element_ids,
num_partitions,
0,
&mut partition_map,
)?;
// Convert partition map to MeshPartition structures
let mut partitions = Vec::new();
for partition_id in 0..num_partitions {
partitions.push(MeshPartition::new(partition_id));
}
for (&element_id, &partition_id) in &partition_map {
partitions[partition_id].add_element(element_id);
}
Self::update_node_information(mesh, &mut partitions)?;
Ok(partitions)
}
/// Recursive bisection for graph partitioning.
fn recursive_bisection(
mesh: &Mesh,
connectivity: &ConnectivityInfo,
elements: &[ElementId],
target_partitions: usize,
base_partition: usize,
partition_map: &mut HashMap<ElementId, usize>,
) -> FeaResult<()> {
if target_partitions == 1 || elements.is_empty() {
// Assign all elements to current partition
for &element_id in elements {
partition_map.insert(element_id, base_partition);
}
return Ok(());
}
// Bisect elements
let (left_elements, right_elements) = Self::bisect_elements(mesh, connectivity, elements)?;
// Recursively partition each half
let left_partitions = target_partitions / 2;
let right_partitions = target_partitions - left_partitions;
Self::recursive_bisection(
mesh,
connectivity,
&left_elements,
left_partitions,
base_partition,
partition_map,
)?;
Self::recursive_bisection(
mesh,
connectivity,
&right_elements,
right_partitions,
base_partition + left_partitions,
partition_map,
)?;
Ok(())
}
/// Bisect a set of elements into two balanced sets.
fn bisect_elements(
mesh: &Mesh,
_connectivity: &ConnectivityInfo,
elements: &[ElementId],
) -> FeaResult<(Vec<ElementId>, Vec<ElementId>)> {
// Simple coordinate-based bisection
let centroids = Self::compute_element_centroids_subset(mesh, elements)?;
// Find principal direction (largest extent)
let mut min_coords = Vector3::new(f64::MAX, f64::MAX, f64::MAX);
let mut max_coords = Vector3::new(f64::MIN, f64::MIN, f64::MIN);
for centroid in centroids.values() {
min_coords.x = min_coords.x.min(centroid.x);
min_coords.y = min_coords.y.min(centroid.y);
min_coords.z = min_coords.z.min(centroid.z);
max_coords.x = max_coords.x.max(centroid.x);
max_coords.y = max_coords.y.max(centroid.y);
max_coords.z = max_coords.z.max(centroid.z);
}
let extents = max_coords - min_coords;
let direction = if extents.x >= extents.y && extents.x >= extents.z {
PartitionDirection::X
} else if extents.y >= extents.z {
PartitionDirection::Y
} else {
PartitionDirection::Z
};
// Sort elements by coordinate along principal direction
let mut sorted_elements: Vec<(ElementId, f64)> = elements
.iter()
.map(|&elem_id| {
let centroid = &centroids[&elem_id];
let coord = match direction {
PartitionDirection::X | PartitionDirection::Auto => centroid.x,
PartitionDirection::Y => centroid.y,
PartitionDirection::Z => centroid.z,
};
(elem_id, coord)
})
.collect();
sorted_elements.sort_by(|a, b| a.1.total_cmp(&b.1));
// Split at median
let mid = sorted_elements.len() / 2;
let left_elements: Vec<ElementId> =
sorted_elements[..mid].iter().map(|(id, _)| *id).collect();
let right_elements: Vec<ElementId> =
sorted_elements[mid..].iter().map(|(id, _)| *id).collect();
Ok((left_elements, right_elements))
}
/// Determine best partitioning direction based on mesh geometry.
fn determine_best_direction(mesh: &Mesh) -> FeaResult<PartitionDirection> {
let mut min_coords = Vector3::new(f64::MAX, f64::MAX, f64::MAX);
let mut max_coords = Vector3::new(f64::MIN, f64::MIN, f64::MIN);
for node in mesh.nodes.values() {
let pos = node.position();
min_coords.x = min_coords.x.min(pos.x);
min_coords.y = min_coords.y.min(pos.y);
min_coords.z = min_coords.z.min(pos.z);
max_coords.x = max_coords.x.max(pos.x);
max_coords.y = max_coords.y.max(pos.y);
max_coords.z = max_coords.z.max(pos.z);
}
let extents = max_coords - min_coords;
// Choose direction with largest extent
if extents.x >= extents.y && extents.x >= extents.z {
Ok(PartitionDirection::X)
} else if extents.y >= extents.z {
Ok(PartitionDirection::Y)
} else {
Ok(PartitionDirection::Z)
}
}
/// Compute element centroids.
fn compute_element_centroids(mesh: &Mesh) -> FeaResult<HashMap<ElementId, Vector3<f64>>> {
let mut centroids = HashMap::new();
for (&element_id, element) in &mesh.elements {
let centroid = Self::compute_element_centroid(mesh, element)?;
centroids.insert(element_id, centroid);
}
Ok(centroids)
}
/// Compute element centroids for a subset.
fn compute_element_centroids_subset(
mesh: &Mesh,
elements: &[ElementId],
) -> FeaResult<HashMap<ElementId, Vector3<f64>>> {
let mut centroids = HashMap::new();
for &element_id in elements {
if let Some(element) = mesh.get_element(element_id) {
let centroid = Self::compute_element_centroid(mesh, element)?;
centroids.insert(element_id, centroid);
}
}
Ok(centroids)
}
/// Compute centroid of a single element.
fn compute_element_centroid(mesh: &Mesh, element: &Element) -> FeaResult<Vector3<f64>> {
let mut centroid = Vector3::zeros();
let mut count = 0;
for &node_id in &element.nodes {
if let Some(node) = mesh.get_node(node_id) {
centroid += node.position();
count += 1;
}
}
if count == 0 {
return Err(MeshError::InvalidElement("Element has no valid nodes".to_string()).into());
}
Ok(centroid / count as f64)
}
/// Compute element weights based on type and quality.
fn compute_element_weights(mesh: &Mesh) -> FeaResult<HashMap<ElementId, f64>> {
let mut weights = HashMap::new();
for (&element_id, element) in &mesh.elements {
// Base weight depends on element type complexity
let base_weight = match element.element_type {
ElementType::Tri3 => 1.0,
ElementType::Tri6 => 2.0,
ElementType::Quad4 => 1.5,
ElementType::Quad8 | ElementType::Quad9 => 3.0,
ElementType::Tet4 => 2.0,
ElementType::Tet10 => 4.0,
ElementType::Hex8 => 3.0,
ElementType::Hex20 | ElementType::Hex27 => 6.0,
ElementType::Wedge6 => 2.5,
ElementType::Wedge15 => 5.0,
_ => 1.0,
};
// Additional weight for high-aspect-ratio elements (more expensive to compute)
let quality_factor = Self::estimate_element_quality(mesh, element)?;
let total_weight = base_weight * (1.0 + (1.0 / quality_factor.max(0.1)));
weights.insert(element_id, total_weight);
}
Ok(weights)
}
/// Update node information for partitions.
fn update_node_information(mesh: &Mesh, partitions: &mut [MeshPartition]) -> FeaResult<()> {
// Collect nodes for each partition
for partition in partitions.iter_mut() {
let mut all_nodes = IndexSet::new();
for &element_id in &partition.elements {
if let Some(element) = mesh.get_element(element_id) {
for &node_id in &element.nodes {
all_nodes.insert(node_id);
}
}
}
partition.nodes = all_nodes;
}
// Identify boundary nodes and partition neighbors
let mut node_to_partitions: HashMap<NodeId, IndexSet<usize>> = HashMap::new();
for (partition_id, partition) in partitions.iter().enumerate() {
for &node_id in &partition.nodes {
node_to_partitions
.entry(node_id)
.or_default()
.insert(partition_id);
}
}
// Update partition boundary/internal nodes and neighbors
for partition in partitions.iter_mut() {
for &node_id in &partition.nodes {
let sharing_partitions = &node_to_partitions[&node_id];
if sharing_partitions.len() == 1 {
partition.internal_nodes.insert(node_id);
} else {
partition.boundary_nodes.insert(node_id);
// Add other partitions as neighbors
for &other_partition in sharing_partitions {
if other_partition != partition.id {
partition.neighbors.insert(other_partition);
}
}
}
}
}
Ok(())
}
fn estimate_element_quality(mesh: &Mesh, element: &Element) -> FeaResult<f64> {
// Simple quality estimate based on aspect ratio
let node_coords: Vec<Vector3<f64>> = element
.nodes
.iter()
.map(|&node_id| {
mesh.nodes
.get(&node_id)
.map(|node| {
// Convert DVector to Vector3
if node.coordinates.len() >= 3 {
Vector3::new(
node.coordinates[0],
node.coordinates[1],
node.coordinates[2],
)
} else if node.coordinates.len() == 2 {
Vector3::new(node.coordinates[0], node.coordinates[1], 0.0)
} else {
Vector3::zeros()
}
})
.ok_or(MeshError::NodeNotFound { node_id: node_id.0 })
})
.collect::<Result<Vec<Vector3<f64>>, _>>()?;
match element.element_type {
ElementType::Tri3 => Self::triangle_quality(&node_coords),
ElementType::Quad4 => Self::quadrilateral_quality(&node_coords),
ElementType::Tet4 => Self::tetrahedron_quality(&node_coords),
ElementType::Hex8 => Self::hexahedron_quality(&node_coords),
_ => Ok(1.0), // Default quality
}
}
fn triangle_quality(coords: &[Vector3<f64>]) -> FeaResult<f64> {
if coords.len() < 3 {
return Ok(0.1);
}
let a = (coords[1] - coords[0]).magnitude();
let b = (coords[2] - coords[1]).magnitude();
let c = (coords[0] - coords[2]).magnitude();
// Quality based on aspect ratio
let s = (a + b + c) / 2.0;
let area = (s * (s - a) * (s - b) * (s - c)).sqrt();
if area > 0.0 {
let quality = (4.0 * (3.0_f64).sqrt() * area) / (a * a + b * b + c * c);
Ok(quality.max(0.1).min(1.0))
} else {
Ok(0.1)
}
}
fn quadrilateral_quality(coords: &[Vector3<f64>]) -> FeaResult<f64> {
if coords.len() < 4 {
return Ok(0.1);
}
// Compute diagonals
let diag1 = (coords[2] - coords[0]).magnitude();
let diag2 = (coords[3] - coords[1]).magnitude();
// Quality based on diagonal ratio
let quality = diag1.min(diag2) / diag1.max(diag2);
Ok(quality.max(0.1).min(1.0))
}
fn tetrahedron_quality(coords: &[Vector3<f64>]) -> FeaResult<f64> {
if coords.len() < 4 {
return Ok(0.1);
}
// Volume-based quality metric
let v1 = coords[1] - coords[0];
let v2 = coords[2] - coords[0];
let v3 = coords[3] - coords[0];
let volume = v1.cross(&v2).dot(&v3).abs() / 6.0;
if volume > 0.0 {
// Compute surface area
let area1 = v1.cross(&v2).magnitude() / 2.0;
let area2 = v1.cross(&v3).magnitude() / 2.0;
let area3 = v2.cross(&v3).magnitude() / 2.0;
let area4 = (coords[2] - coords[1])
.cross(&(coords[3] - coords[1]))
.magnitude()
/ 2.0;
let surface_area = area1 + area2 + area3 + area4;
let quality =
(36.0 * std::f64::consts::PI * volume * volume).powf(1.0 / 3.0) / surface_area;
Ok(quality.max(0.1).min(1.0))
} else {
Ok(0.1)
}
}
fn hexahedron_quality(coords: &[Vector3<f64>]) -> FeaResult<f64> {
if coords.len() < 8 {
return Ok(0.1);
}
// Simplified quality based on edge length ratios
let mut min_edge = f64::MAX;
let mut max_edge = f64::MIN;
// Check edges
let edges = [
(0, 1),
(1, 2),
(2, 3),
(3, 0),
(4, 5),
(5, 6),
(6, 7),
(7, 4),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
];
for (i, j) in &edges {
let length = (coords[*j] - coords[*i]).magnitude();
min_edge = min_edge.min(length);
max_edge = max_edge.max(length);
}
let quality = if max_edge > 0.0 {
min_edge / max_edge
} else {
0.1
};
Ok(quality.max(0.1).min(1.0))
}
}
@@ -0,0 +1,242 @@
// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! Type definitions for mesh partitioning.
use crate::mesh::{ElementId, NodeId};
use indexmap::IndexSet;
/// Partitioning strategy for parallel processing.
#[derive(Debug, Clone, Copy)]
pub enum PartitioningStrategy {
/// Simple coordinate-based partitioning
Coordinate,
/// Graph-based partitioning using connectivity
Graph,
/// Load-balanced partitioning
LoadBalanced,
}
/// Partitioning direction for coordinate-based methods.
#[derive(Debug, Clone, Copy)]
pub enum PartitionDirection {
X,
Y,
Z,
Auto, // Choose based on mesh geometry
}
/// Mesh partition containing elements and associated data.
#[derive(Debug, Clone)]
pub struct MeshPartition {
/// Partition ID
pub id: usize,
/// Elements in this partition
pub elements: IndexSet<ElementId>,
/// Nodes in this partition (including boundary nodes)
pub nodes: IndexSet<NodeId>,
/// Internal nodes (owned by this partition only)
pub internal_nodes: IndexSet<NodeId>,
/// Boundary nodes (shared with other partitions)
pub boundary_nodes: IndexSet<NodeId>,
/// Neighboring partitions
pub neighbors: IndexSet<usize>,
/// Interface elements (shared with other partitions)
pub interface_elements: IndexSet<ElementId>,
}
impl MeshPartition {
/// Create a new empty partition.
pub fn new(id: usize) -> Self {
Self {
id,
elements: IndexSet::new(),
nodes: IndexSet::new(),
internal_nodes: IndexSet::new(),
boundary_nodes: IndexSet::new(),
neighbors: IndexSet::new(),
interface_elements: IndexSet::new(),
}
}
/// Add an element to the partition.
pub fn add_element(&mut self, element_id: ElementId) {
self.elements.insert(element_id);
}
/// Add a node to the partition.
pub fn add_node(&mut self, node_id: NodeId) {
self.nodes.insert(node_id);
}
/// Add an internal node (owned only by this partition).
pub fn add_internal_node(&mut self, node_id: NodeId) {
self.internal_nodes.insert(node_id);
self.nodes.insert(node_id);
}
/// Add a boundary node (shared with other partitions).
pub fn add_boundary_node(&mut self, node_id: NodeId) {
self.boundary_nodes.insert(node_id);
self.nodes.insert(node_id);
}
/// Add a neighboring partition.
pub fn add_neighbor(&mut self, neighbor_id: usize) {
if neighbor_id != self.id {
self.neighbors.insert(neighbor_id);
}
}
/// Add an interface element.
pub fn add_interface_element(&mut self, element_id: ElementId) {
self.interface_elements.insert(element_id);
}
/// Get the number of elements in this partition.
pub fn element_count(&self) -> usize {
self.elements.len()
}
/// Get the number of nodes in this partition.
pub fn node_count(&self) -> usize {
self.nodes.len()
}
/// Get the number of boundary nodes.
pub fn boundary_node_count(&self) -> usize {
self.boundary_nodes.len()
}
/// Get the number of internal nodes.
pub fn internal_node_count(&self) -> usize {
self.internal_nodes.len()
}
/// Check if this partition contains the given element.
pub fn contains_element(&self, element_id: &ElementId) -> bool {
self.elements.contains(element_id)
}
/// Check if this partition contains the given node.
pub fn contains_node(&self, node_id: &NodeId) -> bool {
self.nodes.contains(node_id)
}
/// Check if the given node is a boundary node.
pub fn is_boundary_node(&self, node_id: &NodeId) -> bool {
self.boundary_nodes.contains(node_id)
}
/// Check if the given node is an internal node.
pub fn is_internal_node(&self, node_id: &NodeId) -> bool {
self.internal_nodes.contains(node_id)
}
/// Get load balance statistics for this partition.
pub fn load_balance_info(&self) -> PartitionLoadInfo {
PartitionLoadInfo {
id: self.id,
element_count: self.elements.len(),
node_count: self.nodes.len(),
internal_node_count: self.internal_nodes.len(),
boundary_node_count: self.boundary_nodes.len(),
interface_element_count: self.interface_elements.len(),
neighbor_count: self.neighbors.len(),
}
}
}
/// Load balance information for a partition.
#[derive(Debug, Clone)]
pub struct PartitionLoadInfo {
pub id: usize,
pub element_count: usize,
pub node_count: usize,
pub internal_node_count: usize,
pub boundary_node_count: usize,
pub interface_element_count: usize,
pub neighbor_count: usize,
}
/// Statistics for mesh partitioning.
#[derive(Debug, Clone)]
pub struct PartitioningStats {
pub num_partitions: usize,
pub total_elements: usize,
pub total_nodes: usize,
pub avg_elements_per_partition: f64,
pub max_elements_per_partition: usize,
pub min_elements_per_partition: usize,
pub load_imbalance: f64,
pub total_interface_nodes: usize,
pub communication_volume: usize,
}
impl PartitioningStats {
/// Create new statistics from a set of partitions.
pub fn from_partitions(partitions: &[MeshPartition]) -> Self {
let num_partitions = partitions.len();
let total_elements: usize = partitions.iter().map(MeshPartition::element_count).sum();
// Count unique nodes (avoiding duplicates in boundary nodes)
let mut all_nodes: IndexSet<NodeId> = IndexSet::new();
for partition in partitions {
all_nodes.extend(&partition.nodes);
}
let total_nodes = all_nodes.len();
let element_counts: Vec<usize> = partitions.iter().map(MeshPartition::element_count).collect();
let max_elements = element_counts.iter().max().copied().unwrap_or(0);
let min_elements = element_counts.iter().min().copied().unwrap_or(0);
let avg_elements = if num_partitions > 0 {
total_elements as f64 / num_partitions as f64
} else {
0.0
};
// Calculate load imbalance
let load_imbalance = if avg_elements > 0.0 {
(max_elements as f64 - avg_elements) / avg_elements
} else {
0.0
};
// Count total interface nodes
let total_interface_nodes: usize = partitions.iter().map(MeshPartition::boundary_node_count).sum();
// Estimate communication volume
let communication_volume: usize = partitions
.iter()
.map(|p| p.boundary_node_count() * p.neighbors.len())
.sum();
Self {
num_partitions,
total_elements,
total_nodes,
avg_elements_per_partition: avg_elements,
max_elements_per_partition: max_elements,
min_elements_per_partition: min_elements,
load_imbalance,
total_interface_nodes,
communication_volume,
}
}
/// Check if the partitioning is well-balanced.
pub fn is_well_balanced(&self) -> bool {
self.load_imbalance < 0.1 // Less than 10% imbalance
}
/// Get efficiency rating (0.0 to 1.0).
pub fn efficiency(&self) -> f64 {
let balance_score = 1.0 / (1.0 + self.load_imbalance);
let comm_score = if self.total_nodes > 0 {
1.0 - (self.total_interface_nodes as f64 / self.total_nodes as f64).min(1.0)
} else {
1.0
};
f64::midpoint(balance_score, comm_score)
}
}