Hybrid (mixed u–p) solids: hex8 and tet10 with a constant pressure, hex20 with a linear one (#49)

The isotropic elasticity splits into D_dev + K m mᵀ; an element-wise
discontinuous pressure is condensed out (p̂ = K M⁻¹ Gᵀ u), leaving
K_dev + K G M⁻¹ Gᵀ (Hughes §4.3–4.5). Q2–P1disc and P2–P0 are inf-sup
stable; Q1–P0 does not lock; tet4 and wedge6 still lock and are refused, as
are orthotropic materials (named at the section). ElementDofs counts the
condensed pressures. Verified: element patch, rigid modes and totals for
every hybrid kind, a block in uniaxial strain at ν = 0.4999 (exact), Lamé's
cylinder in plane strain at ν = 0.4999 (hex20 1e-5, tet10 1.3e-3, hex8
2.5e-3 vs the locking displacement hex8 at −80 %). Every non-hybrid result
of the test suites hashes identically before and after.


Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 22:20:21 -04:00
1 parent 0899587127
commit 5ebb9f16dc
11 files changed
+525 -32

No files matched your search

+7 -1
View File
@@ -17,7 +17,7 @@ use modelcraft_fem::{DofKind, DofLayout, DofSet, Elasticity, ElementDofs, PointS
use modelcraft_model::nodal::edge_pressure;
use std::collections::BTreeSet;
use modelcraft_model::{ElementKind, Integration, Load, Model, Resolved, SectionKind, Step, Target, centrifugal_acceleration};
use modelcraft_model::{Elastic, ElementKind, Integration, Load, Model, Resolved, SectionKind, Step, Target, centrifugal_acceleration};
use crate::analysis::MassKind;
use crate::{Error, Result};
@@ -398,6 +398,12 @@ impl Mesh {
.ok_or_else(|| Error::Invalid(format!("section `{name}`: material `{}` is missing", section.material)))?;
let elastic =
material.elastic.as_ref().ok_or_else(|| Error::Invalid(format!("material `{}` has no elastic constants", section.material)))?;
if section.formulation.hybrid && !matches!(elastic, Elastic::Isotropic { .. }) {
return Err(Error::NotSupported(format!(
"hybrid elements of the orthotropic material `{}` (section `{name}`): the pressure split needs isotropic elasticity",
section.material
)));
}
let set = resolved.sets.get(&section.set).ok_or_else(|| Error::Invalid(format!("set `{}` is not resolved", section.set)))?;
let bad = |e: modelcraft_fem::Error| match e {
modelcraft_fem::Error::NotSupported(m) => Error::NotSupported(format!("section `{name}`: {m}")),