* Model, fem and decks: point masses, rotary inertia, springs, dashpots and rod3 trusses Model: PointMass and Spring definitions (spring | dashpot; to ground or two-point; along a degree of freedom, a vector or the line of the ends) on a set or reference point, resolved per node; ElementKind::Rod3. fem: rod2/rod3 truss kernel on Basis::Line2/Line3 declaring ElementDofs (translations). Decks: Abaqus T3D3, MASS, ROTARYI, SPRING1/2/A, DASHPOT1/2/A; Nastran CONM1, CELAS2, CDAMP2; LS-DYNA ELEMENT_MASS/INERTIA and ELEMENT_DISCRETE; what a format cannot take is reported. Commands model.mass.* and model.spring.*. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Implicit solver: trusses, point masses, rotary inertia and springs on the DofLayout Trusses declare their translations like solids; point masses, inertia and springs ask the layout for the kinds they need at their nodes (translations, rotations), and reference points they use get six degrees of freedom. The matrix pattern takes rows for those points; dashpots are ignored with a warning. Existing results are bit-identical (hash-checked). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Explicit solver: trusses, point masses, springs and dashpots on the node-major layout Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: fem.md on trusses and discrete elements in the layout; regenerate features.md Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: trusses in plane models and corotational explicit trusses Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --------- Co-authored-by: jyviko <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
6.7 KiB
FEM foundation: degrees of freedom and shape functions
What every element in crates/fem and every solver builds on. Code: crates/fem/src/dof.rs,
crates/fem/src/basis.rs, crates/fem/src/jet.rs.
Degrees of freedom
Every unknown is a pair (node, DofKind):
DofKind |
Meaning | Model / deck number |
|---|---|---|
Ux, Uy, Uz |
translations along X, Y, Z | 1, 2, 3 |
Rx, Ry, Rz |
rotations about X, Y, Z | 4, 5, 6 |
P |
pressure (mixed u–p elements) | 8 |
T |
temperature | 11 |
The enum order (Ux, Uy, Uz, Rx, Ry, Rz, T, P) is the numbering order within a node.
DofKind::from_number / number convert to the 1-based numbers boundary conditions use;
from_direction / direction to the structural directions 0–5.
Elements declare their unknowns with ElementDofs { node: DofSet, internal: Internal }:
the kinds every node of the element carries (DofSet::TRANSLATIONS for solids, PLANE for
2D elements, STRUCTURAL for shells and beams, TEMPERATURE for heat transfer, translations
plus PRESSURE for continuous-pressure mixed elements) and the internal unknowns the element
condenses out itself (incompatible_modes, discontinuous pressure), which never reach the
global system. An element's matrices are ordered node by node, its node kinds in DofKind
order within a node (a shell's 24 × 24 stiffness: node 1 Ux Uy Uz Rx Ry Rz, node 2 …). Solid
elements: Formulation::dofs() / Solid::dofs().
Numbering (DofLayout): collect what every node needs with DofLayout::builder()
(element(labels, dofs), node(label, kinds) for constraints and boundary conditions that
need a kind no element gives, point(label, kinds) for reference points), then build(). A
node carries the union of what was asked for. Unknowns are numbered mesh nodes in label order,
each node's kinds in DofKind order, then the points in the order added; a slot's unknowns are
contiguous, so a node pair is one dense block in the assembled matrix. The same model always
gives the same numbering.
Lookups: node(label) / slot(label) (a slot is a position: mesh nodes 0..nodes(), points
point_slot(k)), dof(slot, kind), range(slot), kinds(slot), locate(dof) -> (slot, kind), element_dofs(slots, kinds), describe(dof), stride() (uniform layouts).
Trusses declare modelcraft_fem::truss::DOFS (translations, on Basis::Line2/Line3).
Point masses, springs and dashpots are not mesh elements: the implicit solver asks the layout
for what each needs at its nodes (modelcraft_solver::discrete::needs: translations for a mass
or a translational spring, rotations for an inertia or a torsional spring; a reference point
they use is a point with six) and finds its unknowns with discrete::dof_of.
Solvers: modelcraft_solver::system::Mesh::layout numbers the implicit system (mesh nodes,
then six per coupled reference point); assembly (sparse::add_block(k, layout, slots, kinds, data), add_scalar, add_diagonal) works for any mix of kinds per node, so a solid sharing
nodes with a shell adds its translation block into the shell nodes' six-unknown blocks.
Boundary conditions and loads address (node, kind): a boundary condition holds the kinds a node
carries, and a moment needs rotations. The explicit solver has solid nodes only and checks that
its layout is the uniform node-major one (NODE_DOFS = 3).
Shape functions
Basis is every family's interpolation:
| Family | Reference element | Nodes |
|---|---|---|
Line2, Line3 |
ξ ∈ [−1, 1] | ends −1, 1; line3 adds the middle |
Tri3, Tri6 |
ξ, η ≥ 0, ξ + η ≤ 1 | corners (0,0), (1,0), (0,1); mid-sides of 1-2, 2-3, 3-1 |
Quad4, Quad8, Quad9 |
[−1, 1]² | corners counter-clockwise from (−1,−1); mid-sides of 1-2, 2-3, 3-4, 4-1; centre |
Tet4, Tet10 |
unit tetrahedron | corners (0,0,0), (1,0,0), (0,1,0), (0,0,1); mid-sides of 1-2, 2-3, 3-1, 1-4, 2-4, 3-4 |
Wedge6, Wedge15 |
triangle × ζ ∈ [−1, 1] | bottom 1-3, top 4-6; mid-sides of the bottom edges, top edges, vertical edges |
Pyramid5 |
base [−1, 1]² at ζ = 0, apex (0, 0, 1) | base corners as quad4, then the apex |
Hex8, Hex20, Hex27 |
[−1, 1]³ | corners 1-4 at ζ = −1, 5-8 above; hex20 mid-sides (bottom, top, vertical edges); hex27 face centres in ElementKind::Hex8.faces() order, then the centre |
Node orders are the model's (ElementKind::edges() / faces() are the single source):
corners, mid-side nodes in edge order, face nodes, body nodes. Basis::of(ElementKind) maps a
model element; nodes() gives natural node coordinates; dim(), count(), corners(),
degree() (the complete polynomial degree reproduced: 1 for linear families including the
rational pyramid, 2 for the quadratic ones).
Evaluation at a natural point xi: [f64; 3] (unused coordinates are zero):
eval(xi) -> Shape: valuesnand first derivativesdn[i] = ∂Nᵢ/∂(ξ, η, ζ). tet4, tet10, wedge6, hex8, hex20 use the hand-written functions ofshape.rs(the solid kernels' path).eval2(xi) -> Shape2: addsd2n[i][a][b] = ∂²Nᵢ/∂ξₐ∂ξ_b, from second-order forward-mode automatic differentiation of the same formulas (jet::Jet): exact to round-off.gradients(&shape, xyz) -> Gradients: physicaldx[i] = ∇ₓNᵢ = J⁻¹ ∇_ξNᵢanddet J, with J_ab = ∂x_b/∂ξₐ; an inverted or collapsed element isError::Degenerate.hessians(&shape2, xyz) -> Hessians: physicald2x[i]= J⁻¹ (H_ξ − Σ_b (∂N/∂x_b) X_b″) J⁻ᵀ with X_b″ = Σᵢ x_{i,b} H_ξ(Nᵢ), the map's second derivative (T. J. R. Hughes, The Finite Element Method, 2000, §3.9, §4.2), andlaplacian(i). Without the X″ term a field linear in x would have a nonzero Hessian on a curved element.
A family of dimension d maps onto the first d physical coordinates (plane elements in x–y, lines along x). Elements embedded in a higher dimension (shells, beams, trusses in 3D) pass node coordinates in their own local frame.
Verification (basis::tests, dof::tests, jet::tests)
For every family: Kronecker delta at the nodes; partition of unity (Σ N = 1, Σ ∇N = 0,
Σ H = 0); exact reproduction of every monomial up to degree() with its gradient and Hessian
in natural coordinates; first and second derivatives against central differences;
on an affine map, physical gradients, Hessians and Laplacians exact for a complete polynomial
of the family's degree; on a curved map, linear fields exact with zero Hessian, and the
physical Hessian of a quadratic interpolant consistent with finite differences of its physical
gradient. The solver and explicit migration to DofLayout was checked bit for bit: every
result of the solver, explicit, engine and io test suites hashes identically before and after.