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

186 lines
6.8 KiB
Rust

//! 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);
}
}
}