rtx-fea: wire NonlinearStaticAnalysis — Newton on the consistent tangent, MMS-verified at second order
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
NonlinearStaticAnalysis::run returned DVector::zeros unconditionally, like ModalAnalysis and DynamicAnalysis before their repair. It is now full Newton-Raphson on R(u) = f_ext - f_int(u): - ElementMatrixComputer::compute_internal_force_and_tangent integrates f_int = int(B' sigma dV) and K_T = int(B' D_T B dV) in ONE quadrature sweep from a constitutive closure in the element's reduced Voigt space — computing both together is what keeps the tangent consistent with the stress, which is what quadratic convergence rides on. - materials::reduced_constitutive bridges the Material trait (Voigt-6) to that closure: 3-D passes the total strain straight through; 2-D supports the linear plane-stress closed form and refuses nonlinear materials explicitly, since plane-stress condensation of a general law needs a per-point iteration that is not implemented yet. - Dirichlet DOFs are held at their (load-scaled) values and Newton runs on the free DOFs, so the prescribed motion enters through f_int itself — no K_fc bookkeeping to get wrong. Body force enters via set_body_force, the same hook pattern the CFD solvers use for manufactured solutions. Uniform load stepping; other strategies and quasi-Newton refuse explicitly. - StandardFiniteElement::compute_internal_forces, previously a zeros stub, now delegates to the same machinery. - Mesh::validate is now called in run() (the old TODO), and NonlinearConfig gained a Default. Verified two ways (tests/nonlinear_static.rs): - Equivalence: with LinearElastic the loop lands on the directly assembled linear solution to 1e-10 in exactly one Newton step — same B, quadrature and solver, so any disagreement is the nonlinear assembly. - Manufactured solution with a genuinely nonlinear material (energy W = 1/2 e'De + alpha/3 I1^3, so stress and tangent are exact derivatives; body force by central differences of the closed-form stress): L2 errors 6.032e-2, 1.780e-2, 4.595e-3 on 2/4/8 Hex8 — observed orders 1.76 and 1.95, climbing to the theoretical 2. The forcing contains the nonlinear term, so the order is reachable only if it is solved; an inconsistent tangent is caught separately by the iteration-count bound. This unblocks ECSW model-order reduction, which needs a working nonlinear solve underneath it. 551 rtx-fea tests, 0 failing. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e94ad1be6b
commit
6510045b5d
@@ -272,6 +272,75 @@ impl MaterialResponse {
|
||||
}
|
||||
|
||||
/// Base trait for all material models.
|
||||
/// Constitutive closure in an element's reduced Voigt space, for the
|
||||
/// nonlinear assembly path (`ElementMatrixComputer::
|
||||
/// compute_internal_force_and_tangent`).
|
||||
///
|
||||
/// In 3-D the reduced space *is* the material's full Voigt-6 space, so the
|
||||
/// closure passes the total strain straight to
|
||||
/// [`Material::compute_response`] (from a virgin state — the analyses using
|
||||
/// this are path-independent for now) and hands back its stress and
|
||||
/// consistent tangent.
|
||||
///
|
||||
/// In 2-D the element works in plane stress with 3 strain components, and
|
||||
/// condensing a *general* nonlinear material to plane stress requires a
|
||||
/// per-point iteration on the out-of-plane strain that is not implemented
|
||||
/// yet. A linear material needs no iteration — its plane-stress matrix is
|
||||
/// closed-form from `(E, nu)` — so that case is supported and anything else
|
||||
/// is an explicit error rather than silently wrong physics.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn reduced_constitutive(
|
||||
material: &dyn Material,
|
||||
spatial_dim: usize,
|
||||
) -> FeaResult<
|
||||
Box<dyn Fn(&nalgebra::DVector<f64>) -> FeaResult<(nalgebra::DVector<f64>, DMatrix<f64>)> + '_>,
|
||||
> {
|
||||
match spatial_dim {
|
||||
3 => {
|
||||
let state = material.initialize_state(0.0);
|
||||
Ok(Box::new(move |strain: &nalgebra::DVector<f64>| {
|
||||
let strain6 = Vector6::from_iterator(strain.iter().copied());
|
||||
let response = material.compute_response(&strain6, &state, 0.0)?;
|
||||
if !response.is_valid {
|
||||
return Err(MaterialError::StateUpdateFailed {
|
||||
reason: "material reported an invalid response".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let stress = nalgebra::DVector::from_iterator(6, response.stress.iter().copied());
|
||||
Ok((stress, response.tangent_matrix))
|
||||
}))
|
||||
}
|
||||
2 => {
|
||||
if !material.is_linear() {
|
||||
return Err(MaterialError::UnsupportedModel {
|
||||
model: format!(
|
||||
"plane-stress reduction of nonlinear material '{}'",
|
||||
material.material_type()
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
let properties = material.properties();
|
||||
let (e, nu) = (properties.elastic_modulus, properties.poisson_ratio);
|
||||
let factor = e / (1.0 - nu * nu);
|
||||
let mut d = DMatrix::zeros(3, 3);
|
||||
d[(0, 0)] = factor;
|
||||
d[(1, 1)] = factor;
|
||||
d[(0, 1)] = factor * nu;
|
||||
d[(1, 0)] = factor * nu;
|
||||
d[(2, 2)] = factor * (1.0 - nu) / 2.0;
|
||||
Ok(Box::new(move |strain: &nalgebra::DVector<f64>| {
|
||||
Ok((&d * strain, d.clone()))
|
||||
}))
|
||||
}
|
||||
_ => Err(MaterialError::UnsupportedModel {
|
||||
model: format!("{spatial_dim}-D constitutive reduction"),
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Material: Send + Sync {
|
||||
/// Get material properties.
|
||||
fn properties(&self) -> &MaterialProperties;
|
||||
|
||||
Reference in New Issue
Block a user