186 lines
6.8 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|