Files
rustytorch/crates/specialized/rtx-fea/tests/standalone_test.rs
T
Omar SobhandClaude Opus 5 e30cfe4ce9
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
rtx-fea: repair the element library; the crate is now green with no quarantine
Follows the assembly repair. Takes rtx-fea from 21 failures to 253 passing,
0 failing, 0 ignored, with every `#[ignore]` marker gone.

Shape function bugs, all found by one new test asserting two invariants
across the whole element library at once -- partition of unity, and that
the hand-written derivatives sum to zero. The second is the one that gets
skipped, and it is what caught Hexahedron20.

  - Wedge15 summed to 2 at mid-height. Adding a node on a vertical edge
    contributes L_i (1 - t^2) to the sum, so the two corners sharing that
    edge must each give up half of it; the correction was absent. A
    quadratic wedge that doubles every field interpolated through it.

  - Hexahedron20 had sign errors in four hand-written corner
    derivatives -- nodes 3 and 7 in dN/dr, nodes 1 and 5 in dN/ds. The
    values were correct, so partition of unity passed; only the
    derivative-sum invariant exposed it. The strain computed from this
    element was wrong while its interpolation looked right.

  - Quadrilateral9 emitted its shape functions in raw lexicographic
    lattice order while Quad4 and Quad8 use the standard finite-element
    order. A mesh written the usual way paired each node with the wrong
    basis function, which at the element centre made the Jacobian exactly
    singular.

  - Pyramid13 was not a quadratic pyramid basis: it summed to 4 at the
    element centre, and its `derivatives` allocated a 13x3 matrix then
    wrote rows 13 through 15, having been copied from a sixteen-node
    layout, so it panicked before the wrong values could be used. A
    correct 13-node basis is rational, and there is no pyramid quadrature
    rule to integrate it with, so implementing the basis alone would not
    make the element usable. Both now report the gap explicitly rather
    than panicking. Pyramid5 is unaffected and works.

Fixtures corrected rather than tolerances loosened:

  - von Mises stress of an equal biaxial state expected 0, commented "no
    deviatoric stress". Only a hydrostatic state has that. The correct
    value is 100, and expecting 0 would mean a biaxially loaded sheet
    could never yield. The unequal case expected |100-50|; the von Mises
    stress is not a principal difference.
  - A 3-point Gauss rule was required to integrate sin to 1e-10. No
    correct implementation can. Replaced with a convergence assertion,
    which a wrong rule cannot satisfy by luck.
  - MathUtils::SMALL was asserted below EPSILON * 1000, which inverts the
    relationship a practical zero-threshold needs.
  - The Hex20 Jacobian test put all twelve mid-edge nodes at the origin,
    commented "simplified for test". That is not a hexahedron, and its
    mapping is genuinely singular; it only passed because of the
    derivative sign errors above.
  - ElementFactory was required to build every ElementType including
    Point, which has no interpolation and is deliberately rejected.

MemoryInfo displayed decimal GB while its own test constructed binary
GiB, rendering an 8 GiB device as 8.59. Now GiB throughout.

test_mesh_has_real_algorithms searched the *text* of mesh/mod.rs for the
strings "add_node" and "add_element". It broke when those moved into
submodules, but the real problem is that a source-text search cannot tell
a working function from one returning zeros -- it passed throughout the
period when element matrices were a stub and quadrature returned no
points. Replaced with a test that builds a mesh and checks the result.

The crate doc example imported solvers::DirectSolver and
analysis::StaticAnalysis, neither of which has ever existed, so the
doctest never compiled. Replaced with a modal analysis that runs. Also
dropped the "Production Ready: No mocks, stubs, or TODOs - complete
implementation" line, and replaced it with what is actually validated and
what is not.

rtx-fsi unaffected at 26/26.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-19 08:21:58 -07:00

190 lines
6.8 KiB
Rust

// Standalone test for rtx-fea functionality
// This verifies the TDD implementation without depending on other crates
#[cfg(test)]
mod standalone_tests {
// Test that our implementation compiles and has real algorithms
#[test]
fn test_element_kernels_are_real() {
// Verify element kernels compute real stiffness matrices
let kernel_path = include_str!("../src/kernels/element_kernels.rs");
// Check that we're NOT using thread::sleep (mock implementation)
assert!(
!kernel_path.contains("thread::sleep"),
"Found mock thread::sleep - implementation should be real!"
);
// Check that we have real mathematical computations
assert!(
kernel_path.contains("compute_b_matrix"),
"Missing B matrix computation"
);
assert!(
kernel_path.contains("jacobian"),
"Missing Jacobian computation"
);
assert!(
kernel_path.contains("gauss_points"),
"Missing Gauss quadrature"
);
}
/// Exercise the mesh operations rather than grepping for their names.
///
/// This previously searched the text of `src/mesh/mod.rs` for the strings
/// "add_node", "add_element" and "generate_rectangle". It broke when the
/// implementations moved into submodules, which is the smaller problem:
/// the larger one is that a source-text search cannot distinguish a
/// working function from one that returns zeros. Every such check in this
/// file passed for the entire period during which element matrix
/// computation was a stub returning `DMatrix::zeros`, quadrature returned
/// no points at all, and cloning the material database silently dropped
/// every material.
#[test]
fn test_mesh_has_real_algorithms() {
use rtx_fea::mesh::{Element, ElementType, MaterialId, Mesh, Node};
let mut mesh = Mesh::new(2).unwrap();
let n0 = mesh.add_node(Node::new_2d(0.0, 0.0));
let n1 = mesh.add_node(Node::new_2d(1.0, 0.0));
let n2 = mesh.add_node(Node::new_2d(1.0, 1.0));
let n3 = mesh.add_node(Node::new_2d(0.0, 1.0));
assert_eq!(mesh.num_nodes(), 4);
mesh.add_element(
Element::new(ElementType::Quad4, vec![n0, n1, n2, n3], MaterialId(0)).unwrap(),
)
.unwrap();
assert_eq!(mesh.num_elements(), 1);
// Nodes come back at the coordinates they went in at.
let position = mesh.get_node(n2).expect("node 2 should exist").position();
assert!((position.x - 1.0).abs() < 1e-12);
assert!((position.y - 1.0).abs() < 1e-12);
// `generate_rectangle` takes node counts per direction, so a 3 x 3
// grid of nodes yields 9 nodes and 2 x 2 = 4 quadrilaterals.
let generated = Mesh::generate_rectangle(1.0, 1.0, 3, 3).unwrap();
assert_eq!(generated.num_nodes(), 9);
assert_eq!(generated.num_elements(), 4);
}
#[test]
fn test_no_todos_or_unimplemented() {
// Scan key files for TODOs or unimplemented
let files = [
include_str!("../src/kernels/element_kernels.rs"),
include_str!("../src/mesh/mod.rs"),
include_str!("../src/elements/mod.rs"),
include_str!("../src/materials/mod.rs"),
include_str!("../src/assembly/mod.rs"),
include_str!("../src/boundary/mod.rs"),
include_str!("../src/solvers/mod.rs"),
include_str!("../src/analysis/mod.rs"),
];
for (i, file) in files.iter().enumerate() {
assert!(
!file.contains("todo!()"),
"Found todo!() macro in file {}",
i
);
assert!(
!file.contains("unimplemented!()"),
"Found unimplemented!() macro in file {}",
i
);
assert!(
!file.contains("// TODO"),
"Found TODO comment in file {}",
i
);
}
}
#[test]
fn test_error_handling_complete() {
// Verify comprehensive error handling
let error_path = include_str!("../src/error.rs");
// Check for all required error types
assert!(error_path.contains("MeshError"), "Missing MeshError");
assert!(error_path.contains("ElementError"), "Missing ElementError");
assert!(
error_path.contains("MaterialError"),
"Missing MaterialError"
);
assert!(
error_path.contains("AssemblyError"),
"Missing AssemblyError"
);
assert!(error_path.contains("SolverError"), "Missing SolverError");
assert!(
error_path.contains("BoundaryError"),
"Missing BoundaryError"
);
assert!(
error_path.contains("AnalysisError"),
"Missing AnalysisError"
);
assert!(error_path.contains("KernelError"), "Missing KernelError");
}
#[test]
fn test_mathematical_accuracy() {
// Test shape functions for a unit square element
// This verifies real mathematical implementation
// Shape functions at corner nodes should be 1 at that node, 0 at others
let xi: f64 = -1.0;
let eta: f64 = -1.0;
let n1: f64 = 0.25 * (1.0 - xi) * (1.0 - eta);
assert!(
(n1 - 1.0).abs() < 1e-10,
"Shape function N1 incorrect at node 1"
);
let xi: f64 = 1.0;
let eta: f64 = -1.0;
let n2: f64 = 0.25 * (1.0 + xi) * (1.0 - eta);
assert!(
(n2 - 1.0).abs() < 1e-10,
"Shape function N2 incorrect at node 2"
);
// Shape functions should sum to 1 (partition of unity)
let xi: f64 = 0.0;
let eta: f64 = 0.0;
let n1: f64 = 0.25 * (1.0 - xi) * (1.0 - eta);
let n2: f64 = 0.25 * (1.0 + xi) * (1.0 - eta);
let n3: f64 = 0.25 * (1.0 + xi) * (1.0 + eta);
let n4: f64 = 0.25 * (1.0 - xi) * (1.0 + eta);
let sum: f64 = n1 + n2 + n3 + n4;
assert!((sum - 1.0).abs() < 1e-10, "Shape functions don't sum to 1");
}
#[test]
fn test_constitutive_matrix_symmetry() {
// Test that material stiffness matrix is symmetric (real implementation)
let e: f64 = 200e9; // Young's modulus (Pa)
let nu: f64 = 0.3; // Poisson's ratio
// Plane stress constitutive matrix
let factor: f64 = e / (1.0 - nu * nu);
let d11: f64 = factor;
let d12: f64 = factor * nu;
let d33: f64 = factor * (1.0 - nu) / 2.0;
// Check symmetry
assert!((d12 - d12).abs() < 1e-10, "D matrix not symmetric");
// Check positive definiteness (all diagonal terms positive)
assert!(d11 > 0.0, "D11 not positive");
assert!(d11 > 0.0, "D22 not positive");
assert!(d33 > 0.0, "D33 not positive");
}
}