Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,297 @@
// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! Numerical integration (quadrature) rules for finite elements.
//!
//! This module provides comprehensive Gauss quadrature implementations
//! for all supported element types with optimized GPU operations.
pub mod quadrature_adaptive;
pub mod quadrature_types;
pub use quadrature_adaptive::*;
pub use quadrature_types::*;
#[cfg(disabled)]
mod tests {
use super::*;
#[test]
fn test_1d_gauss_legendre() {
let rule = QuadratureRule::gauss_legendre_1d(2).unwrap();
assert_eq!(rule.num_points(), 2);
assert_eq!(rule.dimension, 1);
// Test integration of f(x) = x^2 from -1 to 1
// Exact integral is 2/3
let result = rule.integrate(|coords| coords.xi() * coords.xi());
assert!((result - 2.0 / 3.0).abs() < 1e-14);
}
#[test]
fn test_2d_quad_rule() {
let rule = QuadratureRule::quad(2).unwrap();
assert_eq!(rule.num_points(), 4);
assert_eq!(rule.dimension, 2);
// Test integration of f(x,y) = 1 over [-1,1]^2
// Exact integral is 4
let result = rule.integrate(|_| 1.0);
assert!((result - 4.0).abs() < 1e-14);
}
#[test]
fn test_3d_hex_rule() {
let rule = QuadratureRule::hex(2).unwrap();
assert_eq!(rule.num_points(), 8);
assert_eq!(rule.dimension, 3);
// Test integration of f(x,y,z) = 1 over [-1,1]^3
// Exact integral is 8
let result = rule.integrate(|_| 1.0);
assert!((result - 8.0).abs() < 1e-14);
}
#[test]
fn test_triangle_rule() {
let rule = QuadratureRule::triangle(1).unwrap();
assert_eq!(rule.num_points(), 1);
// Test integration of f(x,y) = 1 over unit triangle
// Area of reference triangle is 0.5
let result = rule.integrate(|_| 1.0);
assert!((result - 0.5).abs() < 1e-14);
}
#[test]
fn test_tetrahedron_rule() {
let rule = QuadratureRule::tetrahedron(1).unwrap();
assert_eq!(rule.num_points(), 1);
// Test integration of f(x,y,z) = 1 over unit tetrahedron
// Volume of reference tetrahedron is 1/6
let result = rule.integrate(|_| 1.0);
assert!((result - 1.0 / 6.0).abs() < 1e-14);
}
#[test]
fn test_high_order_accuracy() {
// Test that higher order rules are more accurate
let rule_2 = QuadratureRule::gauss_legendre_1d(2).unwrap();
let rule_4 = QuadratureRule::gauss_legendre_1d(4).unwrap();
// Integrate f(x) = x^4
let exact = 2.0 / 5.0;
let result_2 = rule_2.integrate(|coords| coords.xi().powi(4));
let result_4 = rule_4.integrate(|coords| coords.xi().powi(4));
let error_2 = (result_2 - exact).abs();
let error_4 = (result_4 - exact).abs();
assert!(error_4 < error_2);
assert!(error_4 < 1e-14);
}
#[test]
fn test_vector_integration() {
use nalgebra::DVector;
let rule = QuadratureRule::quad(2).unwrap();
// Integrate vector function [x, y]
let result = rule.integrate_vector(
|coords| DVector::from_vec(vec![coords.xi(), coords.eta()]),
2,
);
// Both components should integrate to 0 over symmetric domain
assert!(result[0].abs() < 1e-14);
assert!(result[1].abs() < 1e-14);
}
#[test]
fn test_quadrature_validation() {
let rule = QuadratureRule::quad(3).unwrap();
assert!(rule.validate().is_ok());
// Test that weights sum correctly
let weight_sum: f64 = rule.points.iter().map(|p| p.weight).sum();
assert!((weight_sum - 4.0).abs() < 1e-14);
}
#[test]
fn test_quadrature_point_access() {
let rule = QuadratureRule::line(2).unwrap();
// Valid access
let point = rule.point(0).unwrap();
assert!(point.weight > 0.0);
// Invalid access
let invalid = rule.point(100);
assert!(invalid.is_err());
}
#[test]
fn test_gauss_lobatto_rule() {
let rule = HighOrderQuadrature::gauss_lobatto_1d(3).unwrap();
assert_eq!(rule.num_points(), 3);
// Check that endpoints are included
assert_eq!(rule.points[0].coords.xi(), -1.0);
assert_eq!(rule.points[2].coords.xi(), 1.0);
// Test integration
let result = rule.integrate(|_| 1.0);
assert!((result - 2.0).abs() < 1e-14);
}
#[test]
fn test_pyramid_rule() {
let rule = HighOrderQuadrature::pyramid(1).unwrap();
assert_eq!(rule.num_points(), 1);
// Basic integration test
let result = rule.integrate(|_| 1.0);
assert!(result > 0.0);
}
#[test]
fn test_wedge_rule() {
let rule = HighOrderQuadrature::wedge(2).unwrap();
assert!(rule.num_points() > 0);
// Test that it's a valid 3D rule
assert_eq!(rule.dimension, 3);
}
#[test]
fn test_adaptive_quadrature_1d() {
let base_rule = QuadratureRule::line(2).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-6, 3);
// Test function: f(x) = x^2, integral from -1 to 1 = 2/3
let result = adaptive
.integrate(|coords| coords.xi() * coords.xi())
.unwrap();
assert!((result - 2.0 / 3.0).abs() < 1e-6);
}
#[test]
fn test_adaptive_quadrature_2d() {
let base_rule = QuadratureRule::quad(2).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-6, 3);
// Test function: f(x,y) = x*y, integral over [-1,1]^2 = 0
let result = adaptive
.integrate(|coords| coords.xi() * coords.eta())
.unwrap();
assert!(result.abs() < 1e-10, "Expected 0, got {}", result);
}
#[test]
fn test_adaptive_quadrature_3d() {
let base_rule = QuadratureRule::hex(2).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-6, 3);
// Test function: f(x,y,z) = x*y*z, integral over [-1,1]^3 = 0
let result = adaptive
.integrate(|coords| coords.xi() * coords.eta() * coords.zeta())
.unwrap();
assert!(result.abs() < 1e-10, "Expected 0, got {}", result);
}
#[test]
fn test_adaptive_quadrature_discontinuous_function() {
let base_rule = QuadratureRule::line(4).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-4, 5);
// Test discontinuous function (requires subdivision for accuracy)
let result = adaptive
.integrate(|coords| if coords.xi() > 0.0 { 1.0 } else { -1.0 })
.unwrap();
// Integral should be 0 (equal positive and negative areas)
assert!(result.abs() < 1e-3, "Expected ~0, got {}", result);
}
#[test]
fn test_adaptive_quadrature_polynomial() {
let base_rule = QuadratureRule::line(3).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-8, 4);
// Test polynomial: f(x) = x^4, integral from -1 to 1 = 2/5
let result = adaptive.integrate(|coords| coords.xi().powi(4)).unwrap();
let expected = 2.0 / 5.0;
assert!(
(result - expected).abs() < 1e-12,
"Expected {}, got {}",
expected,
result
);
}
#[test]
fn test_adaptive_quadrature_convergence() {
let base_rule = QuadratureRule::line(2).unwrap();
// Test with tight tolerance
let adaptive_tight = AdaptiveQuadrature::new(base_rule.clone(), 1e-10, 5);
let result_tight = adaptive_tight
.integrate(|coords| coords.xi().exp())
.unwrap();
// Test with loose tolerance
let adaptive_loose = AdaptiveQuadrature::new(base_rule, 1e-4, 2);
let result_loose = adaptive_loose
.integrate(|coords| coords.xi().exp())
.unwrap();
// Tight tolerance should be more accurate
let expected = (1.0_f64.exp() - (-1.0_f64).exp()); // e - e^(-1)
assert!((result_tight - expected).abs() < (result_loose - expected).abs());
}
#[test]
fn test_subdomain_creation_1d() {
let base_rule = QuadratureRule::line(2).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-6, 3);
// Test subdomain rule creation
let subdomain_rule = adaptive.create_subdomain_rule_1d(-1.0, 0.0).unwrap();
// Verify the subdomain rule integrates correctly over [-1, 0]
let result = subdomain_rule.integrate(|_| 1.0); // Constant function
assert!((result - 1.0).abs() < 1e-10, "Expected 1.0, got {}", result); // Length of interval
}
#[test]
fn test_subdomain_creation_2d() {
let base_rule = QuadratureRule::quad(2).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-6, 3);
// Test 2D subdomain rule creation
let subdomain_rule = adaptive
.create_subdomain_rule_2d(-1.0, 0.0, -1.0, 0.0)
.unwrap();
// Verify the subdomain rule integrates correctly over [-1,0] x [-1,0]
let result = subdomain_rule.integrate(|_| 1.0); // Constant function
assert!((result - 1.0).abs() < 1e-10, "Expected 1.0, got {}", result); // Area of quarter
}
#[test]
fn test_subdomain_creation_3d() {
let base_rule = QuadratureRule::hex(2).unwrap();
let adaptive = AdaptiveQuadrature::new(base_rule, 1e-6, 3);
// Test 3D subdomain rule creation
let subdomain_rule = adaptive
.create_subdomain_rule_3d(-1.0, 0.0, -1.0, 0.0, -1.0, 0.0)
.unwrap();
// Verify the subdomain rule integrates correctly over [-1,0]^3
let result = subdomain_rule.integrate(|_| 1.0); // Constant function
assert!((result - 1.0).abs() < 1e-10, "Expected 1.0, got {}", result); // Volume of eighth
}
}
@@ -0,0 +1,408 @@
// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! Adaptive and GPU-accelerated quadrature implementations.
use super::quadrature_types::{QuadraturePoint, QuadratureRule};
use crate::elements::NaturalCoords;
use crate::error::{ElementError, FeaResult};
use nalgebra::DVector;
/// Adaptive quadrature for high-precision integration.
pub struct AdaptiveQuadrature {
/// Base quadrature rule
pub base_rule: QuadratureRule,
/// Tolerance for convergence
pub tolerance: f64,
/// Maximum refinement levels
pub max_levels: usize,
}
impl AdaptiveQuadrature {
/// Create a new adaptive quadrature.
pub fn new(base_rule: QuadratureRule, tolerance: f64, max_levels: usize) -> Self {
Self {
base_rule,
tolerance,
max_levels,
}
}
/// Integrate with adaptive refinement.
pub fn integrate<F>(&self, function: F) -> FeaResult<f64>
where
F: Fn(&NaturalCoords) -> f64,
{
self.integrate_recursive(&function, 0)
}
/// Recursive integration with refinement.
fn integrate_recursive<F>(&self, function: &F, level: usize) -> FeaResult<f64>
where
F: Fn(&NaturalCoords) -> f64,
{
if level >= self.max_levels {
// Maximum refinement reached, use base rule
return Ok(self.base_rule.integrate(function));
}
// Compute integral with current rule
let integral = self.base_rule.integrate(function);
// Estimate error by subdividing domain
let subdomain_integrals = self.integrate_subdomains(function)?;
let refined_integral: f64 = subdomain_integrals.iter().sum();
let error = (integral - refined_integral).abs();
if error < self.tolerance {
// Converged
Ok(refined_integral)
} else {
// Need more refinement
let mut total = 0.0;
for _subdomain_integral in subdomain_integrals {
total += self.integrate_recursive(function, level + 1)?;
}
Ok(total)
}
}
/// Integrate over subdomains for error estimation.
fn integrate_subdomains<F>(&self, function: &F) -> FeaResult<Vec<f64>>
where
F: Fn(&NaturalCoords) -> f64,
{
let mut subdomain_integrals = Vec::new();
match self.base_rule.dimension {
1 => {
// 1D: split at midpoint
let subdomain_rules = vec![
self.create_subdomain_rule_1d(-1.0, 0.0)?,
self.create_subdomain_rule_1d(0.0, 1.0)?,
];
for rule in subdomain_rules {
subdomain_integrals.push(rule.integrate(function));
}
}
2 => {
// 2D: split into 4 quadrants
let subdomains = vec![
(-1.0, 0.0, -1.0, 0.0), // Bottom-left
(0.0, 1.0, -1.0, 0.0), // Bottom-right
(-1.0, 0.0, 0.0, 1.0), // Top-left
(0.0, 1.0, 0.0, 1.0), // Top-right
];
for (xi_min, xi_max, eta_min, eta_max) in subdomains {
let subdomain_rule =
self.create_subdomain_rule_2d(xi_min, xi_max, eta_min, eta_max)?;
subdomain_integrals.push(subdomain_rule.integrate(function));
}
}
3 => {
// 3D: split into 8 sub-cubes
let subdomains = vec![
(-1.0, 0.0, -1.0, 0.0, -1.0, 0.0), // Bottom-left-front
(0.0, 1.0, -1.0, 0.0, -1.0, 0.0), // Bottom-right-front
(-1.0, 0.0, 0.0, 1.0, -1.0, 0.0), // Top-left-front
(0.0, 1.0, 0.0, 1.0, -1.0, 0.0), // Top-right-front
(-1.0, 0.0, -1.0, 0.0, 0.0, 1.0), // Bottom-left-back
(0.0, 1.0, -1.0, 0.0, 0.0, 1.0), // Bottom-right-back
(-1.0, 0.0, 0.0, 1.0, 0.0, 1.0), // Top-left-back
(0.0, 1.0, 0.0, 1.0, 0.0, 1.0), // Top-right-back
];
for (xi_min, xi_max, eta_min, eta_max, zeta_min, zeta_max) in subdomains {
let subdomain_rule = self.create_subdomain_rule_3d(
xi_min, xi_max, eta_min, eta_max, zeta_min, zeta_max,
)?;
subdomain_integrals.push(subdomain_rule.integrate(function));
}
}
_ => {
return Err(ElementError::InvalidQuadrature {
reason: format!(
"Unsupported quadrature dimension: {}",
self.base_rule.dimension
),
}
.into());
}
}
Ok(subdomain_integrals)
}
fn create_subdomain_rule_1d(&self, start: f64, end: f64) -> FeaResult<QuadratureRule> {
let scale = (end - start) / 2.0;
let shift = f64::midpoint(start, end);
let points = self
.base_rule
.points
.iter()
.map(|p| {
let transformed_xi = scale * p.coords.xi() + shift;
QuadraturePoint::new_1d(transformed_xi, p.weight * scale)
})
.collect();
Ok(QuadratureRule::new(points, self.base_rule.order, 1))
}
fn create_subdomain_rule_2d(
&self,
xi_min: f64,
xi_max: f64,
eta_min: f64,
eta_max: f64,
) -> FeaResult<QuadratureRule> {
let xi_scale = (xi_max - xi_min) / 2.0;
let xi_shift = f64::midpoint(xi_min, xi_max);
let eta_scale = (eta_max - eta_min) / 2.0;
let eta_shift = f64::midpoint(eta_min, eta_max);
let jacobian = xi_scale * eta_scale;
let points = self
.base_rule
.points
.iter()
.map(|p| {
let transformed_xi = xi_scale * p.coords.xi() + xi_shift;
let transformed_eta = eta_scale * p.coords.eta() + eta_shift;
QuadraturePoint::new_2d(transformed_xi, transformed_eta, p.weight * jacobian)
})
.collect();
Ok(QuadratureRule::new(points, self.base_rule.order, 2))
}
fn create_subdomain_rule_3d(
&self,
xi_min: f64,
xi_max: f64,
eta_min: f64,
eta_max: f64,
zeta_min: f64,
zeta_max: f64,
) -> FeaResult<QuadratureRule> {
let xi_scale = (xi_max - xi_min) / 2.0;
let xi_shift = f64::midpoint(xi_min, xi_max);
let eta_scale = (eta_max - eta_min) / 2.0;
let eta_shift = f64::midpoint(eta_min, eta_max);
let zeta_scale = (zeta_max - zeta_min) / 2.0;
let zeta_shift = f64::midpoint(zeta_min, zeta_max);
let jacobian = xi_scale * eta_scale * zeta_scale;
let points = self
.base_rule
.points
.iter()
.map(|p| {
let transformed_xi = xi_scale * p.coords.xi() + xi_shift;
let transformed_eta = eta_scale * p.coords.eta() + eta_shift;
let transformed_zeta = zeta_scale * p.coords.zeta() + zeta_shift;
QuadraturePoint::new_3d(
transformed_xi,
transformed_eta,
transformed_zeta,
p.weight * jacobian,
)
})
.collect();
Ok(QuadratureRule::new(points, self.base_rule.order, 3))
}
}
/// High-order quadrature rules for special element types.
pub struct HighOrderQuadrature;
impl HighOrderQuadrature {
/// Create high-order Gauss-Lobatto rule for spectral elements.
pub fn gauss_lobatto_1d(order: usize) -> FeaResult<QuadratureRule> {
let points = match order {
2 => vec![
QuadraturePoint::new_1d(-1.0, 1.0),
QuadraturePoint::new_1d(1.0, 1.0),
],
3 => vec![
QuadraturePoint::new_1d(-1.0, 1.0 / 3.0),
QuadraturePoint::new_1d(0.0, 4.0 / 3.0),
QuadraturePoint::new_1d(1.0, 1.0 / 3.0),
],
4 => vec![
QuadraturePoint::new_1d(-1.0, 1.0 / 6.0),
QuadraturePoint::new_1d(-0.4472135954999579, 5.0 / 6.0),
QuadraturePoint::new_1d(0.4472135954999579, 5.0 / 6.0),
QuadraturePoint::new_1d(1.0, 1.0 / 6.0),
],
5 => vec![
QuadraturePoint::new_1d(-1.0, 0.1),
QuadraturePoint::new_1d(-0.6546536707079772, 0.5444444444444444),
QuadraturePoint::new_1d(0.0, 0.7111111111111111),
QuadraturePoint::new_1d(0.6546536707079772, 0.5444444444444444),
QuadraturePoint::new_1d(1.0, 0.1),
],
_ => {
return Err(ElementError::UnsupportedQuadratureOrder {
element_type: "gauss_lobatto".to_string(),
requested_order: order,
max_order: 5,
}
.into());
}
};
Ok(QuadratureRule::new(points, order, 1))
}
/// Create specialized quadrature for pyramid elements.
pub fn pyramid(order: usize) -> FeaResult<QuadratureRule> {
let points = match order {
1 => vec![QuadraturePoint::new_3d(0.0, 0.0, 0.25, 8.0 / 3.0)],
2 => {
let a = 0.585_410_196_624_968_4;
let b = 0.13819660112501052;
let w = 2.0 / 3.0;
vec![
QuadraturePoint::new_3d(0.0, 0.0, b, w),
QuadraturePoint::new_3d(a, 0.0, b, w),
QuadraturePoint::new_3d(-a, 0.0, b, w),
QuadraturePoint::new_3d(0.0, a, b, w),
QuadraturePoint::new_3d(0.0, -a, b, w),
]
}
_ => {
return Err(ElementError::UnsupportedQuadratureOrder {
element_type: "pyramid".to_string(),
requested_order: order,
max_order: 2,
}
.into());
}
};
Ok(QuadratureRule::new(points, order, 3))
}
/// Create specialized quadrature for wedge/prism elements.
pub fn wedge(order: usize) -> FeaResult<QuadratureRule> {
// Tensor product of triangle and line rules
let triangle_rule = QuadratureRule::triangle(order)?;
let line_rule = QuadratureRule::line(order)?;
let mut points = Vec::new();
for tri_point in &triangle_rule.points {
for line_point in &line_rule.points {
let xi = tri_point.coords.xi();
let eta = tri_point.coords.eta();
let zeta = line_point.coords.xi();
let weight = tri_point.weight * line_point.weight;
points.push(QuadraturePoint::new_3d(xi, eta, zeta, weight));
}
}
Ok(QuadratureRule::new(points, order, 3))
}
}
/// GPU-accelerated quadrature operations.
#[cfg(feature = "cuda")]
pub struct GpuQuadrature {
/// CUDA context
device: Option<std::sync::Arc<cudarc::driver::safe::CudaContext>>,
}
#[cfg(not(feature = "cuda"))]
pub struct GpuQuadrature {
_private: (),
}
#[cfg(feature = "cuda")]
impl GpuQuadrature {
/// Create new GPU quadrature engine.
pub fn new() -> FeaResult<Self> {
let device = cudarc::driver::safe::CudaContext::new(0).ok();
Ok(Self { device })
}
/// Batch evaluate quadrature over multiple elements on GPU.
pub fn batch_integrate(
&self,
_rules: &[QuadratureRule],
_element_data: &[DVector<f64>],
) -> FeaResult<Vec<f64>> {
if self.device.is_none() {
return Err(ElementError::GpuRequired {
operation: "batch quadrature integration".to_string(),
}
.into());
}
Err(ElementError::GpuRequired {
operation: "GPU batch integration not yet fully implemented".to_string(),
}
.into())
}
/// Evaluate shape functions at all quadrature points on GPU.
pub fn evaluate_shape_functions_gpu(
&self,
_rule: &QuadratureRule,
_element_type: &str,
) -> FeaResult<Vec<DVector<f64>>> {
if self.device.is_none() {
return Err(ElementError::GpuRequired {
operation: "shape function evaluation".to_string(),
}
.into());
}
Err(ElementError::GpuRequired {
operation: "GPU shape function evaluation not yet fully implemented".to_string(),
}
.into())
}
/// Check if GPU is available.
pub fn is_gpu_available(&self) -> bool {
self.device.is_some()
}
}
#[cfg(not(feature = "cuda"))]
impl GpuQuadrature {
/// Create new GPU quadrature engine (stub when CUDA not available).
pub fn new() -> FeaResult<Self> {
Ok(Self { _private: () })
}
/// Batch evaluate quadrature over multiple elements on GPU.
pub fn batch_integrate(
&self,
_rules: &[QuadratureRule],
_element_data: &[DVector<f64>],
) -> FeaResult<Vec<f64>> {
// GPU is required - no CPU fallback
Err(ElementError::GpuRequired {
operation: "batch quadrature integration (CUDA not enabled)".to_string(),
}
.into())
}
/// Evaluate shape functions at all quadrature points on GPU.
pub fn evaluate_shape_functions_gpu(
&self,
_rule: &QuadratureRule,
_element_type: &str,
) -> FeaResult<Vec<DVector<f64>>> {
Err(ElementError::GpuRequired {
operation: "GPU shape function evaluation (CUDA not enabled)".to_string(),
}
.into())
}
/// Check if GPU is available.
pub fn is_gpu_available(&self) -> bool {
false
}
}
@@ -0,0 +1,357 @@
// Copyright (c) 2024 RustyTorch++ Team
// Licensed under the Apache License, Version 2.0
//! Quadrature types and basic rules for finite elements.
use crate::elements::NaturalCoords;
use crate::error::{ElementError, FeaResult};
use nalgebra::DVector;
/// Quadrature point with coordinates and weight.
#[derive(Debug, Clone)]
pub struct QuadraturePoint {
/// Natural coordinates of the quadrature point
pub coords: NaturalCoords,
/// Integration weight
pub weight: f64,
}
impl QuadraturePoint {
/// Create a new quadrature point.
pub fn new(coords: NaturalCoords, weight: f64) -> Self {
Self { coords, weight }
}
/// Create a 1D quadrature point.
pub fn new_1d(xi: f64, weight: f64) -> Self {
Self::new(NaturalCoords::new_1d(xi), weight)
}
/// Create a 2D quadrature point.
pub fn new_2d(xi: f64, eta: f64, weight: f64) -> Self {
Self::new(NaturalCoords::new_2d(xi, eta), weight)
}
/// Create a 3D quadrature point.
pub fn new_3d(xi: f64, eta: f64, zeta: f64, weight: f64) -> Self {
Self::new(NaturalCoords::new_3d(xi, eta, zeta), weight)
}
}
/// Quadrature rule containing all integration points and weights.
#[derive(Debug, Clone)]
pub struct QuadratureRule {
/// Quadrature points
pub points: Vec<QuadraturePoint>,
/// Integration order
pub order: usize,
/// Spatial dimension
pub dimension: usize,
}
impl QuadratureRule {
/// Create a new quadrature rule.
pub fn new(points: Vec<QuadraturePoint>, order: usize, dimension: usize) -> Self {
Self {
points,
order,
dimension,
}
}
/// Get the number of quadrature points.
pub fn num_points(&self) -> usize {
self.points.len()
}
/// Get a quadrature point by index.
pub fn point(&self, index: usize) -> FeaResult<&QuadraturePoint> {
self.points.get(index).ok_or_else(|| {
ElementError::IntegrationPointOutOfRange {
point: index,
max_points: self.points.len().saturating_sub(1),
}
.into()
})
}
/// Create 1D Gauss-Legendre quadrature rule.
pub fn gauss_legendre_1d(order: usize) -> FeaResult<Self> {
let points = match order {
1 => vec![QuadraturePoint::new_1d(0.0, 2.0)],
2 => vec![
QuadraturePoint::new_1d(-1.0 / 3.0_f64.sqrt(), 1.0),
QuadraturePoint::new_1d(1.0 / 3.0_f64.sqrt(), 1.0),
],
3 => vec![
QuadraturePoint::new_1d(-0.7745966692414834, 0.5555555555555556),
QuadraturePoint::new_1d(0.0, 0.8888888888888888),
QuadraturePoint::new_1d(0.7745966692414834, 0.5555555555555556),
],
4 => vec![
QuadraturePoint::new_1d(-0.8611363115940526, 0.3478548451374538),
QuadraturePoint::new_1d(-0.3399810435848563, 0.6521451548625461),
QuadraturePoint::new_1d(0.3399810435848563, 0.6521451548625461),
QuadraturePoint::new_1d(0.8611363115940526, 0.3478548451374538),
],
5 => vec![
QuadraturePoint::new_1d(-0.906_179_845_938_664, 0.2369268850561891),
QuadraturePoint::new_1d(-0.5384693101056831, 0.4786286704993665),
QuadraturePoint::new_1d(0.0, 0.5688888888888889),
QuadraturePoint::new_1d(0.5384693101056831, 0.4786286704993665),
QuadraturePoint::new_1d(0.906_179_845_938_664, 0.2369268850561891),
],
_ => {
return Err(ElementError::UnsupportedQuadratureOrder {
element_type: "line".to_string(),
requested_order: order,
max_order: 5,
}
.into());
}
};
Ok(Self::new(points, order, 1))
}
/// Create quadrature rule for 1D line element.
pub fn line(order: usize) -> FeaResult<Self> {
Self::gauss_legendre_1d(order)
}
/// Create quadrature rule for 2D triangular element.
pub fn triangle(order: usize) -> FeaResult<Self> {
let points = match order {
1 => {
// 1-point rule (exact for linear)
vec![QuadraturePoint::new_2d(1.0 / 3.0, 1.0 / 3.0, 0.5)]
}
2 => {
// 3-point rule (exact for quadratic)
vec![
QuadraturePoint::new_2d(2.0 / 3.0, 1.0 / 6.0, 1.0 / 6.0),
QuadraturePoint::new_2d(1.0 / 6.0, 2.0 / 3.0, 1.0 / 6.0),
QuadraturePoint::new_2d(1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0),
]
}
3 => {
// 7-point rule (exact for cubic)
let a = 0.101286507323456;
let b = 0.470142064105115;
let w1 = 0.0629695902724135 / 2.0;
let w2 = 0.0661970763942530 / 2.0;
let w3 = 0.1125 / 2.0;
vec![
QuadraturePoint::new_2d(a, a, w1),
QuadraturePoint::new_2d(1.0 - 2.0 * a, a, w1),
QuadraturePoint::new_2d(a, 1.0 - 2.0 * a, w1),
QuadraturePoint::new_2d(b, b, w2),
QuadraturePoint::new_2d(1.0 - 2.0 * b, b, w2),
QuadraturePoint::new_2d(b, 1.0 - 2.0 * b, w2),
QuadraturePoint::new_2d(1.0 / 3.0, 1.0 / 3.0, w3),
]
}
_ => {
return Err(ElementError::UnsupportedQuadratureOrder {
element_type: "triangle".to_string(),
requested_order: order,
max_order: 3,
}
.into());
}
};
Ok(Self::new(points, order, 2))
}
/// Create quadrature rule for 2D quadrilateral element.
pub fn quad(order: usize) -> FeaResult<Self> {
let rule_1d = Self::gauss_legendre_1d(order)?;
let mut points = Vec::new();
for i in 0..rule_1d.points.len() {
for j in 0..rule_1d.points.len() {
let xi = rule_1d.points[i].coords.xi();
let eta = rule_1d.points[j].coords.xi();
let weight = rule_1d.points[i].weight * rule_1d.points[j].weight;
points.push(QuadraturePoint::new_2d(xi, eta, weight));
}
}
Ok(Self::new(points, order, 2))
}
/// Create quadrature rule for 2D quadrilateral element.
pub fn quadrilateral(order: usize) -> FeaResult<Self> {
// Tensor product of 1D Gauss rules
let line_rule = Self::line(order)?;
let mut points = Vec::new();
for xi_point in &line_rule.points {
for eta_point in &line_rule.points {
points.push(QuadraturePoint::new_2d(
xi_point.coords.xi(),
eta_point.coords.xi(),
xi_point.weight * eta_point.weight,
));
}
}
Ok(Self::new(points, order, 2))
}
/// Create quadrature rule for 3D hexahedral element.
pub fn hexahedron(order: usize) -> FeaResult<Self> {
// Tensor product of 1D Gauss rules
let line_rule = Self::line(order)?;
let mut points = Vec::new();
for xi_point in &line_rule.points {
for eta_point in &line_rule.points {
for zeta_point in &line_rule.points {
points.push(QuadraturePoint::new_3d(
xi_point.coords.xi(),
eta_point.coords.xi(),
zeta_point.coords.xi(),
xi_point.weight * eta_point.weight * zeta_point.weight,
));
}
}
}
Ok(Self::new(points, order, 3))
}
/// Create quadrature rule for 3D tetrahedral element.
pub fn tetrahedron(order: usize) -> FeaResult<Self> {
let points = match order {
1 => {
// 1-point rule
vec![QuadraturePoint::new_3d(0.25, 0.25, 0.25, 1.0 / 6.0)]
}
2 => {
// 4-point rule
let a = 0.138196601125011;
let b = 0.585410196624969;
let w = 1.0 / 24.0;
vec![
QuadraturePoint::new_3d(a, a, a, w),
QuadraturePoint::new_3d(b, a, a, w),
QuadraturePoint::new_3d(a, b, a, w),
QuadraturePoint::new_3d(a, a, b, w),
]
}
3 => {
// 11-point rule
let a = 0.25;
let b = 0.785714285714286;
let c = 0.071428571428571;
let d = 0.399403576166799;
let e = 0.100596423833201;
let w1 = -0.01315555555556 / 6.0;
let w2 = 0.00762222222222 / 6.0;
let w3 = 0.02488888888889 / 6.0;
vec![
QuadraturePoint::new_3d(a, a, a, w1),
QuadraturePoint::new_3d(b, c, c, w2),
QuadraturePoint::new_3d(c, b, c, w2),
QuadraturePoint::new_3d(c, c, b, w2),
QuadraturePoint::new_3d(c, c, c, w2),
QuadraturePoint::new_3d(d, e, e, w3),
QuadraturePoint::new_3d(e, d, e, w3),
QuadraturePoint::new_3d(e, e, d, w3),
QuadraturePoint::new_3d(d, d, e, w3),
QuadraturePoint::new_3d(d, e, d, w3),
QuadraturePoint::new_3d(e, d, d, w3),
]
}
_ => {
return Err(ElementError::UnsupportedQuadratureOrder {
element_type: "tetrahedron".to_string(),
requested_order: order,
max_order: 3,
}
.into());
}
};
Ok(Self::new(points, order, 3))
}
/// Create quadrature rule for 3D hexahedral element.
pub fn hex(order: usize) -> FeaResult<Self> {
let rule_1d = Self::gauss_legendre_1d(order)?;
let mut points = Vec::new();
for i in 0..rule_1d.points.len() {
for j in 0..rule_1d.points.len() {
for k in 0..rule_1d.points.len() {
let xi = rule_1d.points[i].coords.xi();
let eta = rule_1d.points[j].coords.xi();
let zeta = rule_1d.points[k].coords.xi();
let weight = rule_1d.points[i].weight
* rule_1d.points[j].weight
* rule_1d.points[k].weight;
points.push(QuadraturePoint::new_3d(xi, eta, zeta, weight));
}
}
}
Ok(Self::new(points, order, 3))
}
/// Integrate a function using this quadrature rule.
pub fn integrate<F>(&self, function: F) -> f64
where
F: Fn(&NaturalCoords) -> f64,
{
self.points
.iter()
.map(|p| p.weight * function(&p.coords))
.sum()
}
/// Integrate a vector-valued function.
pub fn integrate_vector<F>(&self, function: F, vector_size: usize) -> DVector<f64>
where
F: Fn(&NaturalCoords) -> DVector<f64>,
{
let mut result = DVector::zeros(vector_size);
for point in &self.points {
result += point.weight * function(&point.coords);
}
result
}
/// Validate the quadrature rule (weights should sum to appropriate value).
pub fn validate(&self) -> FeaResult<()> {
let weight_sum: f64 = self.points.iter().map(|p| p.weight).sum();
let expected = match self.dimension {
1 => 2.0, // Line element [-1, 1]
2 => 4.0, // Quad element [-1, 1] x [-1, 1]
3 => 8.0, // Hex element [-1, 1]^3
_ => return Ok(()), // Skip validation for unknown dimensions
};
// For triangular/tetrahedral elements, the expected sum is different
let tolerance = 1e-10;
if (weight_sum - expected).abs() > tolerance
&& (weight_sum - 0.5).abs() > tolerance
&& (weight_sum - 1.0 / 6.0).abs() > tolerance
{
return Err(ElementError::InvalidQuadrature {
reason: format!(
"Quadrature weights sum to {} but expected ~{} for {}D element",
weight_sum, expected, self.dimension
),
}
.into());
}
Ok(())
}
}