266 lines
7.6 KiB
Rust
266 lines
7.6 KiB
Rust
// TDD: RED phase - Tests for face connectivity
|
||
|
||
use nalgebra::Vector3;
|
||
use rtx_cfd::mesh::Mesh;
|
||
use rtx_cfd::mesh::structured::StructuredMesh;
|
||
use rtx_cfd::mesh::unstructured::UnstructuredMesh;
|
||
use rtx_cfd::traits::MeshEntity;
|
||
|
||
#[test]
|
||
fn test_structured_2d_face_generation() {
|
||
let mesh = StructuredMesh::new(3, 3, 2.0, 2.0).unwrap();
|
||
|
||
// Should generate faces for 2D mesh
|
||
let stats = mesh.statistics();
|
||
|
||
// For 3x3 grid:
|
||
// - Horizontal faces: 3 rows × 2 edges = 6
|
||
// - Vertical faces: 2 rows × 3 edges = 6
|
||
// Total: 12 faces
|
||
assert_eq!(stats.total_faces, 12);
|
||
|
||
// Boundary faces should be all perimeter faces
|
||
// Perimeter: 2×(3-1) + 2×(3-1) = 8 boundary faces
|
||
assert_eq!(stats.boundary_faces, 8);
|
||
}
|
||
|
||
#[test]
|
||
fn test_structured_3d_face_generation() {
|
||
let mesh = StructuredMesh::new_3d(3, 3, 3, 2.0, 2.0, 2.0).unwrap();
|
||
|
||
let stats = mesh.statistics();
|
||
|
||
// For 3x3x3 grid, there should be many more faces in 3D
|
||
assert!(stats.total_faces > 0, "3D mesh should have faces");
|
||
|
||
// Should have boundary faces on all 6 sides of the cube
|
||
assert!(
|
||
stats.boundary_faces > 0,
|
||
"3D mesh should have boundary faces"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_face_normals_2d() {
|
||
let mesh = StructuredMesh::new(3, 3, 2.0, 2.0).unwrap();
|
||
|
||
// Test that boundary faces have correct normals pointing outward
|
||
let faces = mesh.get_faces();
|
||
let mut boundary_face_count = 0;
|
||
|
||
for face in faces {
|
||
if face.is_boundary() {
|
||
boundary_face_count += 1;
|
||
let normal = face.normal();
|
||
|
||
// Normal should be unit vector
|
||
let magnitude = normal.magnitude();
|
||
assert!(
|
||
(magnitude - 1.0).abs() < 1e-10,
|
||
"Face normal should be unit vector"
|
||
);
|
||
|
||
// For 2D mesh, normal should be in x or y direction
|
||
assert!(normal.z.abs() < 1e-10 || (normal.x.abs() < 1e-10 && normal.y.abs() < 1e-10));
|
||
}
|
||
}
|
||
|
||
assert!(boundary_face_count > 0, "Should have boundary faces");
|
||
}
|
||
|
||
#[test]
|
||
fn test_face_areas_2d() {
|
||
let mesh = StructuredMesh::new(4, 4, 3.0, 3.0).unwrap();
|
||
|
||
let faces = mesh.get_faces();
|
||
let dx = mesh.dx(); // 3.0 / (4-1) = 1.0
|
||
let dy = mesh.dy(); // 3.0 / (4-1) = 1.0
|
||
|
||
for face in faces {
|
||
let area = face.area();
|
||
|
||
// For structured 2D mesh, face area should be dx or dy
|
||
assert!(
|
||
(area - dx).abs() < 1e-10 || (area - dy).abs() < 1e-10,
|
||
"Face area should match grid spacing: got {}, expected {} or {}",
|
||
area,
|
||
dx,
|
||
dy
|
||
);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_face_centroids_2d() {
|
||
let mesh = StructuredMesh::new(3, 3, 2.0, 2.0).unwrap();
|
||
|
||
let faces = mesh.get_faces();
|
||
let bounds = mesh.bounds();
|
||
|
||
for face in faces {
|
||
let centroid = face.centroid();
|
||
|
||
// All face centroids should be within mesh bounds
|
||
assert!(
|
||
bounds.contains(centroid),
|
||
"Face centroid {:?} should be within bounds {:?}",
|
||
centroid,
|
||
bounds
|
||
);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_unstructured_face_generation() {
|
||
let mut mesh = UnstructuredMesh::new();
|
||
|
||
// Create a simple triangle
|
||
let n1 = mesh.add_node(Vector3::new(0.0, 0.0, 0.0)).unwrap();
|
||
let n2 = mesh.add_node(Vector3::new(1.0, 0.0, 0.0)).unwrap();
|
||
let n3 = mesh.add_node(Vector3::new(0.5, 1.0, 0.0)).unwrap();
|
||
let _cell = mesh.add_triangle_cell(n1, n2, n3).unwrap();
|
||
|
||
// Generate faces automatically
|
||
mesh.generate_faces().unwrap();
|
||
|
||
let stats = mesh.statistics();
|
||
|
||
// Triangle should generate 3 edges (faces)
|
||
assert_eq!(stats.total_faces, 3);
|
||
|
||
// All faces should be boundary faces for single triangle
|
||
assert_eq!(stats.boundary_faces, 3);
|
||
}
|
||
|
||
#[test]
|
||
fn test_face_neighbors() {
|
||
let mut mesh = UnstructuredMesh::new();
|
||
|
||
// Create two adjacent triangles sharing an edge
|
||
let n1 = mesh.add_node(Vector3::new(0.0, 0.0, 0.0)).unwrap();
|
||
let n2 = mesh.add_node(Vector3::new(1.0, 0.0, 0.0)).unwrap();
|
||
let n3 = mesh.add_node(Vector3::new(0.5, 1.0, 0.0)).unwrap();
|
||
let n4 = mesh.add_node(Vector3::new(1.5, 1.0, 0.0)).unwrap();
|
||
|
||
let cell1 = mesh.add_triangle_cell(n1, n2, n3).unwrap();
|
||
let cell2 = mesh.add_triangle_cell(n2, n4, n3).unwrap();
|
||
|
||
mesh.generate_faces().unwrap();
|
||
|
||
// Check that cells are neighbors (share an edge n2-n3)
|
||
let neighbors1 = mesh.get_cell_neighbors(cell1).unwrap();
|
||
let neighbors2 = mesh.get_cell_neighbors(cell2).unwrap();
|
||
|
||
assert!(
|
||
neighbors1.contains(&cell2),
|
||
"Cell1 should have Cell2 as neighbor"
|
||
);
|
||
assert!(
|
||
neighbors2.contains(&cell1),
|
||
"Cell2 should have Cell1 as neighbor"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_face_connectivity_validation() {
|
||
let mesh = StructuredMesh::new(4, 4, 2.0, 2.0).unwrap();
|
||
|
||
// Mesh should validate successfully
|
||
assert!(mesh.validate().is_ok());
|
||
|
||
// All faces should reference valid vertices
|
||
let faces = mesh.get_faces();
|
||
|
||
for face in faces {
|
||
for &vertex_id in face.vertex_indices() {
|
||
// Each vertex should exist in the mesh
|
||
assert!(
|
||
mesh.has_node(vertex_id),
|
||
"Face references non-existent vertex {}",
|
||
vertex_id
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_3d_face_normal_computation() {
|
||
let mesh = StructuredMesh::new_3d(2, 2, 2, 1.0, 1.0, 1.0).unwrap();
|
||
|
||
let faces = mesh.get_faces();
|
||
|
||
for face in faces.iter().filter(|f| f.is_boundary()) {
|
||
let normal = face.normal();
|
||
|
||
// Normal should be unit vector
|
||
let magnitude = normal.magnitude();
|
||
assert!(
|
||
(magnitude - 1.0).abs() < 1e-6,
|
||
"Face normal magnitude should be 1, got {}",
|
||
magnitude
|
||
);
|
||
|
||
// For axis-aligned cube, normals should be along coordinate axes
|
||
let is_axis_aligned =
|
||
(normal.x.abs() - 1.0).abs() < 1e-6 && normal.y.abs() < 1e-6 && normal.z.abs() < 1e-6
|
||
|| (normal.y.abs() - 1.0).abs() < 1e-6
|
||
&& normal.x.abs() < 1e-6
|
||
&& normal.z.abs() < 1e-6
|
||
|| (normal.z.abs() - 1.0).abs() < 1e-6
|
||
&& normal.x.abs() < 1e-6
|
||
&& normal.y.abs() < 1e-6;
|
||
|
||
assert!(
|
||
is_axis_aligned,
|
||
"Boundary face normal should be axis-aligned, got {:?}",
|
||
normal
|
||
);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_internal_vs_boundary_faces() {
|
||
let mesh = StructuredMesh::new(4, 4, 2.0, 2.0).unwrap();
|
||
|
||
let faces = mesh.get_faces();
|
||
let mut internal_faces = 0;
|
||
let mut boundary_faces = 0;
|
||
|
||
for face in faces {
|
||
if face.is_boundary() {
|
||
boundary_faces += 1;
|
||
} else {
|
||
internal_faces += 1;
|
||
}
|
||
}
|
||
|
||
// Should have both internal and boundary faces
|
||
assert!(boundary_faces > 0, "Should have boundary faces");
|
||
assert!(internal_faces >= 0, "May have internal faces");
|
||
|
||
// Total should match statistics
|
||
let stats = mesh.statistics();
|
||
assert_eq!(boundary_faces, stats.boundary_faces);
|
||
assert_eq!(boundary_faces + internal_faces, stats.total_faces);
|
||
}
|
||
|
||
#[test]
|
||
fn test_face_area_consistency() {
|
||
let mesh = StructuredMesh::new(3, 3, 2.0, 2.0).unwrap();
|
||
|
||
let faces = mesh.get_faces();
|
||
|
||
// All faces in structured mesh should have the same area (grid spacing)
|
||
if let Some(first_face) = faces.first() {
|
||
let expected_area = first_face.area();
|
||
|
||
for face in faces {
|
||
let area = face.area();
|
||
assert!(
|
||
(area - expected_area).abs() < 1e-10,
|
||
"All faces in structured mesh should have same area"
|
||
);
|
||
}
|
||
}
|
||
}
|