Files
rustytorch/crates/specialized/rtx-cfd/tests/face_connectivity_tests.rs
T
2026-03-04 00:08:42 +00:00

266 lines
7.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
);
}
}
}