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
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]>
190 lines
6.8 KiB
Rust
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");
|
|
}
|
|
}
|