Shells and beams: MITC4/MITC3 shells, Timoshenko beams, six-DOF nodes (M4) (#47)

* fem: MITC4/MITC3 flat shells and Timoshenko beams with profile properties (ElementDofs STRUCTURAL)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* solver: shells and beams on the DofLayout beside solids, 2D elements and trusses; analytic verification and tagged benchmarks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* engine: probe rotations, reaction moments and shell and beam element results; docs for shells and beams

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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:00:25 -04:00
1 parent 6de6de13aa
commit 29ae46ff84
18 files changed
+2411 -48

No files matched your search

+13 -3
View File
@@ -10,7 +10,8 @@
//! D. S. Malkus, M. E. Plesha, R. J. Witt, *Concepts and Applications of Finite Element
//! Analysis*, 4th ed., Wiley 2002, §9.2). The reference point's rotations take part through the
//! lever arms, so a moment on it reaches the face as a couple even when only translations are
//! coupled; coupled rotations have nothing to act on at solid nodes, which have none.
//! coupled. Coupled rotations tie the rotations of shell and beam nodes, θ_s = θ_r; solid
//! nodes have none.
//! - **Distributing** (an interpolation constraint): the reference point moves with the rigid
//! motion that fits the set's nodal translations best in the least-squares sense, with equal
//! weights (as the model and the deck writers define it). About the nodes' centroid c, with
@@ -50,8 +51,8 @@
use std::collections::BTreeMap;
use modelcraft_fem::DofKind;
use modelcraft_fem::dense::inverse3;
use modelcraft_fem::{DofKind, DofSet};
use modelcraft_model::{CouplingKind, Model, Resolved};
use crate::sparse::Csr;
@@ -113,7 +114,8 @@ pub fn coupling_rows<'m>(model: &'m Model, resolved: &Resolved, mesh: &Mesh) ->
let coupled = |range: std::ops::Range<usize>| range.filter(|k| c.dofs.contains(*k as u8 + 1));
match c.kind {
CouplingKind::Kinematic => {
if coupled(0..3).next().is_none() {
let rotations = |n: usize| mesh.layout.kinds(n).includes(DofSet::ROTATIONS);
if coupled(0..3).next().is_none() && !(nodes.iter().any(|n| rotations(*n)) && coupled(3..6).next().is_some()) {
return Err(Error::Invalid(format!(
"coupling `{name}` couples only rotations, which the nodes of solid elements do not have; couple translations too"
)));
@@ -133,6 +135,14 @@ pub fn coupling_rows<'m>(model: &'m Model, resolved: &Resolved, mesh: &Mesh) ->
}
rows.push(Row { terms, pivot, coupling: name });
}
// Shell and beam nodes also turn with the point: θ_s = θ_r.
if rotations(n) {
for k in coupled(3..6) {
let pivot =
DofKind::from_direction(k).ok_or_else(|| Error::Internal("direction".into())).and_then(|kind| mesh.dof(n, kind))?;
rows.push(Row { terms: vec![(pivot, 1.0), (rp(k)?, -1.0)], pivot, coupling: name });
}
}
}
}
CouplingKind::Distributing => {