forked from 1b-agent/fieldcraft-upstream
FEM foundation A: DofKind/DofLayout, shape-function library with Hessians, solvers on DofLayout (#35)
* fem: DofKind/DofLayout and the shape-function library with second derivatives DofKind (Ux..Rz, T, P), DofSet, ElementDofs (per-node kinds and condensed internal unknowns) and DofLayout: mesh nodes by label, kinds in order, then reference points. basis::Basis covers line2/3, tri3/6, quad4/8/9, tet4/10, wedge6/15, pyramid5, hex8/20/27 with values, first and second natural derivatives (second-order forward AD, fem::jet) and the physical gradients, Hessians and Laplacians with the map's second-derivative term. Tested for Kronecker delta, partition of unity, polynomial completeness, finite differences, affine and curved maps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * solver, explicit: number degrees of freedom with DofLayout The implicit mesh numbers its unknowns with a DofLayout (mesh nodes by label with the kinds their elements declare, then six per coupled reference point); assembly adds element blocks by (node, DofKind), so a node may carry any mix of kinds; couplings, boundary conditions, loads and results address (node, kind). The explicit system keeps node-major translations, now checked against its layout (NODE_DOFS). Solid formulations declare their ElementDofs. Every static, frequency and explicit result of the solver, explicit, engine and io test suites hashes bit-identically before and after (957 results). docs/fem.md describes the DOF model and the shape-function conventions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * fem, solver: simplify after review Block offsets resolved once per node pair; add_scalar/add_diagonal take the kinds like add_block; one checked Jacobian inverse (dense::positive_inverse) for the solid kernels and the basis library; static natural-node tables; the Hessian path inverts the Jacobian once. 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:
16 files changed
+1805
-192
No files matched your search
@@ -34,7 +34,8 @@
|
||||
|
||||
use modelcraft_model::ElementKind;
|
||||
|
||||
use crate::dense::{Cholesky, Matrix, inverse3};
|
||||
use crate::dense::{Cholesky, Matrix, inverse3, positive_inverse};
|
||||
use crate::dof::{DofSet, ElementDofs, Internal};
|
||||
use crate::material::{Elasticity, stress};
|
||||
use crate::quadrature::{Point, Rule};
|
||||
use crate::shape::{self, MAX_NODES, Shape};
|
||||
@@ -144,6 +145,13 @@ pub enum Formulation {
|
||||
}
|
||||
|
||||
impl Formulation {
|
||||
/// The unknowns of a solid element so formulated: three translations per node, and the
|
||||
/// incompatible modes it condenses out.
|
||||
pub fn dofs(self) -> ElementDofs {
|
||||
let incompatible_modes = if self == Formulation::IncompatibleModes { INCOMPATIBLE_DOFS } else { 0 };
|
||||
ElementDofs { node: DofSet::TRANSLATIONS, internal: Internal { incompatible_modes, pressure: 0 } }
|
||||
}
|
||||
|
||||
/// The formulation a model section asks for, if this element type has it.
|
||||
pub fn from_section(kind: ElementKind, f: &modelcraft_model::Formulation) -> Result<Formulation> {
|
||||
let ty = element_type(kind)?;
|
||||
@@ -220,6 +228,11 @@ impl<'a> Solid<'a> {
|
||||
Ok(Solid { ty, xyz, formulation })
|
||||
}
|
||||
|
||||
/// The element's unknowns, in the order of its matrices.
|
||||
pub fn dofs(&self) -> ElementDofs {
|
||||
self.formulation.dofs()
|
||||
}
|
||||
|
||||
/// The stiffness integration rule.
|
||||
pub fn rule(&self) -> Rule {
|
||||
match (self.formulation, self.ty.reduced) {
|
||||
@@ -249,11 +262,7 @@ impl<'a> Solid<'a> {
|
||||
fn gradients(&self, xi: [f64; 3]) -> Result<Gradients> {
|
||||
let shape = (self.ty.shape)(xi);
|
||||
let j = self.jacobian(&shape);
|
||||
let (inv, det) = inverse3(&j).ok_or_else(|| self.degenerate())?;
|
||||
let norms: f64 = j.iter().map(|r| (r[0] * r[0] + r[1] * r[1] + r[2] * r[2]).sqrt()).product();
|
||||
if !(det > 1e-12 * norms) {
|
||||
return Err(self.degenerate());
|
||||
}
|
||||
let (inv, det) = positive_inverse(&j).ok_or_else(|| self.degenerate())?;
|
||||
let mut dx = [[0.0; 3]; MAX_NODES];
|
||||
for (g, d) in dx.iter_mut().zip(shape.derivatives()) {
|
||||
for b in 0..3 {
|
||||
|
||||
Reference in new issue
Block a user