Initial commit
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user