forked from 1b-agent/fieldcraft-upstream
Implicit solver: fem kernels (tet4/10, hex8, hex20), sparse Cholesky, block Lanczos, solve.* commands (M4) (#23)
* Renumber the layer table for solver crates: fem at L1, solver at L2, fsi at L3 L0 model -> L1 geometry, mesh, fem, decks, render -> L2 solver (explicit, lbm later) -> L3 fsi (later) -> L4 doc -> L5 io -> L6 engine -> L7 ui-egui, mcp -> apps. UI_MIN_LAYER moves to 7, the testkit sits above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * modelcraft-fem: element kernels for tet4, tet10, hex8 (full, incompatible modes), hex20 (full, reduced) Shape functions, Gauss and conical-product quadrature, stiffness, consistent and HRZ-lumped mass, body forces, consistent pressure loads on faces, integration-point stresses and their least-squares extrapolation to nodes, isotropic and orthotropic elasticity. The element table is one row per kind, so wedges, shells and beams are added locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * modelcraft-solver: linear static and frequency analysis with an analytic verification suite Assembly from the resolved model on the node graph, boundary conditions by elimination, consistent pressure, gravity and body-force loads, nested-dissection ordering, sparse up-looking Cholesky with a factor-size cap, reactions, integration-point and nodal-averaged stresses, von Mises; shift-invert block Lanczos with full reorthogonalization for natural modes, participation and effective mass. ADR 0001 records the own-linear-algebra decision (determinism, wasm). Verified against closed forms: displacement and traction patch tests for every element, cantilever tip deflection (Timoshenko) with convergence rates, Lamé cylinder, Kirsch hole (Kt -> 3), cantilever and free-free beam frequencies with six rigid-body modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Engine: solve.static, solve.frequency and solve.probe; results kept in the session Results are stored per step, restored on a failed command and cleared when the model changes. Caps (degrees of freedom, factor entries, Lanczos vectors) may only be lowered; hostile parameters are tested against a solvable session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: implicit solver architecture, roadmap M4, parity and references Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Simplify the solver: shared face integration, block assembly, cached extrapolation - model: one face integrator (nodal::face_normal_weights) for tractions and pressures - fem: mid-side tables from ElementKind::edges, orthotropic via inverse3, symmetric accumulation without a symmetrize pass, incompatible-mode recovery without K_uu, model's coordinate check, dead table rows removed - solver: one pattern search per node pair (block, scalar and diagonal adds), extrapolation built once per element type, reactions from the prescribed rows only, matrices dropped after restriction, the free-body shift keyed on the factorization error, step selection in the solver, Error::Element naming the element - engine: the solver owns the default step and the shift check; the model comparison runs once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: results are stamped with the session revision 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:
33 files changed
+4559
-53
No files matched your search
@@ -0,0 +1,76 @@
|
||||
//! Element faces: consistent nodal forces of surface loads.
|
||||
//!
|
||||
//! A pressure p on a face gives node i the force fᵢ = −p ∫ Nᵢ n dA, where n dA =
|
||||
//! (∂x/∂ξ × ∂x/∂η) dξ dη on the face's own, possibly curved, geometry; faces are numbered so this
|
||||
//! normal points out of the element ([`modelcraft_model::ElementKind::faces`]), and a positive
|
||||
//! pressure pushes in. This is the consistent load vector of the virtual work of the surface load
|
||||
//! (Zienkiewicz, Taylor & Zhu 2013, §6.6), integrated with the model's face rules (exact for a
|
||||
//! uniform pressure on a flat face).
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// ∫ Nᵢ n dA for every node of a face (node coordinates in the face's node order): the model's
|
||||
/// face integration ([`modelcraft_model::nodal::face_normal_weights`]), so pressures and the
|
||||
/// tractions the model distributes use the same rule.
|
||||
pub fn normal_weights(xyz: &[[f64; 3]]) -> Result<Vec<[f64; 3]>> {
|
||||
modelcraft_model::nodal::face_normal_weights(xyz).map_err(|e| Error::Invalid(e.to_string()))
|
||||
}
|
||||
|
||||
/// Consistent nodal forces of a uniform pressure (positive pushes into the element).
|
||||
pub fn pressure(xyz: &[[f64; 3]], magnitude: f64) -> Result<Vec<[f64; 3]>> {
|
||||
Ok(normal_weights(xyz)?.into_iter().map(|w| w.map(|c| -magnitude * c)).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pressure_on_flat_faces_is_the_textbook_split() {
|
||||
// A 2 × 3 quadrilateral in z = 0, normal +z: quad4 shares p A equally, quad8 gives the
|
||||
// corners −A/12 and the mid-sides A/3.
|
||||
let quad = [[0., 0., 0.], [2., 0., 0.], [2., 3., 0.], [0., 3., 0.]];
|
||||
let f = pressure(&quad, 1.0).unwrap();
|
||||
assert!(f.iter().all(|v| (v[2] + 1.5).abs() < 1e-14 && v[0] == 0.0 && v[1] == 0.0), "{f:?}");
|
||||
let mut q8 = quad.to_vec();
|
||||
for (a, b) in [(0, 1), (1, 2), (2, 3), (3, 0)] {
|
||||
q8.push([0, 1, 2].map(|k| (quad[a][k] + quad[b][k]) / 2.0));
|
||||
}
|
||||
let f = pressure(&q8, 1.0).unwrap();
|
||||
for (i, v) in f.iter().enumerate() {
|
||||
let want = if i < 4 { 0.5 } else { -2.0 };
|
||||
assert!((v[2] - want).abs() < 1e-13, "{i}: {v:?}");
|
||||
}
|
||||
// Triangles: tri3 A/3 each; tri6 corners 0, mid-sides A/3.
|
||||
let tri = [[0., 0., 0.], [1., 0., 0.], [0., 1., 0.]];
|
||||
let f = pressure(&tri, 6.0).unwrap();
|
||||
assert!(f.iter().all(|v| (v[2] + 1.0).abs() < 1e-14), "{f:?}");
|
||||
let mut t6 = tri.to_vec();
|
||||
for (a, b) in [(0, 1), (1, 2), (2, 0)] {
|
||||
t6.push([0, 1, 2].map(|k| (tri[a][k] + tri[b][k]) / 2.0));
|
||||
}
|
||||
let f = pressure(&t6, 6.0).unwrap();
|
||||
for (i, v) in f.iter().enumerate() {
|
||||
let want = if i < 3 { 0.0 } else { -1.0 };
|
||||
assert!((v[2] - want).abs() < 1e-13, "{i}: {v:?}");
|
||||
}
|
||||
assert!(pressure(&tri[..2], 1.0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pressure_on_a_curved_face_sums_to_the_projected_area() {
|
||||
// A quadratic face on a quarter cylinder r = 2, z in 0..1, normal outward: the total
|
||||
// pressure force is −p times the projected areas (2 in x and 2 in y).
|
||||
let r: f64 = 2.0;
|
||||
let at = |t: f64, z: f64| [r * t.cos(), r * t.sin(), z];
|
||||
let h = std::f64::consts::FRAC_PI_2;
|
||||
// Corners counter-clockwise seen from outside: (0,0), (0,1)... with the normal outward.
|
||||
let xyz = vec![at(0.0, 0.0), at(h, 0.0), at(h, 1.0), at(0.0, 1.0), at(h / 2.0, 0.0), at(h, 0.5), at(h / 2.0, 1.0), at(0.0, 0.5)];
|
||||
let w = normal_weights(&xyz).unwrap();
|
||||
let total = w.iter().fold([0.0; 3], |a, v| [a[0] + v[0], a[1] + v[1], a[2] + v[2]]);
|
||||
// A quadratic arc interpolates the circle closely; the projected area is exact for the
|
||||
// interpolated surface (its end points are exact).
|
||||
assert!((total[0].abs() - 2.0).abs() < 1e-12 && (total[1].abs() - 2.0).abs() < 1e-12 && total[2].abs() < 1e-12, "{total:?}");
|
||||
assert!(total[0] > 0.0 && total[1] > 0.0, "this ordering points outwards: {total:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user