forked from 1b-agent/fieldcraft-upstream
fem, solver: thermal strain in nonlinear static steps
Temperatures now strain the material in nonlinear steps instead of being refused. At finite strain the deformation gradient splits multiplicatively, F = F_m F_theta with F_theta = diag(1 + alpha_i dT) at each point: the material sees F_m and the loops run on the thermally expanded volume J_theta dV0 (mixed elements project det F_m), so the tangent stays consistent. At small strain the split is additive, and elements on their linear stiffness take the equivalent nodal forces as a dead load. The step's temperatures are its temperature loads, or the steady conduction of its temperature conditions and heat loads (one-way coupling, as the linear coupled step); both at once are refused. They ramp with the load factor from each material's reference temperature. Johnson-Cook plasticity softens at the point's temperature. Verified: free expansion and a fully constrained neo-Hookean block at 20 % thermal stretch, full and hybrid (exact); a bar held at its ends heated past yield (exact); the linear limit and the conducted temperatures against the linear solvers; tangents by central differences with a temperature field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
53adab54c2
commit
3f9e9e30d8
8 files changed
+492
-94
No files matched your search
+29
-13
@@ -11,7 +11,7 @@ use fieldcraft_fem::beam::{Beam, BeamSection};
|
||||
use fieldcraft_fem::constitutive::MaterialModel;
|
||||
use fieldcraft_fem::dense::Matrix;
|
||||
use fieldcraft_fem::element::{Formulation, Solid, ThermalStrain};
|
||||
use fieldcraft_fem::finite::Kinematics;
|
||||
use fieldcraft_fem::finite::{Kinematics, Temperature};
|
||||
use fieldcraft_fem::plane::{Plane, Theory};
|
||||
use fieldcraft_fem::plasticity::J2;
|
||||
use fieldcraft_fem::shell::{Shell, ShellSection};
|
||||
@@ -122,27 +122,36 @@ impl Kernel<'_> {
|
||||
/// Nonlinear internal forces and tangent ([`fieldcraft_fem::finite`]) for the element's
|
||||
/// displacements `u` (its degrees of freedom in order: 3 per node, or 2 for 2D elements),
|
||||
/// total Lagrangian or with small strain.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn nonlinear(
|
||||
&self,
|
||||
m: &MaterialModel,
|
||||
u: &[f64],
|
||||
kin: Kinematics,
|
||||
thermal: Option<&Temperature>,
|
||||
committed: &[f64],
|
||||
updated: &mut [f64],
|
||||
tangent: bool,
|
||||
) -> fieldcraft_fem::Result<fieldcraft_fem::finite::Finite> {
|
||||
match self {
|
||||
Kernel::Solid(s) => s.nonlinear(m, u.as_chunks::<3>().0, kin, committed, updated, tangent),
|
||||
Kernel::Plane(p) => p.nonlinear(m, u.as_chunks::<2>().0, kin, committed, updated, tangent),
|
||||
Kernel::Solid(s) => s.nonlinear(m, u.as_chunks::<3>().0, kin, thermal, committed, updated, tangent),
|
||||
Kernel::Plane(p) => p.nonlinear(m, u.as_chunks::<2>().0, kin, thermal, committed, updated, tangent),
|
||||
}
|
||||
}
|
||||
|
||||
/// Cauchy stress and strain (logarithmic at finite strain) at the integration points, as
|
||||
/// [`Self::nonlinear`].
|
||||
pub fn nonlinear_stresses(&self, m: &MaterialModel, u: &[f64], kin: Kinematics, committed: &[f64]) -> fieldcraft_fem::Result<Vec<PointStress>> {
|
||||
pub fn nonlinear_stresses(
|
||||
&self,
|
||||
m: &MaterialModel,
|
||||
u: &[f64],
|
||||
kin: Kinematics,
|
||||
thermal: Option<&Temperature>,
|
||||
committed: &[f64],
|
||||
) -> fieldcraft_fem::Result<Vec<PointStress>> {
|
||||
match self {
|
||||
Kernel::Solid(s) => s.nonlinear_stresses(m, u.as_chunks::<3>().0, kin, committed),
|
||||
Kernel::Plane(p) => p.nonlinear_stresses(m, u.as_chunks::<2>().0, kin, committed),
|
||||
Kernel::Solid(s) => s.nonlinear_stresses(m, u.as_chunks::<3>().0, kin, thermal, committed),
|
||||
Kernel::Plane(p) => p.nonlinear_stresses(m, u.as_chunks::<2>().0, kin, thermal, committed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,13 +945,7 @@ pub(crate) fn hold(slot: Option<&mut Option<f64>>, value: f64, conflict: impl Fn
|
||||
|
||||
/// Add the equivalent nodal forces of the thermal strains of a temperature field.
|
||||
pub fn add_thermal_loads(f: &mut [f64], mesh: &Mesh, field: &[Option<f64>]) -> Result<()> {
|
||||
for e in &mesh.elements {
|
||||
let Some(th) = e.thermal_strain(field) else { continue };
|
||||
let forces = e.kernel()?.thermal_load(&e.d, &th).map_err(|err| Error::element(e.label, err))?;
|
||||
for (node, v) in e.nodes.iter().zip(forces) {
|
||||
add(f, &translations(mesh, *node)?, v);
|
||||
}
|
||||
}
|
||||
add_element_thermal_loads(f, mesh, field, |_| true)?;
|
||||
for t in &mesh.trusses {
|
||||
let Some(th) = t.thermal_strain(field) else { continue };
|
||||
let forces = t.truss()?.thermal_load(t.young, t.area, &th).map_err(|err| Error::element(t.label, err))?;
|
||||
@@ -953,6 +956,19 @@ pub fn add_thermal_loads(f: &mut [f64], mesh: &Mesh, field: &[Option<f64>]) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add the equivalent nodal forces of the thermal strains of the solid and 2D elements whose
|
||||
/// index `keep` accepts.
|
||||
pub fn add_element_thermal_loads(f: &mut [f64], mesh: &Mesh, field: &[Option<f64>], keep: impl Fn(usize) -> bool) -> Result<()> {
|
||||
for (_, e) in mesh.elements.iter().enumerate().filter(|(i, _)| keep(*i)) {
|
||||
let Some(th) = e.thermal_strain(field) else { continue };
|
||||
let forces = e.kernel()?.thermal_load(&e.d, &th).map_err(|err| Error::element(e.label, err))?;
|
||||
for (node, v) in e.nodes.iter().zip(forces) {
|
||||
add(f, &translations(mesh, *node)?, v);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The uniform force per volume a step's gravity and body forces put on each shell and beam
|
||||
/// element ([`Mesh::structural`] order): beam end forces are recovered with it.
|
||||
pub fn structural_body_forces(model: &Model, resolved: &Resolved, mesh: &Mesh, step: &Step) -> Result<Vec<[f64; 3]>> {
|
||||
|
||||
Reference in new issue
Block a user