// TDD: RED phase - Tests for mesh quality metrics use nalgebra::Vector3; use rtx_cfd::mesh::Mesh; use rtx_cfd::mesh::entities::{Cell, Face, Node}; use rtx_cfd::mesh::structured::StructuredMesh; use rtx_cfd::mesh::unstructured::UnstructuredMesh; use rtx_cfd::traits::MeshEntity; #[test] fn test_triangle_aspect_ratio() { let mut mesh = UnstructuredMesh::new(); // Test equilateral triangle (ideal aspect ratio = 1.0) 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, 0.866, 0.0)).unwrap(); // sqrt(3)/2 ≈ 0.866 let cell_id = mesh.add_triangle_cell(n1, n2, n3).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let aspect_ratio = cell.compute_aspect_ratio(&nodes).unwrap(); // Equilateral triangle should have aspect ratio close to 1.0 assert!( (aspect_ratio - 1.0).abs() < 0.1, "Equilateral triangle aspect ratio: {}", aspect_ratio ); } #[test] fn test_degenerate_triangle_aspect_ratio() { let mut mesh = UnstructuredMesh::new(); // Test degenerate triangle (very long and thin) let n1 = mesh.add_node(Vector3::new(0.0, 0.0, 0.0)).unwrap(); let n2 = mesh.add_node(Vector3::new(10.0, 0.0, 0.0)).unwrap(); let n3 = mesh.add_node(Vector3::new(5.0, 0.1, 0.0)).unwrap(); // Very thin let cell_id = mesh.add_triangle_cell(n1, n2, n3).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let aspect_ratio = cell.compute_aspect_ratio(&nodes).unwrap(); // Should have high aspect ratio (bad quality) assert!( aspect_ratio > 10.0, "Degenerate triangle aspect ratio: {}", aspect_ratio ); } #[test] fn test_quadrilateral_aspect_ratio() { let mut mesh = UnstructuredMesh::new(); // Test square (ideal aspect ratio = 1.0) 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(1.0, 1.0, 0.0)).unwrap(); let n4 = mesh.add_node(Vector3::new(0.0, 1.0, 0.0)).unwrap(); let cell_id = mesh.add_quadrilateral_cell(n1, n2, n3, n4).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let aspect_ratio = cell.compute_aspect_ratio(&nodes).unwrap(); // Square should have aspect ratio close to 1.0 assert!( (aspect_ratio - 1.0).abs() < 0.1, "Square aspect ratio: {}", aspect_ratio ); } #[test] fn test_rectangle_aspect_ratio() { let mut mesh = UnstructuredMesh::new(); // Test rectangle (2:1 ratio) let n1 = mesh.add_node(Vector3::new(0.0, 0.0, 0.0)).unwrap(); let n2 = mesh.add_node(Vector3::new(2.0, 0.0, 0.0)).unwrap(); let n3 = mesh.add_node(Vector3::new(2.0, 1.0, 0.0)).unwrap(); let n4 = mesh.add_node(Vector3::new(0.0, 1.0, 0.0)).unwrap(); let cell_id = mesh.add_quadrilateral_cell(n1, n2, n3, n4).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let aspect_ratio = cell.compute_aspect_ratio(&nodes).unwrap(); // Rectangle should have aspect ratio of 2.0 assert!( (aspect_ratio - 2.0).abs() < 0.1, "Rectangle aspect ratio: {}", aspect_ratio ); } #[test] fn test_triangle_skewness_ideal() { let mut mesh = UnstructuredMesh::new(); // Test equilateral triangle (ideal skewness = 0.0) 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, 0.866, 0.0)).unwrap(); let cell_id = mesh.add_triangle_cell(n1, n2, n3).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let skewness = cell.compute_skewness(&nodes).unwrap(); // Equilateral triangle should have skewness close to 0.0 assert!( skewness < 0.1, "Equilateral triangle skewness: {}", skewness ); } #[test] fn test_triangle_skewness_degenerate() { let mut mesh = UnstructuredMesh::new(); // Test triangle with very acute angle (high skewness) 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.99, 0.01, 0.0)).unwrap(); // Very acute angle let cell_id = mesh.add_triangle_cell(n1, n2, n3).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let skewness = cell.compute_skewness(&nodes).unwrap(); // Should have high skewness (bad quality) assert!(skewness > 0.8, "Acute triangle skewness: {}", skewness); } #[test] fn test_quadrilateral_skewness() { let mut mesh = UnstructuredMesh::new(); // Test square (ideal skewness = 0.0) 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(1.0, 1.0, 0.0)).unwrap(); let n4 = mesh.add_node(Vector3::new(0.0, 1.0, 0.0)).unwrap(); let cell_id = mesh.add_quadrilateral_cell(n1, n2, n3, n4).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let skewness = cell.compute_skewness(&nodes).unwrap(); // Square should have low skewness assert!(skewness < 0.1, "Square skewness: {}", skewness); } #[test] fn test_parallelogram_skewness() { let mut mesh = UnstructuredMesh::new(); // Test skewed parallelogram 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(1.5, 1.0, 0.0)).unwrap(); // Skewed let n4 = mesh.add_node(Vector3::new(0.5, 1.0, 0.0)).unwrap(); // Skewed let cell_id = mesh.add_quadrilateral_cell(n1, n2, n3, n4).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let skewness = cell.compute_skewness(&nodes).unwrap(); // Should have moderate skewness assert!( skewness > 0.2 && skewness < 0.8, "Parallelogram skewness: {}", skewness ); } #[test] fn test_tetrahedron_aspect_ratio() { let mut mesh = UnstructuredMesh::new(); // Test regular tetrahedron (ideal aspect ratio ≈ 1.0) let h = (2.0 / 3.0_f64).sqrt(); // Height of regular tetrahedron with unit 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, 0.866, 0.0)).unwrap(); let n4 = mesh.add_node(Vector3::new(0.5, 0.289, h)).unwrap(); // Apex let cell_id = mesh.add_tetrahedron_cell(n1, n2, n3, n4).unwrap(); let cell = mesh.get_cell_by_id(cell_id).unwrap(); let nodes = mesh.get_cell_nodes(cell_id).unwrap(); let aspect_ratio = cell.compute_aspect_ratio(&nodes).unwrap(); // Regular tetrahedron should have good aspect ratio assert!( aspect_ratio < 2.0, "Regular tetrahedron aspect ratio: {}", aspect_ratio ); } #[test] fn test_structured_mesh_quality() { let mesh = StructuredMesh::new(5, 5, 2.0, 2.0).unwrap(); // All cells in a structured mesh should have identical quality let stats = mesh.statistics(); // Should have uniform cell volumes assert!((stats.max_cell_volume - stats.min_cell_volume).abs() < 1e-10); // `StructuredMesh::new` takes node counts per direction — `dx` is // `width / (nx - 1)` — so a 5x5 node grid over a 2.0 x 2.0 domain has // 4x4 cells of 0.5 x 0.5, giving 0.25 each. // // The assertion previously read 0.16, which is 4/25 and assumes 5x5 // *cells*, while the comment beside it computed 0.25 from the node // convention. The two disagreed with each other. assert!((stats.average_cell_volume - 0.25).abs() < 1e-10); // Should have good aspect ratio for square domain assert!((stats.aspect_ratio - 1.0).abs() < 0.1); } #[test] fn test_mesh_quality_histogram() { let mut mesh = UnstructuredMesh::new(); // Create triangles with various qualities // Good 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, 0.866, 0.0)).unwrap(); mesh.add_triangle_cell(n1, n2, n3).unwrap(); // Bad triangle let n4 = mesh.add_node(Vector3::new(2.0, 0.0, 0.0)).unwrap(); let n5 = mesh.add_node(Vector3::new(12.0, 0.0, 0.0)).unwrap(); let n6 = mesh.add_node(Vector3::new(7.0, 0.1, 0.0)).unwrap(); mesh.add_triangle_cell(n4, n5, n6).unwrap(); let stats = mesh.statistics(); let quality_histogram = mesh.compute_quality_histogram(10).unwrap(); // Should have cells in different quality bins let total_cells: usize = quality_histogram.iter().sum(); assert_eq!(total_cells, 2); // At least one cell should be in high quality bin, one in low quality bin assert!(quality_histogram[9] > 0 || quality_histogram[8] > 0); // High quality bins assert!(quality_histogram[0] > 0 || quality_histogram[1] > 0); // Low quality bins } #[test] fn test_orthogonality_calculation() { let mesh = StructuredMesh::new(3, 3, 1.0, 1.0).unwrap(); // Structured mesh should have perfect orthogonality let stats = mesh.statistics(); assert!( stats.orthogonality > 0.99, "Structured mesh orthogonality: {}", stats.orthogonality ); assert!( stats.non_orthogonality < 0.01, "Structured mesh non-orthogonality: {}", stats.non_orthogonality ); }