// TDD: RED phase - Tests for mesh refinement algorithms use nalgebra::Vector3; use rtx_cfd::mesh::Mesh; use rtx_cfd::mesh::refinement::{AdaptiveRefinement, RefinementCriteria, RefinementStrategy}; use rtx_cfd::mesh::structured::StructuredMesh; use rtx_cfd::mesh::unstructured::UnstructuredMesh; #[test] fn test_adaptive_refinement_criteria_validation() { let criteria = RefinementCriteria { max_error: 1e-3, min_cell_size: 1e-6, max_cell_size: 1e6, max_levels: 10, }; let refinement = AdaptiveRefinement::new(criteria); // Test refinement decision logic assert!(refinement.needs_refinement(1e-2, 1e-3, 5)); // High error, reasonable size, low level assert!(!refinement.needs_refinement(1e-4, 1e-3, 5)); // Low error assert!(!refinement.needs_refinement(1e-2, 1e-7, 5)); // Too small cell assert!(!refinement.needs_refinement(1e-2, 1e-3, 15)); // Too many levels } #[test] fn test_error_based_cell_marking() { let criteria = RefinementCriteria::default(); let refinement = AdaptiveRefinement::new(criteria); // Test with various error distributions let errors = vec![1e-2, 1e-4, 5e-3, 1e-5, 2e-3, 1e-6]; let cell_sizes = vec![1e-3, 1e-3, 1e-3, 1e-3, 1e-3, 1e-3]; let levels = vec![2, 2, 2, 2, 2, 2]; let marked_cells = refinement .mark_cells_for_refinement(&errors, &cell_sizes, &levels) .unwrap(); // Should mark cells 0, 2, 4 (indices with errors > 1e-3) let expected_marked: Vec = vec![0, 2, 4]; assert_eq!(marked_cells, expected_marked); } #[test] fn test_gradient_based_error_indicator() { let criteria = RefinementCriteria::default(); let refinement = AdaptiveRefinement::new(criteria); // Create a simple 2D velocity field with gradients let velocity_field = vec![ Vector3::new(0.0, 0.0, 0.0), // Cell 0: no gradient Vector3::new(1.0, 0.0, 0.0), // Cell 1: moderate gradient Vector3::new(2.0, 1.0, 0.0), // Cell 2: high gradient Vector3::new(0.1, 0.1, 0.0), // Cell 3: low gradient ]; let errors = refinement .compute_gradient_error_indicator(&velocity_field) .unwrap(); // Cell 2 should have highest error (highest velocity magnitude) assert!(errors[2] > errors[1]); assert!(errors[1] > errors[0]); assert!(errors[1] > errors[3]); } #[test] fn test_residual_based_error_indicator() { let criteria = RefinementCriteria::default(); let refinement = AdaptiveRefinement::new(criteria); // Mock residuals for momentum and continuity equations let momentum_residuals = vec![1e-3, 1e-2, 5e-3, 1e-4]; let continuity_residuals = vec![1e-4, 1e-3, 2e-3, 1e-5]; let errors = refinement .compute_residual_error_indicator(&momentum_residuals, &continuity_residuals) .unwrap(); // Cell 1 should have highest combined residual assert!(errors[1] > errors[2]); assert!(errors[2] > errors[0]); assert!(errors[0] > errors[3]); } #[test] fn test_structured_mesh_refinement() { let mut mesh = StructuredMesh::new(3, 3, 1.0, 1.0).unwrap(); let initial_cell_count = mesh.cell_count(); // Test uniform refinement mesh.refine().unwrap(); // Should quadruple the number of cells in 2D assert_eq!(mesh.cell_count(), initial_cell_count * 4); // Grid spacing should be halved assert!((mesh.dx() - 0.25).abs() < 1e-10); assert!((mesh.dy() - 0.25).abs() < 1e-10); } #[test] fn test_unstructured_mesh_cell_subdivision() { 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_id = mesh.add_triangle_cell(n1, n2, n3).unwrap(); let initial_cell_count = mesh.cell_count(); // Mark this cell for refinement and subdivide let cells_to_refine = vec![cell_id]; mesh.refine_cells(&cells_to_refine).unwrap(); // Triangle subdivision should create 4 triangles assert_eq!(mesh.cell_count(), initial_cell_count + 3); // 1 original -> 4 total, so +3 } #[test] fn test_hanging_node_consistency() { let mut mesh = UnstructuredMesh::new(); // Create two adjacent triangles 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(); // Refine only the first cell mesh.refine_cells(&vec![cell1]).unwrap(); // Should handle hanging nodes correctly assert!(mesh.validate().is_ok()); } #[test] fn test_adaptive_refinement_quality_metrics() { let mut mesh = StructuredMesh::new(4, 4, 2.0, 2.0).unwrap(); // Get initial quality metrics let initial_stats = mesh.statistics(); let initial_aspect_ratio = initial_stats.aspect_ratio; // Refine mesh mesh.refine().unwrap(); // Quality should be maintained or improved let refined_stats = mesh.statistics(); assert!(refined_stats.aspect_ratio <= initial_aspect_ratio * 1.1); // Allow small degradation } #[test] fn test_refinement_level_tracking() { let criteria = RefinementCriteria { max_levels: 3, ..Default::default() }; let refinement = AdaptiveRefinement::new(criteria); // Test level enforcement let errors = vec![1e-2; 10]; // All high error let cell_sizes = vec![1e-3; 10]; // All reasonable size let levels = vec![0, 1, 2, 3, 4, 0, 1, 2, 3, 4]; // Mixed levels let marked_cells = refinement .mark_cells_for_refinement(&errors, &cell_sizes, &levels) .unwrap(); // Should not mark cells with level >= max_levels (indices 3, 4, 8, 9) assert!(!marked_cells.contains(&3)); assert!(!marked_cells.contains(&4)); assert!(!marked_cells.contains(&8)); assert!(!marked_cells.contains(&9)); }