Files
rustytorch/crates/specialized/rtx-fea/src/lib.rs
T
Omar SobhandClaude Fable 5 8071d5888d
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
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
rtx-fea: ECSW model-order reduction — POD-Galerkin plus hyper-reduction, verified end to end
The third Farhat gap. New rtx_fea::mor module:

- pod::pod_basis — orthonormal SVD basis with an energy-criterion
  truncation. Verified: rank-2 data yields exactly 2 orthonormal modes that
  reconstruct every snapshot to machine precision; a loose tolerance
  truncates a dominant-mode-plus-noise set to one mode.
- nnls — Lawson-Hanson non-negative least squares with the early stop that
  makes ECSW work: iteration ends at the requested residual, and the
  active-set structure caps the support at one column per outer iteration,
  so sparsity falls out of the stopping tolerance. Verified against KKT
  conditions, exact positive solutions, negative-clipping, and a
  sparsity-vs-tolerance case. Its thresholds are RELATIVE to the problem's
  own scales — the first version used absolute cutoffs (1e-14) that
  silently ended the iteration on ECSW's small-magnitude training systems
  at 1.2e-3 instead of the requested 1e-4.
- ecsw::train_ecsw — element weights such that a small subset reproduces
  the reduced internal force (the virtual work against the basis) over the
  training snapshots. w = 1 solves the system exactly by construction, so
  it is always consistent; nonnegativity is what keeps a sampled element
  from producing energy.
- reduced::ReducedNonlinearModel — Newton in POD coordinates, assembling
  either every element (POD-Galerkin) or the ECSW sample, on the same
  per-element force/tangent machinery the nonlinear analysis uses.

End-to-end verification (tests/ecsw_mor.rs): a clamped nonlinear block,
snapshots from a 4-point load sweep, evaluated at an UNSEEN load factor:

    POD modes: 2         ECSW sample: 5 of 24 elements
    training residual 2.2e-7 (requested 1e-4)
    error vs full solve: POD-Galerkin 3.09e-7, ECSW 3.08e-7
    hyper-reduction cost (ECSW vs full ROM): 1.6e-8

And the assertion with the most teeth: the same 5 elements with their
weights forced to 1 read a relative error of 1.22 — a completely wrong
field — so the accuracy is carried by the WEIGHTS, not by the subset
happening to be representative.

Scope, stated plainly: geometrically linear, materially nonlinear,
homogeneous Dirichlet only (no lifting); the basis lives on the free DOFs.

559 rtx-fea tests, 0 failing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-20 00:43:32 -07:00

310 lines
11 KiB
Rust

// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! # RTX-FEA: GPU-Accelerated Finite Element Analysis
//!
//! A production-ready finite element analysis library with comprehensive GPU acceleration
//! using CUDA. This crate provides all the essential components for modern FEA simulations.
//!
//! ## Features
//!
//! - **GPU-Accelerated**: Leverages cudarc 0.17.3 for CUDA operations
//! - **Comprehensive Elements**: Support for Tri3/6, Quad4/8/9, Tet4/10, Hex8/20/27, and more
//! - **Advanced Materials**: Linear elastic, hyperelastic, and plasticity models
//! - **Efficient Assembly**: Sparse matrix assembly with GPU optimization
//! - **Robust Solvers**: Direct and iterative solvers with GPU acceleration
//! - **Mesh Management**: Advanced mesh operations including refinement and partitioning
//!
//! ## Maturity
//!
//! Validated: element stiffness and mass matrices, shape functions across the
//! element library, global assembly, constraint handling, the generalized
//! eigensolver, and modal analysis end to end against closed-form bar
//! frequencies. See `tests/element_matrices_physical.rs`,
//! `tests/shape_function_invariants.rs`, `tests/eigenvalue_closed_form.rs`
//! and `tests/modal_closed_form.rs`.
//!
//! Validated since: manufactured solutions across the element library and
//! the nonlinear path — `NonlinearStaticAnalysis` runs full Newton on the
//! consistent tangent (orders 1.76/1.95 by MMS) and [`mor`] provides
//! POD-Galerkin reduction with ECSW hyper-reduction on top of it.
//!
//! Not yet: `Pyramid13` and pyramid quadrature are unimplemented and report
//! so; plane-stress condensation of nonlinear materials refuses explicitly.
//! The only test modules still behind `#[cfg(disabled)]` are the GPU solver
//! tests, which need CUDA hardware — everything else is enabled, with
//! fixtures corrected where they encoded abandoned designs.
//!
//! ## Quick Start
//!
//! Natural frequencies of a fixed-free bar:
//!
//! ```rust
//! use rtx_fea::analysis::{Analysis, AnalysisConfig, AnalysisData, ModalAnalysis};
//! use rtx_fea::assembly::DofComponent;
//! use rtx_fea::boundary::{BoundaryCondition, BoundaryConditionSet, DirichletBC};
//! use rtx_fea::materials::{LinearElastic, MaterialDatabase};
//! use rtx_fea::mesh::{Element, ElementType, MaterialId, Mesh, Node};
//!
//! // A 4 x 1 grid of quadrilaterals spanning a 1.0 x 0.05 strip.
//! let mut mesh = Mesh::new(2)?;
//! let mut columns = Vec::new();
//! for i in 0..=4 {
//! let x = f64::from(i) * 0.25;
//! columns.push([
//! mesh.add_node(Node::new_2d(x, 0.0)),
//! mesh.add_node(Node::new_2d(x, 0.05)),
//! ]);
//! }
//! for i in 0..4 {
//! let nodes = vec![columns[i][0], columns[i + 1][0], columns[i + 1][1], columns[i][1]];
//! mesh.add_element(Element::new(ElementType::Quad4, nodes, MaterialId(0))?)?;
//! }
//!
//! let mut materials = MaterialDatabase::new();
//! materials.add_material(MaterialId(0), LinearElastic::new(200e9, 0.3).with_density(8000.0), None);
//!
//! // Clamp the left edge. Without constraints the stiffness matrix is
//! // singular and the analysis reports that rather than returning noise.
//! let mut boundary_conditions = BoundaryConditionSet::new();
//! boundary_conditions.add_condition(BoundaryCondition::Dirichlet(DirichletBC::fixed(
//! columns[0].to_vec(),
//! vec![DofComponent::DisplacementX, DofComponent::DisplacementY],
//! 0.0,
//! )));
//!
//! let mut analysis = ModalAnalysis::new(mesh, materials, 3, AnalysisConfig::default())
//! .with_boundary_conditions(boundary_conditions);
//! let results = analysis.run()?;
//!
//! let AnalysisData::Vector(frequencies) = &results.additional_data["frequencies"] else {
//! panic!("frequencies should be a vector")
//! };
//! assert_eq!(frequencies.len(), 3);
//! assert!(frequencies.iter().all(|f| f.is_finite() && *f > 0.0));
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ## Module Organization
//!
//! - [`mesh`]: Mesh data structures, generation, and operations
//! - [`elements`]: Finite element formulations and shape functions
//! - [`materials`]: Constitutive models and material behavior
//! - [`assembly`]: Global matrix assembly and equation systems
//! - [`boundary`]: Boundary conditions and constraints
//! - [`solvers`]: Linear and nonlinear equation solvers
//! - [`kernels`]: GPU kernels and CUDA operations
//! - [`analysis`]: High-level analysis drivers
//!
//! ## Examples
//!
//! The crate includes comprehensive examples:
//!
//! - `linear_elasticity`: Basic linear elastic analysis
//! - `cantilever_beam`: Classical beam bending problem
//! - Advanced examples with nonlinear materials and contact
pub mod analysis;
pub mod assembly;
pub mod boundary;
pub mod elements;
pub mod error;
pub mod kernels;
pub mod materials;
pub mod mesh;
pub mod mor;
pub mod solvers;
pub mod utils;
// Re-export commonly used types for convenience
pub use error::{FeaError, FeaResult};
/// Common prelude for RTX-FEA.
///
/// This module re-exports the most commonly used types and traits
/// to make it easier to get started with the library.
pub mod prelude {
pub use crate::analysis::{Analysis, DynamicAnalysis, StaticLinearAnalysis};
pub use crate::assembly::{AssemblyOptions, GlobalAssembler, SparseMatrix};
pub use crate::boundary::{BoundaryCondition, DirichletBC, NeumannBC};
pub use crate::elements::{
ElementFactory, FiniteElement, NaturalCoords, PhysicalCoords, QuadratureRule,
ShapeFunctionEval,
};
pub use crate::error::{FeaError, FeaResult};
pub use crate::materials::{LinearElastic, Material, MaterialProperties};
pub use crate::mesh::{
Element, ElementId, ElementType, MaterialId, Mesh, Node, NodeId,
geometry::{Box3D, Circle, Rectangle, Sphere},
};
pub use crate::solvers::{CholeskyDirect, ConjugateGradient, LinearSolver, SolverOptions};
}
/// Library information and version.
pub mod info {
/// Get the library version.
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
/// Get the library name.
pub fn name() -> &'static str {
env!("CARGO_PKG_NAME")
}
/// Get the library description.
pub fn description() -> &'static str {
env!("CARGO_PKG_DESCRIPTION")
}
/// Check if CUDA support is available.
pub fn has_cuda_support() -> bool {
cfg!(feature = "cuda")
}
/// Get supported element types.
pub fn supported_elements() -> Vec<crate::mesh::ElementType> {
crate::mesh::ElementType::all()
}
/// Library build information.
pub fn build_info() -> BuildInfo {
BuildInfo {
version: version().to_string(),
cuda_support: has_cuda_support(),
element_types: supported_elements(),
build_date: std::env::var("VERGEN_BUILD_DATE")
.unwrap_or_else(|_| "unknown".to_string()),
git_sha: std::env::var("VERGEN_GIT_SHA").unwrap_or_else(|_| "unknown".to_string()),
}
}
/// Detailed build information.
#[derive(Debug, Clone)]
pub struct BuildInfo {
/// Library version
pub version: String,
/// CUDA support enabled
pub cuda_support: bool,
/// Supported element types
pub element_types: Vec<crate::mesh::ElementType>,
/// Build date
pub build_date: String,
/// Git commit SHA
pub git_sha: String,
}
impl std::fmt::Display for BuildInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "RTX-FEA Build Information:")?;
writeln!(f, " Version: {}", self.version)?;
writeln!(
f,
" CUDA Support: {}",
if self.cuda_support { "Yes" } else { "No" }
)?;
writeln!(f, " Element Types: {} supported", self.element_types.len())?;
writeln!(f, " Build Date: {}", self.build_date)?;
writeln!(f, " Git SHA: {}", &self.git_sha[..8])?;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_library_info() {
assert!(!info::version().is_empty());
assert_eq!(info::name(), "rtx-fea");
assert!(!info::description().is_empty());
let build_info = info::build_info();
assert!(!build_info.version.is_empty());
assert!(!build_info.element_types.is_empty());
}
#[test]
fn test_supported_elements() {
let elements = info::supported_elements();
assert!(elements.contains(&mesh::ElementType::Tri3));
assert!(elements.contains(&mesh::ElementType::Quad4));
assert!(elements.contains(&mesh::ElementType::Tet4));
assert!(elements.contains(&mesh::ElementType::Hex8));
}
#[test]
fn test_prelude_imports() -> FeaResult<()> {
use prelude::*;
// Test that we can create basic types from prelude
let _mesh = Mesh::new(2)?;
let _coords = NaturalCoords::new_2d(0.0, 0.0);
let _material = LinearElastic::new(200e9, 0.3);
// Test error types
let _result: FeaResult<()> = Ok(());
Ok(())
}
// Fixture correction on re-enable (2026-08-19): the factory deliberately
// refuses Point/Line2/Line3, which have no finite element implementation
// — requiring every ElementType to construct asserted a capability the
// crate does not claim. Implemented types must construct with the right
// node count; unimplemented ones must refuse explicitly, not stub.
#[test]
fn test_element_factory() {
use elements::ElementFactory;
use mesh::ElementType;
for element_type in ElementType::all() {
let element = ElementFactory::create(element_type);
match element_type {
ElementType::Point | ElementType::Line2 | ElementType::Line3 => {
assert!(element.is_err(), "{element_type:?} has no implementation");
}
_ => {
let element = element
.unwrap_or_else(|e| panic!("failed to create {element_type:?}: {e}"));
assert_eq!(element.num_nodes(), element_type.num_nodes());
}
}
}
}
#[test]
fn test_mesh_creation() {
let mesh = mesh::Mesh::generate_rectangle(1.0, 1.0, 3, 3);
assert!(mesh.is_ok());
let mesh = mesh.unwrap();
assert_eq!(mesh.num_nodes(), 9); // 3x3 grid
assert_eq!(mesh.num_elements(), 4); // 2x2 quads
assert!(mesh.validate().is_ok());
}
#[test]
fn test_basic_workflow() {
use materials::LinearElastic;
// Create mesh
let mesh = mesh::Mesh::generate_rectangle(1.0, 1.0, 3, 3).unwrap();
// Create material
let material = LinearElastic::new(200e9, 0.3);
// Validate mesh
assert!(mesh.validate().is_ok());
assert_eq!(mesh.spatial_dimension, 2);
assert!(mesh.num_elements() > 0);
assert!(mesh.num_nodes() > 0);
// Check material properties
assert!((material.elastic_modulus() - 200e9).abs() < 1e-6);
assert!((material.poisson_ratio() - 0.3).abs() < 1e-6);
}
}