755 lines
24 KiB
Rust
755 lines
24 KiB
Rust
// TDD: GREEN phase - Implement structured mesh
|
|
|
|
use super::{Mesh, MeshBounds, MeshStatistics};
|
|
use crate::error::{CfdError, CfdResult};
|
|
use crate::mesh::entities::{Cell, Face, Node};
|
|
use crate::traits::MeshEntity;
|
|
use indexmap::IndexMap;
|
|
use nalgebra::Vector3;
|
|
|
|
/// Structured (regular) mesh for rectangular domains
|
|
#[derive(Debug, Clone)]
|
|
pub struct StructuredMesh {
|
|
/// Number of nodes in x direction
|
|
nx: usize,
|
|
/// Number of nodes in y direction
|
|
ny: usize,
|
|
/// Number of nodes in z direction
|
|
nz: usize,
|
|
/// Domain width (x direction)
|
|
width: f64,
|
|
/// Domain height (y direction)
|
|
height: f64,
|
|
/// Domain depth (z direction)
|
|
depth: f64,
|
|
/// Grid spacing in x direction
|
|
dx: f64,
|
|
/// Grid spacing in y direction
|
|
dy: f64,
|
|
/// Grid spacing in z direction
|
|
dz: f64,
|
|
/// Nodes storage
|
|
nodes: IndexMap<usize, Node>,
|
|
/// Cells storage
|
|
cells: IndexMap<usize, Cell>,
|
|
/// Faces storage
|
|
faces: IndexMap<usize, Face>,
|
|
/// Whether this is a 2D mesh
|
|
is_2d: bool,
|
|
}
|
|
|
|
impl StructuredMesh {
|
|
/// Create a new 2D structured mesh
|
|
pub fn new(nx: usize, ny: usize, width: f64, height: f64) -> CfdResult<Self> {
|
|
if nx < 2 || ny < 2 {
|
|
return Err(CfdError::mesh("Mesh dimensions must be at least 2x2"));
|
|
}
|
|
if width <= 0.0 || height <= 0.0 {
|
|
return Err(CfdError::mesh("Mesh dimensions must be positive"));
|
|
}
|
|
|
|
let dx = width / (nx - 1) as f64;
|
|
let dy = height / (ny - 1) as f64;
|
|
|
|
let mut mesh = Self {
|
|
nx,
|
|
ny,
|
|
nz: 1,
|
|
width,
|
|
height,
|
|
depth: 0.0,
|
|
dx,
|
|
dy,
|
|
dz: 0.0,
|
|
nodes: IndexMap::new(),
|
|
cells: IndexMap::new(),
|
|
faces: IndexMap::new(),
|
|
is_2d: true,
|
|
};
|
|
|
|
mesh.generate_nodes()?;
|
|
mesh.generate_cells()?;
|
|
mesh.generate_faces()?;
|
|
|
|
Ok(mesh)
|
|
}
|
|
|
|
/// Create a new 3D structured mesh
|
|
pub fn new_3d(
|
|
nx: usize,
|
|
ny: usize,
|
|
nz: usize,
|
|
width: f64,
|
|
height: f64,
|
|
depth: f64,
|
|
) -> CfdResult<Self> {
|
|
if nx < 2 || ny < 2 || nz < 2 {
|
|
return Err(CfdError::mesh("Mesh dimensions must be at least 2x2x2"));
|
|
}
|
|
if width <= 0.0 || height <= 0.0 || depth <= 0.0 {
|
|
return Err(CfdError::mesh("Mesh dimensions must be positive"));
|
|
}
|
|
|
|
let dx = width / (nx - 1) as f64;
|
|
let dy = height / (ny - 1) as f64;
|
|
let dz = depth / (nz - 1) as f64;
|
|
|
|
let mut mesh = Self {
|
|
nx,
|
|
ny,
|
|
nz,
|
|
width,
|
|
height,
|
|
depth,
|
|
dx,
|
|
dy,
|
|
dz,
|
|
nodes: IndexMap::new(),
|
|
cells: IndexMap::new(),
|
|
faces: IndexMap::new(),
|
|
is_2d: false,
|
|
};
|
|
|
|
mesh.generate_nodes()?;
|
|
mesh.generate_cells()?;
|
|
mesh.generate_faces()?;
|
|
|
|
Ok(mesh)
|
|
}
|
|
|
|
/// Get number of nodes in x direction
|
|
#[must_use]
|
|
pub fn nx(&self) -> usize {
|
|
self.nx
|
|
}
|
|
|
|
/// Get number of nodes in y direction
|
|
#[must_use]
|
|
pub fn ny(&self) -> usize {
|
|
self.ny
|
|
}
|
|
|
|
/// Get number of nodes in z direction
|
|
#[must_use]
|
|
pub fn nz(&self) -> usize {
|
|
self.nz
|
|
}
|
|
|
|
/// Get domain width
|
|
#[must_use]
|
|
pub fn width(&self) -> f64 {
|
|
self.width
|
|
}
|
|
|
|
/// Get domain height
|
|
#[must_use]
|
|
pub fn height(&self) -> f64 {
|
|
self.height
|
|
}
|
|
|
|
/// Get domain depth
|
|
#[must_use]
|
|
pub fn depth(&self) -> f64 {
|
|
self.depth
|
|
}
|
|
|
|
/// Get grid spacing in x direction
|
|
#[must_use]
|
|
pub fn dx(&self) -> f64 {
|
|
self.dx
|
|
}
|
|
|
|
/// Get grid spacing in y direction
|
|
#[must_use]
|
|
pub fn dy(&self) -> f64 {
|
|
self.dy
|
|
}
|
|
|
|
/// Get grid spacing in z direction
|
|
#[must_use]
|
|
pub fn dz(&self) -> f64 {
|
|
self.dz
|
|
}
|
|
|
|
/// Convert (i,j,k) indices to linear node index
|
|
fn node_index(&self, i: usize, j: usize, k: usize) -> usize {
|
|
k * self.nx * self.ny + j * self.nx + i
|
|
}
|
|
|
|
/// Convert (i,j,k) indices to linear cell index
|
|
fn cell_index(&self, i: usize, j: usize, k: usize) -> usize {
|
|
k * (self.nx - 1) * (self.ny - 1) + j * (self.nx - 1) + i
|
|
}
|
|
|
|
/// Get node at (i,j,k) coordinates
|
|
pub fn get_node(&self, i: usize, j: usize, k: usize) -> CfdResult<&Node> {
|
|
if i >= self.nx || j >= self.ny || k >= self.nz {
|
|
return Err(CfdError::mesh("Node indices out of bounds"));
|
|
}
|
|
|
|
let index = self.node_index(i, j, k);
|
|
self.nodes
|
|
.get(&index)
|
|
.ok_or_else(|| CfdError::mesh("Node not found"))
|
|
}
|
|
|
|
/// Get cell at (i,j,k) coordinates
|
|
pub fn get_cell(&self, i: usize, j: usize, k: usize) -> CfdResult<&Cell> {
|
|
let nz_cells = if self.is_2d { 1 } else { self.nz - 1 };
|
|
if i >= self.nx - 1 || j >= self.ny - 1 || k >= nz_cells {
|
|
return Err(CfdError::mesh("Cell indices out of bounds"));
|
|
}
|
|
|
|
let index = self.cell_index(i, j, k);
|
|
self.cells
|
|
.get(&index)
|
|
.ok_or_else(|| CfdError::mesh("Cell not found"))
|
|
}
|
|
|
|
/// Generate all nodes
|
|
fn generate_nodes(&mut self) -> CfdResult<()> {
|
|
for k in 0..self.nz {
|
|
for j in 0..self.ny {
|
|
for i in 0..self.nx {
|
|
let x = i as f64 * self.dx;
|
|
let y = j as f64 * self.dy;
|
|
let z = if self.is_2d { 0.0 } else { k as f64 * self.dz };
|
|
|
|
let position = Vector3::new(x, y, z);
|
|
let index = self.node_index(i, j, k);
|
|
let node = Node::new(index, position);
|
|
|
|
self.nodes.insert(index, node);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Generate all cells
|
|
fn generate_cells(&mut self) -> CfdResult<()> {
|
|
let nz_cells = if self.is_2d { 1 } else { self.nz - 1 };
|
|
for k in 0..nz_cells {
|
|
for j in 0..self.ny - 1 {
|
|
for i in 0..self.nx - 1 {
|
|
let cell_id = self.cell_index(i, j, k);
|
|
|
|
if self.is_2d {
|
|
// 2D quadrilateral cell
|
|
let vertices = vec![
|
|
self.node_index(i, j, 0),
|
|
self.node_index(i + 1, j, 0),
|
|
self.node_index(i + 1, j + 1, 0),
|
|
self.node_index(i, j + 1, 0),
|
|
];
|
|
|
|
let centroid = Vector3::new(
|
|
(i as f64 + 0.5) * self.dx,
|
|
(j as f64 + 0.5) * self.dy,
|
|
0.0,
|
|
);
|
|
|
|
let volume = self.dx * self.dy; // Area for 2D
|
|
let mut cell = Cell::new(cell_id, vertices, centroid, volume);
|
|
|
|
// Check if cell is on boundary
|
|
if i == 0 || i == self.nx - 2 || j == 0 || j == self.ny - 2 {
|
|
cell.set_boundary(true);
|
|
}
|
|
|
|
self.cells.insert(cell_id, cell);
|
|
} else {
|
|
// 3D hexahedral cell
|
|
let vertices = vec![
|
|
self.node_index(i, j, k),
|
|
self.node_index(i + 1, j, k),
|
|
self.node_index(i + 1, j + 1, k),
|
|
self.node_index(i, j + 1, k),
|
|
self.node_index(i, j, k + 1),
|
|
self.node_index(i + 1, j, k + 1),
|
|
self.node_index(i + 1, j + 1, k + 1),
|
|
self.node_index(i, j + 1, k + 1),
|
|
];
|
|
|
|
let centroid = Vector3::new(
|
|
(i as f64 + 0.5) * self.dx,
|
|
(j as f64 + 0.5) * self.dy,
|
|
(k as f64 + 0.5) * self.dz,
|
|
);
|
|
|
|
let volume = self.dx * self.dy * self.dz;
|
|
let mut cell = Cell::new(cell_id, vertices, centroid, volume);
|
|
|
|
// Check if cell is on boundary
|
|
if i == 0
|
|
|| i == self.nx - 2
|
|
|| j == 0
|
|
|| j == self.ny - 2
|
|
|| k == 0
|
|
|| k == self.nz - 2
|
|
{
|
|
cell.set_boundary(true);
|
|
}
|
|
|
|
self.cells.insert(cell_id, cell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Generate all faces
|
|
fn generate_faces(&mut self) -> CfdResult<()> {
|
|
let mut face_id = 0;
|
|
|
|
if self.is_2d {
|
|
// Generate 2D faces (edges)
|
|
// Horizontal faces
|
|
for j in 0..self.ny {
|
|
for i in 0..self.nx - 1 {
|
|
let vertices = vec![self.node_index(i, j, 0), self.node_index(i + 1, j, 0)];
|
|
|
|
let centroid =
|
|
Vector3::new((i as f64 + 0.5) * self.dx, j as f64 * self.dy, 0.0);
|
|
let area = self.dx;
|
|
let normal = Vector3::new(
|
|
0.0,
|
|
if j == 0 {
|
|
-1.0
|
|
} else if j == self.ny - 1 {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
},
|
|
0.0,
|
|
);
|
|
|
|
let is_boundary = j == 0 || j == self.ny - 1;
|
|
let face = if is_boundary {
|
|
Face::new_boundary(face_id, vertices, centroid, area, normal)
|
|
} else {
|
|
Face::new(face_id, vertices, centroid, area)
|
|
};
|
|
|
|
self.faces.insert(face_id, face);
|
|
face_id += 1;
|
|
}
|
|
}
|
|
|
|
// Vertical faces
|
|
for j in 0..self.ny - 1 {
|
|
for i in 0..self.nx {
|
|
let vertices = vec![self.node_index(i, j, 0), self.node_index(i, j + 1, 0)];
|
|
|
|
let centroid =
|
|
Vector3::new(i as f64 * self.dx, (j as f64 + 0.5) * self.dy, 0.0);
|
|
let area = self.dy;
|
|
let normal = Vector3::new(
|
|
if i == 0 {
|
|
-1.0
|
|
} else if i == self.nx - 1 {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
},
|
|
0.0,
|
|
0.0,
|
|
);
|
|
|
|
let is_boundary = i == 0 || i == self.nx - 1;
|
|
let face = if is_boundary {
|
|
Face::new_boundary(face_id, vertices, centroid, area, normal)
|
|
} else {
|
|
Face::new(face_id, vertices, centroid, area)
|
|
};
|
|
|
|
self.faces.insert(face_id, face);
|
|
face_id += 1;
|
|
}
|
|
}
|
|
} else {
|
|
// 3D face generation - create faces for all 6 directions
|
|
self.generate_3d_faces(&mut face_id)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Generate 3D faces for structured mesh
|
|
fn generate_3d_faces(&mut self, face_id: &mut usize) -> CfdResult<()> {
|
|
// X-direction faces (YZ planes)
|
|
for k in 0..self.nz - 1 {
|
|
for j in 0..self.ny - 1 {
|
|
for i in 0..self.nx {
|
|
let vertices = vec![
|
|
self.node_index(i, j, k),
|
|
self.node_index(i, j + 1, k),
|
|
self.node_index(i, j + 1, k + 1),
|
|
self.node_index(i, j, k + 1),
|
|
];
|
|
|
|
let centroid = Vector3::new(
|
|
i as f64 * self.dx,
|
|
(j as f64 + 0.5) * self.dy,
|
|
(k as f64 + 0.5) * self.dz,
|
|
);
|
|
|
|
let area = self.dy * self.dz;
|
|
let is_boundary = i == 0 || i == self.nx - 1;
|
|
let normal = Vector3::new(
|
|
if i == 0 {
|
|
-1.0
|
|
} else if i == self.nx - 1 {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
},
|
|
0.0,
|
|
0.0,
|
|
);
|
|
|
|
let face = if is_boundary {
|
|
Face::new_boundary(*face_id, vertices, centroid, area, normal)
|
|
} else {
|
|
Face::new(*face_id, vertices, centroid, area)
|
|
};
|
|
|
|
self.faces.insert(*face_id, face);
|
|
*face_id += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Y-direction faces (XZ planes)
|
|
for k in 0..self.nz - 1 {
|
|
for j in 0..self.ny {
|
|
for i in 0..self.nx - 1 {
|
|
let vertices = vec![
|
|
self.node_index(i, j, k),
|
|
self.node_index(i, j, k + 1),
|
|
self.node_index(i + 1, j, k + 1),
|
|
self.node_index(i + 1, j, k),
|
|
];
|
|
|
|
let centroid = Vector3::new(
|
|
(i as f64 + 0.5) * self.dx,
|
|
j as f64 * self.dy,
|
|
(k as f64 + 0.5) * self.dz,
|
|
);
|
|
|
|
let area = self.dx * self.dz;
|
|
let is_boundary = j == 0 || j == self.ny - 1;
|
|
let normal = Vector3::new(
|
|
0.0,
|
|
if j == 0 {
|
|
-1.0
|
|
} else if j == self.ny - 1 {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
},
|
|
0.0,
|
|
);
|
|
|
|
let face = if is_boundary {
|
|
Face::new_boundary(*face_id, vertices, centroid, area, normal)
|
|
} else {
|
|
Face::new(*face_id, vertices, centroid, area)
|
|
};
|
|
|
|
self.faces.insert(*face_id, face);
|
|
*face_id += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Z-direction faces (XY planes)
|
|
for k in 0..self.nz {
|
|
for j in 0..self.ny - 1 {
|
|
for i in 0..self.nx - 1 {
|
|
let vertices = vec![
|
|
self.node_index(i, j, k),
|
|
self.node_index(i + 1, j, k),
|
|
self.node_index(i + 1, j + 1, k),
|
|
self.node_index(i, j + 1, k),
|
|
];
|
|
|
|
let centroid = Vector3::new(
|
|
(i as f64 + 0.5) * self.dx,
|
|
(j as f64 + 0.5) * self.dy,
|
|
k as f64 * self.dz,
|
|
);
|
|
|
|
let area = self.dx * self.dy;
|
|
let is_boundary = k == 0 || k == self.nz - 1;
|
|
let normal = Vector3::new(
|
|
0.0,
|
|
0.0,
|
|
if k == 0 {
|
|
-1.0
|
|
} else if k == self.nz - 1 {
|
|
1.0
|
|
} else {
|
|
0.0
|
|
},
|
|
);
|
|
|
|
let face = if is_boundary {
|
|
Face::new_boundary(*face_id, vertices, centroid, area, normal)
|
|
} else {
|
|
Face::new(*face_id, vertices, centroid, area)
|
|
};
|
|
|
|
self.faces.insert(*face_id, face);
|
|
*face_id += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get all faces
|
|
#[must_use]
|
|
pub fn get_faces(&self) -> Vec<&Face> {
|
|
self.faces.values().collect()
|
|
}
|
|
|
|
/// Check if a node exists
|
|
#[must_use]
|
|
pub fn has_node(&self, node_id: usize) -> bool {
|
|
self.nodes.contains_key(&node_id)
|
|
}
|
|
}
|
|
|
|
impl Mesh for StructuredMesh {
|
|
fn cell_count(&self) -> usize {
|
|
self.cells.len()
|
|
}
|
|
|
|
fn node_count(&self) -> usize {
|
|
self.nodes.len()
|
|
}
|
|
|
|
fn bounds(&self) -> MeshBounds {
|
|
MeshBounds::new(
|
|
Vector3::new(0.0, 0.0, 0.0),
|
|
Vector3::new(self.width, self.height, self.depth),
|
|
)
|
|
}
|
|
|
|
fn validate(&self) -> CfdResult<()> {
|
|
// Check that all cells have valid vertices
|
|
for (_, cell) in &self.cells {
|
|
for &vertex_id in cell.vertex_indices() {
|
|
if !self.nodes.contains_key(&vertex_id) {
|
|
return Err(CfdError::mesh("Cell references non-existent vertex"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check mesh connectivity
|
|
if self.cells.is_empty() {
|
|
return Err(CfdError::mesh("Mesh has no cells"));
|
|
}
|
|
|
|
if self.nodes.is_empty() {
|
|
return Err(CfdError::mesh("Mesh has no nodes"));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn statistics(&self) -> MeshStatistics {
|
|
let mut stats = MeshStatistics::new();
|
|
stats.total_cells = self.cells.len();
|
|
stats.total_nodes = self.nodes.len();
|
|
stats.total_faces = self.faces.len();
|
|
|
|
// Calculate volume statistics
|
|
if !self.cells.is_empty() {
|
|
let volumes: Vec<f64> = self
|
|
.cells
|
|
.values()
|
|
.map(super::super::traits::MeshEntity::volume)
|
|
.collect();
|
|
stats.min_cell_volume = volumes.iter().fold(f64::INFINITY, |a, &b| a.min(b));
|
|
stats.max_cell_volume = volumes.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
|
|
stats.average_cell_volume = volumes.iter().sum::<f64>() / volumes.len() as f64;
|
|
}
|
|
|
|
// Count boundary faces
|
|
stats.boundary_faces = self
|
|
.faces
|
|
.values()
|
|
.filter(|face| face.is_boundary())
|
|
.count();
|
|
|
|
// Calculate aspect ratio
|
|
let dimensions = self.bounds().dimensions();
|
|
let max_dim = dimensions.x.max(dimensions.y).max(dimensions.z);
|
|
let min_dim = dimensions.x.min(dimensions.y).min(dimensions.z.max(1e-10));
|
|
stats.aspect_ratio = max_dim / min_dim;
|
|
|
|
stats
|
|
}
|
|
|
|
fn is_boundary_cell(&self, cell_id: usize) -> bool {
|
|
self.cells
|
|
.get(&cell_id)
|
|
.is_some_and(super::super::traits::MeshEntity::is_boundary)
|
|
}
|
|
|
|
fn get_cell_neighbors(&self, cell_id: usize) -> CfdResult<Vec<usize>> {
|
|
// For structured mesh, we can calculate neighbors directly
|
|
let mut neighbors = Vec::new();
|
|
|
|
// Find the (i,j,k) coordinates of this cell
|
|
let nz_cells = if self.is_2d { 1 } else { self.nz - 1 };
|
|
for k in 0..nz_cells {
|
|
for j in 0..self.ny - 1 {
|
|
for i in 0..self.nx - 1 {
|
|
if self.cell_index(i, j, k) == cell_id {
|
|
// Add neighboring cells
|
|
if i > 0 {
|
|
neighbors.push(self.cell_index(i - 1, j, k));
|
|
}
|
|
if i < self.nx - 2 {
|
|
neighbors.push(self.cell_index(i + 1, j, k));
|
|
}
|
|
if j > 0 {
|
|
neighbors.push(self.cell_index(i, j - 1, k));
|
|
}
|
|
if j < self.ny - 2 {
|
|
neighbors.push(self.cell_index(i, j + 1, k));
|
|
}
|
|
if !self.is_2d {
|
|
if k > 0 {
|
|
neighbors.push(self.cell_index(i, j, k - 1));
|
|
}
|
|
if k < nz_cells - 1 {
|
|
neighbors.push(self.cell_index(i, j, k + 1));
|
|
}
|
|
}
|
|
return Ok(neighbors);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Err(CfdError::mesh("Cell not found"))
|
|
}
|
|
|
|
fn refine(&mut self) -> CfdResult<()> {
|
|
// Simple uniform refinement - double the resolution
|
|
let new_nx = (self.nx - 1) * 2 + 1;
|
|
let new_ny = (self.ny - 1) * 2 + 1;
|
|
|
|
if self.is_2d {
|
|
*self = Self::new(new_nx, new_ny, self.width, self.height)?;
|
|
} else {
|
|
let new_nz = (self.nz - 1) * 2 + 1;
|
|
*self = Self::new_3d(new_nx, new_ny, new_nz, self.width, self.height, self.depth)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn coarsen(&mut self) -> CfdResult<()> {
|
|
// Simple uniform coarsening - halve the resolution
|
|
// Ensure we have enough cells to coarsen
|
|
if self.nx < 3 || self.ny < 3 {
|
|
return Err(CfdError::mesh("Mesh too small to coarsen"));
|
|
}
|
|
|
|
let new_nx = (self.nx - 1) / 2 + 1;
|
|
let new_ny = (self.ny - 1) / 2 + 1;
|
|
|
|
if new_nx < 2 || new_ny < 2 {
|
|
return Err(CfdError::mesh("Coarsening would result in invalid mesh"));
|
|
}
|
|
|
|
if self.is_2d {
|
|
*self = Self::new(new_nx, new_ny, self.width, self.height)?;
|
|
} else {
|
|
if self.nz < 3 {
|
|
return Err(CfdError::mesh("3D mesh too small to coarsen"));
|
|
}
|
|
let new_nz = (self.nz - 1) / 2 + 1;
|
|
if new_nz < 2 {
|
|
return Err(CfdError::mesh("Coarsening would result in invalid 3D mesh"));
|
|
}
|
|
*self = Self::new_3d(new_nx, new_ny, new_nz, self.width, self.height, self.depth)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_structured_mesh_2d() {
|
|
let mesh = StructuredMesh::new(3, 3, 1.0, 1.0).unwrap();
|
|
assert_eq!(mesh.nx(), 3);
|
|
assert_eq!(mesh.ny(), 3);
|
|
assert_eq!(mesh.cell_count(), 4); // (3-1) * (3-1)
|
|
assert_eq!(mesh.node_count(), 9); // 3 * 3
|
|
}
|
|
|
|
#[test]
|
|
fn test_structured_mesh_3d() {
|
|
let mesh = StructuredMesh::new_3d(2, 2, 2, 1.0, 1.0, 1.0).unwrap();
|
|
assert_eq!(mesh.nx(), 2);
|
|
assert_eq!(mesh.ny(), 2);
|
|
assert_eq!(mesh.nz(), 2);
|
|
assert_eq!(mesh.cell_count(), 1); // (2-1) * (2-1) * (2-1)
|
|
assert_eq!(mesh.node_count(), 8); // 2 * 2 * 2
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_dimensions() {
|
|
assert!(StructuredMesh::new(0, 5, 1.0, 1.0).is_err());
|
|
assert!(StructuredMesh::new(5, 0, 1.0, 1.0).is_err());
|
|
assert!(StructuredMesh::new(5, 5, 0.0, 1.0).is_err());
|
|
assert!(StructuredMesh::new(5, 5, 1.0, 0.0).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_access() {
|
|
let mesh = StructuredMesh::new(3, 3, 1.0, 1.0).unwrap();
|
|
let node = mesh.get_node(0, 0, 0).unwrap();
|
|
assert_eq!(node.position(), Vector3::new(0.0, 0.0, 0.0));
|
|
|
|
let node = mesh.get_node(2, 2, 0).unwrap();
|
|
assert_eq!(node.position(), Vector3::new(1.0, 1.0, 0.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_cell_access() {
|
|
let mesh = StructuredMesh::new(3, 3, 1.0, 1.0).unwrap();
|
|
let cell = mesh.get_cell(0, 0, 0).unwrap();
|
|
assert_eq!(cell.vertex_count(), 4);
|
|
assert!((cell.volume() - 0.25).abs() < 1e-10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_mesh_bounds() {
|
|
let mesh = StructuredMesh::new(4, 5, 2.0, 3.0).unwrap();
|
|
let bounds = mesh.bounds();
|
|
assert_eq!(bounds.min, Vector3::new(0.0, 0.0, 0.0));
|
|
assert_eq!(bounds.max, Vector3::new(2.0, 3.0, 0.0));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mesh_refinement() {
|
|
let mut mesh = StructuredMesh::new(3, 3, 1.0, 1.0).unwrap();
|
|
let original_cells = mesh.cell_count();
|
|
|
|
mesh.refine().unwrap();
|
|
assert_eq!(mesh.cell_count(), original_cells * 4);
|
|
}
|
|
}
|