//! TDD Tests for Hexahedron FiniteElement Implementations //! Following strict Red-Green-Refactor cycle //! No mocks, stubs, or TODOs - only full implementations #[cfg(test)] mod hexahedron20_tests { use nalgebra::Vector3; use rtx_fea::elements::shape_functions::shape_3d::Hexahedron20; use rtx_fea::elements::{FiniteElement, NaturalCoords}; use rtx_fea::mesh::ElementType; #[test] fn test_hex20_element_type() { // RED: Test that Hexahedron20 implements FiniteElement and returns correct type let hex20 = Hexahedron20::new(); // GREEN: Hexahedron20 should return ElementType::Hex20 assert_eq!(hex20.element_type(), ElementType::Hex20); } #[test] fn test_hex20_num_nodes() { // RED: Test that Hexahedron20 correctly reports number of nodes let hex20 = Hexahedron20::new(); // GREEN: Hexahedron20 has 20 nodes (8 corners + 12 mid-edges) assert_eq!(hex20.num_nodes(), 20); } #[test] fn test_hex20_dimensions() { // RED: Test spatial and parametric dimensions let hex20 = Hexahedron20::new(); // GREEN: Hexahedron20 is 3D element with 3D parametric space assert_eq!(hex20.spatial_dimension(), 3); assert_eq!(hex20.parametric_dimension(), 3); } #[test] fn test_hex20_shape_functions_at_corner() { // RED: Test shape function evaluation at corner node let hex20 = Hexahedron20::new(); // Test at corner (−1,−1,−1) - should be 1 at node 0, 0 at others let coords = NaturalCoords::new_3d(-1.0, -1.0, -1.0); let shape = hex20.shape_functions(&coords).unwrap(); // GREEN: Verify shape function properties assert!((shape.value(0).unwrap() - 1.0).abs() < 1e-10); for i in 1..20 { assert!(shape.value(i).unwrap().abs() < 1e-10); } } #[test] fn test_hex20_jacobian() { // RED: Test Jacobian computation let hex20 = Hexahedron20::new(); // Create a regular hex with side length 2 let mut node_coords = Vec::new(); // Corner nodes (8) for k in 0..2 { for j in 0..2 { for i in 0..2 { node_coords.push(Vector3::new( 2.0 * i as f64 - 1.0, 2.0 * j as f64 - 1.0, 2.0 * k as f64 - 1.0, )); } } } // Mid-edge nodes (12) - simplified for test for _ in 8..20 { node_coords.push(Vector3::new(0.0, 0.0, 0.0)); } let coords = NaturalCoords::new_3d(0.0, 0.0, 0.0); let jac = hex20.jacobian(&coords, &node_coords).unwrap(); // GREEN: For regular hex, Jacobian at center should be diagonal assert!(jac.determinant().abs() > 1e-10); // Non-singular } #[test] fn test_hex20_quadrature() { // RED: Test quadrature rule generation let hex20 = Hexahedron20::new(); // GREEN: Get default quadrature rule let quad_rule = hex20.quadrature_rule(None).unwrap(); assert!(quad_rule.points.len() > 0); // For order 3, should have at least 8 points (2x2x2) let quad_rule_3 = hex20.quadrature_rule(Some(3)).unwrap(); assert!(quad_rule_3.points.len() >= 8); } } #[cfg(test)] mod hexahedron27_tests { use nalgebra::Vector3; use rtx_fea::elements::shape_functions::shape_3d::Hexahedron27; use rtx_fea::elements::{FiniteElement, NaturalCoords}; use rtx_fea::mesh::ElementType; #[test] fn test_hex27_element_type() { // RED: Test that Hexahedron27 implements FiniteElement let hex27 = Hexahedron27::new(); // GREEN: Hexahedron27 should return ElementType::Hex27 assert_eq!(hex27.element_type(), ElementType::Hex27); } #[test] fn test_hex27_num_nodes() { // RED: Test node count let hex27 = Hexahedron27::new(); // GREEN: Hexahedron27 has 27 nodes (8 corners + 12 mid-edges + 6 face-centers + 1 center) assert_eq!(hex27.num_nodes(), 27); } #[test] fn test_hex27_partition_of_unity() { // RED: Test that shape functions sum to 1 everywhere let hex27 = Hexahedron27::new(); let test_points = vec![ (0.0, 0.0, 0.0), // Center (0.5, 0.5, 0.5), // Random point (-0.5, 0.3, -0.7), // Another random point ]; for (x, y, z) in test_points { let coords = NaturalCoords::new_3d(x, y, z); let shape = hex27.shape_functions(&coords).unwrap(); // GREEN: Sum of all shape functions should be 1 let sum: f64 = (0..27).map(|i| shape.value(i).unwrap()).sum(); assert!((sum - 1.0).abs() < 1e-10); } } } #[cfg(test)] mod integration_tests { use rtx_fea::elements::shape_functions::shape_3d::{Hexahedron20, Hexahedron27}; use rtx_fea::elements::{FiniteElement, NaturalCoords}; #[test] fn test_hex20_numerical_integration() { // RED: Test numerical integration over element let hex20 = Hexahedron20::new(); let quad_rule = hex20.quadrature_rule(Some(3)).unwrap(); // Integrate constant function f=1 over reference element [-1,1]^3 let mut integral = 0.0; for point in &quad_rule.points { let coords = NaturalCoords::new_3d(point.coords.xi(), point.coords.eta(), point.coords.zeta()); let _shape = hex20.shape_functions(&coords).unwrap(); integral += 1.0 * point.weight; } // GREEN: Volume of reference hex should be 8 assert!((integral - 8.0).abs() < 1e-10); } #[test] fn test_hex27_derivative_consistency() { // RED: Test that shape function derivatives are consistent let hex27 = Hexahedron27::new(); let test_points = vec![(0.1, 0.2, 0.3), (-0.3, 0.4, -0.2)]; for (x, y, z) in test_points { let coords = NaturalCoords::new_3d(x, y, z); let shape_eval = hex27.shape_functions(&coords).unwrap(); // GREEN: Sum of derivatives should be zero (constant preservation) let mut sum_dxi = 0.0; let mut sum_deta = 0.0; let mut sum_dzeta = 0.0; for i in 0..27 { sum_dxi += shape_eval.derivative(i, 0).unwrap(); sum_deta += shape_eval.derivative(i, 1).unwrap(); sum_dzeta += shape_eval.derivative(i, 2).unwrap(); } assert!(sum_dxi.abs() < 1e-10); assert!(sum_deta.abs() < 1e-10); assert!(sum_dzeta.abs() < 1e-10); } } }