//! TDD Tests for Triangle3 FiniteElement Implementation //! Following strict Red-Green-Refactor cycle //! No mocks, stubs, or TODOs - only full implementations #[cfg(test)] mod triangle3_finite_element_tests { use nalgebra::Vector3; use rtx_fea::elements::shape_functions::shape_2d::Triangle3; use rtx_fea::elements::{FiniteElement, NaturalCoords}; use rtx_fea::mesh::ElementType; #[test] fn test_triangle3_element_type() { // RED: Test that Triangle3 implements FiniteElement and returns correct type let tri3 = Triangle3::new(); // GREEN: Triangle3 should return ElementType::Tri3 assert_eq!(tri3.element_type(), ElementType::Tri3); } #[test] fn test_triangle3_num_nodes() { // RED: Test that Triangle3 correctly reports number of nodes let tri3 = Triangle3::new(); // GREEN: Triangle3 has 3 nodes assert_eq!(tri3.num_nodes(), 3); } #[test] fn test_triangle3_dimensions() { // RED: Test spatial and parametric dimensions let tri3 = Triangle3::new(); // GREEN: Triangle3 is 2D element with 2D parametric space assert_eq!(tri3.spatial_dimension(), 2); assert_eq!(tri3.parametric_dimension(), 2); } #[test] fn test_triangle3_shape_functions() { // RED: Test shape function evaluation at various points let tri3 = Triangle3::new(); // Test at corner nodes let coords_n1 = NaturalCoords::new_2d(0.0, 0.0); let shape_n1 = tri3.shape_functions(&coords_n1).unwrap(); assert!((shape_n1.value(0).unwrap() - 1.0).abs() < 1e-10); assert!((shape_n1.value(1).unwrap() - 0.0).abs() < 1e-10); assert!((shape_n1.value(2).unwrap() - 0.0).abs() < 1e-10); let coords_n2 = NaturalCoords::new_2d(1.0, 0.0); let shape_n2 = tri3.shape_functions(&coords_n2).unwrap(); assert!((shape_n2.value(0).unwrap() - 0.0).abs() < 1e-10); assert!((shape_n2.value(1).unwrap() - 1.0).abs() < 1e-10); assert!((shape_n2.value(2).unwrap() - 0.0).abs() < 1e-10); let coords_n3 = NaturalCoords::new_2d(0.0, 1.0); let shape_n3 = tri3.shape_functions(&coords_n3).unwrap(); assert!((shape_n3.value(0).unwrap() - 0.0).abs() < 1e-10); assert!((shape_n3.value(1).unwrap() - 0.0).abs() < 1e-10); assert!((shape_n3.value(2).unwrap() - 1.0).abs() < 1e-10); // Test at centroid (1/3, 1/3) let coords_center = NaturalCoords::new_2d(1.0 / 3.0, 1.0 / 3.0); let shape_center = tri3.shape_functions(&coords_center).unwrap(); assert!((shape_center.value(0).unwrap() - 1.0 / 3.0).abs() < 1e-10); assert!((shape_center.value(1).unwrap() - 1.0 / 3.0).abs() < 1e-10); assert!((shape_center.value(2).unwrap() - 1.0 / 3.0).abs() < 1e-10); // GREEN: Verify partition of unity let sum: f64 = (0..3).map(|i| shape_center.value(i).unwrap()).sum(); assert!((sum - 1.0).abs() < 1e-10); } #[test] fn test_triangle3_jacobian() { // RED: Test Jacobian computation let tri3 = Triangle3::new(); // Define a simple triangle in 2D let node_coords = vec![ Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0), ]; // Test Jacobian at centroid let coords = NaturalCoords::new_2d(1.0 / 3.0, 1.0 / 3.0); let jac = tri3.jacobian(&coords, &node_coords).unwrap(); // GREEN: For this simple triangle, Jacobian should be constant assert!(jac.determinant().abs() > 1e-10); // Non-singular assert_eq!(jac.jacobian.nrows(), 2); assert_eq!(jac.jacobian.ncols(), 2); } #[test] fn test_triangle3_quadrature() { // RED: Test quadrature rule generation let tri3 = Triangle3::new(); // GREEN: Get default quadrature rule let quad_rule = tri3.quadrature_rule(None).unwrap(); assert!(quad_rule.points.len() > 0); // Get higher order quadrature let quad_rule_3 = tri3.quadrature_rule(Some(3)).unwrap(); assert!(quad_rule_3.points.len() >= 3); // Verify weights sum to area of reference triangle (0.5) let weight_sum: f64 = quad_rule.points.iter().map(|p| p.weight).sum(); assert!((weight_sum - 0.5).abs() < 1e-10); } #[test] fn test_triangle3_coordinate_mapping() { // RED: Test mapping between natural and physical coordinates let tri3 = Triangle3::new(); let node_coords = vec![ Vector3::new(1.0, 1.0, 0.0), Vector3::new(3.0, 1.0, 0.0), Vector3::new(2.0, 3.0, 0.0), ]; // Map centroid from natural to physical let nat_coords = NaturalCoords::new_2d(1.0 / 3.0, 1.0 / 3.0); let phys_coords = tri3.map_to_physical(&nat_coords, &node_coords).unwrap(); // GREEN: Centroid should map to (2.0, 5.0/3.0, 0.0) assert!((phys_coords.coords.x - 2.0).abs() < 1e-10); assert!((phys_coords.coords.y - 5.0 / 3.0).abs() < 1e-10); assert!((phys_coords.coords.z - 0.0).abs() < 1e-10); } } #[cfg(test)] mod integration_tests { use nalgebra::Vector3; use rtx_fea::elements::shape_functions::shape_2d::Triangle3; use rtx_fea::elements::{FiniteElement, NaturalCoords}; #[test] fn test_triangle3_integration_accuracy() { // RED: Test numerical integration over element let tri3 = Triangle3::new(); let quad_rule = tri3.quadrature_rule(Some(2)).unwrap(); // Integrate constant function f=1 over reference triangle let mut integral = 0.0; for point in &quad_rule.points { let coords = NaturalCoords::new_2d(point.coords.xi(), point.coords.eta()); let _shape = tri3.shape_functions(&coords).unwrap(); integral += 1.0 * point.weight; } // GREEN: Integral of 1 over reference triangle should be 0.5 assert!((integral - 0.5).abs() < 1e-10); } #[test] fn test_triangle3_shape_derivatives_consistency() { // RED: Test that shape function derivatives are consistent let tri3 = Triangle3::new(); // Test at multiple points let test_points = vec![(0.2, 0.3), (0.5, 0.1), (0.1, 0.7)]; for (x, y) in test_points { let coords = NaturalCoords::new_2d(x, y); let shape_eval = tri3.shape_functions(&coords).unwrap(); // GREEN: Verify derivatives sum to zero (constant preservation) let mut sum_dxi = 0.0; let mut sum_deta = 0.0; for i in 0..3 { sum_dxi += shape_eval.derivative(i, 0).unwrap(); sum_deta += shape_eval.derivative(i, 1).unwrap(); } assert!(sum_dxi.abs() < 1e-10); assert!(sum_deta.abs() < 1e-10); } } }