forked from 1b-agent/fieldcraft-upstream
fem: pyramid5 element (solver, quality, decks, VTK)
A five-node pyramid, the conforming transition between a hexahedron's face and tetrahedra: Bedrosian's rational shape functions with a collapsed (Duffy) Gauss rule, a certified Jacobian on the base square with exact volume, a scaled Jacobian, C3D5 / CPYRAM / LS-DYNA degenerate solid, VTK_PYRAMID and the binary mesh code 13. A distorted hex8-pyramid5-tet4 transition mesh passes the patch test and uniaxial tension exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
4e6fd59409
commit
3a5a8213a1
28 files changed
+325
-28
No files matched your search
@@ -207,6 +207,7 @@ impl Basis {
|
||||
ElementKind::Tet4 => Basis::Tet4,
|
||||
ElementKind::Tet10 => Basis::Tet10,
|
||||
ElementKind::Wedge6 => Basis::Wedge6,
|
||||
ElementKind::Pyramid5 => Basis::Pyramid5,
|
||||
ElementKind::Hex8 => Basis::Hex8,
|
||||
ElementKind::Hex20 => Basis::Hex20,
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ use crate::{Error, Result};
|
||||
pub enum Family {
|
||||
Tetrahedron,
|
||||
Wedge,
|
||||
Pyramid,
|
||||
Hexahedron,
|
||||
/// The 2D elements of plane and axisymmetric models ([`crate::plane`]).
|
||||
Triangle,
|
||||
@@ -74,7 +75,7 @@ pub struct ElementType {
|
||||
}
|
||||
|
||||
/// The element table.
|
||||
pub static ELEMENT_TYPES: [ElementType; 5] = [
|
||||
pub static ELEMENT_TYPES: [ElementType; 6] = [
|
||||
ElementType {
|
||||
kind: ElementKind::Tet4,
|
||||
family: Family::Tetrahedron,
|
||||
@@ -108,6 +109,17 @@ pub static ELEMENT_TYPES: [ElementType; 5] = [
|
||||
mass: Rule::Wedge,
|
||||
incompatible_modes: false,
|
||||
},
|
||||
ElementType {
|
||||
kind: ElementKind::Pyramid5,
|
||||
family: Family::Pyramid,
|
||||
nodes: 5,
|
||||
corners: 5,
|
||||
shape: shape::pyramid5,
|
||||
full: Rule::Pyramid(2),
|
||||
reduced: None,
|
||||
mass: Rule::Pyramid(3),
|
||||
incompatible_modes: false,
|
||||
},
|
||||
ElementType {
|
||||
kind: ElementKind::Hex8,
|
||||
family: Family::Hexahedron,
|
||||
@@ -137,7 +149,7 @@ pub fn element_type(kind: ElementKind) -> Result<&'static ElementType> {
|
||||
ELEMENT_TYPES
|
||||
.iter()
|
||||
.find(|t| t.kind == kind)
|
||||
.ok_or_else(|| Error::NotSupported(format!("{} elements (the solver has tet4, tet10, wedge6, hex8 and hex20 so far)", kind.name())))
|
||||
.ok_or_else(|| Error::NotSupported(format!("{} elements (the solver has tet4, tet10, wedge6, pyramid5, hex8 and hex20 so far)", kind.name())))
|
||||
}
|
||||
|
||||
/// How a solid element is formulated.
|
||||
@@ -730,6 +742,7 @@ pub fn extrapolation(ty: &ElementType, rule: Rule) -> Result<Extrapolation> {
|
||||
let corner_basis = |xi: [f64; 3]| match ty.family {
|
||||
Family::Tetrahedron => shape::tet4(xi),
|
||||
Family::Wedge => shape::wedge6(xi),
|
||||
Family::Pyramid => shape::pyramid5(xi),
|
||||
Family::Hexahedron => shape::hex8(xi),
|
||||
Family::Triangle => Basis::Tri3.eval(xi),
|
||||
Family::Quadrilateral => Basis::Quad4.eval(xi),
|
||||
|
||||
@@ -833,9 +833,15 @@ mod tests {
|
||||
let x8: Vec<[f64; 3]> = x20[..8].to_vec();
|
||||
let x6 = vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.1], [0., 0., 1.], [1.1, 0., 1.], [0., 1., 1.2]];
|
||||
let x4 = vec![x10[0], x10[1], x10[2], x10[3]];
|
||||
for (kind, x) in
|
||||
[(ElementKind::Tet4, &x4), (ElementKind::Tet10, &x10), (ElementKind::Wedge6, &x6), (ElementKind::Hex8, &x8), (ElementKind::Hex20, &x20)]
|
||||
{
|
||||
let x5 = vec![[0., 0., 0.], [1., 0., 0.1], [1.1, 1., 0.], [0., 1., 0.], [0.5, 0.4, 0.9]];
|
||||
for (kind, x) in [
|
||||
(ElementKind::Tet4, &x4),
|
||||
(ElementKind::Tet10, &x10),
|
||||
(ElementKind::Wedge6, &x6),
|
||||
(ElementKind::Pyramid5, &x5),
|
||||
(ElementKind::Hex8, &x8),
|
||||
(ElementKind::Hex20, &x20),
|
||||
] {
|
||||
for m in [&svk, &neo] {
|
||||
let solid = Solid::new(kind, x, Formulation::Full).unwrap();
|
||||
check_tangent(&solid, m, &displacement(x.len(), 0.08));
|
||||
|
||||
@@ -66,6 +66,10 @@ pub enum Rule {
|
||||
/// Three triangle points times two Gauss points in ζ on the reference wedge: point k of each
|
||||
/// layer sits near corner k, the lower layer first.
|
||||
Wedge,
|
||||
/// The collapsed (Duffy) product on the reference pyramid, n points per direction: Gauss–Legendre
|
||||
/// in u, v ∈ [−1, 1] and w ∈ [0, 1], mapped by ξ = u(1 − w), η = v(1 − w), ζ = w with the
|
||||
/// weight times (1 − w)², which makes the pyramid's rational functions polynomials in (u, v, w).
|
||||
Pyramid(u8),
|
||||
}
|
||||
|
||||
impl Rule {
|
||||
@@ -125,6 +129,19 @@ impl Rule {
|
||||
let tri = [[b, b], [a, b], [b, a]];
|
||||
gauss_legendre(2).iter().flat_map(|&(z, wz)| tri.iter().map(move |t| Point { xi: [t[0], t[1], z], weight: wz / 6.0 })).collect()
|
||||
}
|
||||
Rule::Pyramid(n) => {
|
||||
let g = gauss_legendre(usize::from(n));
|
||||
let mut out = Vec::with_capacity(g.len().pow(3));
|
||||
for &(z, wz) in g {
|
||||
let (w, ww) = ((z + 1.0) / 2.0, wz / 2.0);
|
||||
for &(v, wv) in g {
|
||||
for &(u, wu) in g {
|
||||
out.push(Point { xi: [u * (1.0 - w), v * (1.0 - w), w], weight: wu * wv * ww * (1.0 - w) * (1.0 - w) });
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +199,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pyramid: ∫ ξ^a η^b ζ^c over the pyramid of base [−1, 1]² at ζ = 0 and apex (0, 0, 1)
|
||||
// = ∫_0^1 ζ^c ∫ ξ^a ∫ η^b over the square of half-width 1 − ζ: for even a, b,
|
||||
// 4/((a+1)(b+1)) ∫_0^1 ζ^c (1 − ζ)^(a+b+2) = 4/((a+1)(b+1)) · B(c + 1, a + b + 3).
|
||||
let beta = |p: i32, q: i32| f(p - 1) * f(q - 1) / f(p + q - 1);
|
||||
// Three points per direction are exact to degree 5 in w, i.e. a + b + c ≤ 3.
|
||||
for a in 0..=3 {
|
||||
for b in 0..=3 - a {
|
||||
for c in 0..=3 - a - b {
|
||||
let exact = if a % 2 == 1 || b % 2 == 1 { 0.0 } else { 4.0 / f64::from((a + 1) * (b + 1)) * beta(c + 1, a + b + 3) };
|
||||
let got = integrate(Rule::Pyramid(3), a, b, c);
|
||||
assert!((got - exact).abs() < 1e-14, "pyramid ξ^{a} η^{b} ζ^{c}: {got} vs {exact}");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!((integrate(Rule::Pyramid(2), 0, 0, 0) - 4.0 / 3.0).abs() < 1e-15);
|
||||
// Triangle: ∫ ξ^a η^b = a! b! / (a + b + 2)!.
|
||||
for (rule, degree) in [(Rule::TriConical(3), 4), (Rule::TriConical(4), 6), (Rule::TriCentroid, 1), (Rule::Tri3, 2)] {
|
||||
for a in 0..=degree {
|
||||
|
||||
@@ -116,6 +116,13 @@ pub fn wedge6(xi: [f64; 3]) -> Shape {
|
||||
s
|
||||
}
|
||||
|
||||
/// Five-node pyramid: base [−1, 1]² at ζ = 0, apex (0, 0, 1), the rational functions of
|
||||
/// [`crate::basis`] (Bedrosian 1992), conforming with tetrahedra on its triangles and with
|
||||
/// hexahedra on its base.
|
||||
pub fn pyramid5(xi: [f64; 3]) -> Shape {
|
||||
crate::basis::Basis::Pyramid5.eval(xi)
|
||||
}
|
||||
|
||||
/// Twenty-node serendipity hexahedron.
|
||||
pub fn hex20(xi: [f64; 3]) -> Shape {
|
||||
let mut s = Shape::new(20);
|
||||
|
||||
@@ -13,6 +13,7 @@ fn distorted(kind: ElementKind, curved: bool) -> Vec<[f64; 3]> {
|
||||
let corners: Vec<[f64; 3]> = match kind {
|
||||
ElementKind::Tet4 | ElementKind::Tet10 => vec![[0.1, -0.05, 0.0], [1.2, 0.1, 0.05], [0.2, 0.9, -0.1], [0.05, 0.15, 1.1]],
|
||||
ElementKind::Wedge6 => vec![[0.0, 0.0, 0.0], [2.1, 0.1, -0.1], [0.2, 1.3, 0.05], [0.1, -0.1, 0.9], [2.0, 0.05, 1.1], [0.15, 1.2, 1.0]],
|
||||
ElementKind::Pyramid5 => vec![[0.0, 0.0, 0.0], [2.1, 0.1, -0.1], [2.3, 1.2, 0.05], [-0.1, 0.9, 0.1], [0.9, 0.6, 1.3]],
|
||||
_ => vec![
|
||||
[0.0, 0.0, 0.0],
|
||||
[2.1, 0.1, -0.1],
|
||||
@@ -34,10 +35,11 @@ fn distorted(kind: ElementKind, curved: bool) -> Vec<[f64; 3]> {
|
||||
xyz
|
||||
}
|
||||
|
||||
const CASES: [(ElementKind, Formulation); 10] = [
|
||||
const CASES: [(ElementKind, Formulation); 11] = [
|
||||
(ElementKind::Tet4, Formulation::Full),
|
||||
(ElementKind::Tet10, Formulation::Full),
|
||||
(ElementKind::Wedge6, Formulation::Full),
|
||||
(ElementKind::Pyramid5, Formulation::Full),
|
||||
(ElementKind::Hex8, Formulation::Full),
|
||||
(ElementKind::Hex8, Formulation::IncompatibleModes),
|
||||
(ElementKind::Hex20, Formulation::Full),
|
||||
@@ -156,6 +158,7 @@ fn extrapolation_reproduces_linear_fields_on_affine_elements() {
|
||||
let mut xyz: Vec<[f64; 3]> = match kind {
|
||||
ElementKind::Tet10 => vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]],
|
||||
ElementKind::Wedge6 => vec![[0., 0., 0.], [2., 0., 0.], [0.5, 1., 0.], [0., 0.2, 1.], [2., 0.2, 1.], [0.5, 1.2, 1.]],
|
||||
ElementKind::Pyramid5 => vec![[0., 0., 0.], [2., 0., 0.], [2.5, 1., 0.], [0.5, 1., 0.], [1.2, 0.5, 1.]],
|
||||
_ => vec![[0., 0., 0.], [2., 0., 0.], [2.5, 1., 0.], [0.5, 1., 0.], [0., 0., 1.], [2., 0., 1.], [2.5, 1., 1.], [0.5, 1., 1.]],
|
||||
};
|
||||
let c = xyz.clone();
|
||||
|
||||
Reference in new issue
Block a user