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
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]>
310 lines
11 KiB
Rust
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);
|
|
}
|
|
}
|