Explicit dynamics core: central differences, one-point hex8 with hourglass control, nodal-pressure tet4, solve.explicit (M10 X1) (#27)

* Model: explicit dynamic steps (end time, increment scale and cap, output intervals, initial velocities, rigid walls, hourglass and bulk viscosity controls); deck writers refuse them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* fem: uniform-strain hex8 and tet4 kinematics, Flanagan–Belytschko hourglass shape vectors, element frequency bound

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Explicit solver crate (L2): central differences with lumped mass, one-point hex8 with Flanagan–Belytschko hourglass control, Bonet–Burton tet4, bulk viscosity, rigid walls, energy account; wave and impact verification

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Engine: solve.explicit and solve.history commands, explicit results in the session and solve.probe; cantilever period against the modal solver

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Docs: explicit solver in the architecture, roadmap (M10 started), parity and literature (Part H)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Simplify: Model::select_step shared by both solvers, Procedure::sets/is_dynamic, fused per-increment passes without allocation, shared element tail, generic command caps

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Catalogue: solve.explicit is ready; solve.history beside solve.probe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* Rebase onto the implicit couplings work: Procedure is no longer Copy

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:
authored and GitHub committed 2026-10-13 18:28:32 -04:00
1 parent 48039639cb
commit d45a2da5b5
33 files changed
+2757 -65

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
//! What an explicit dynamic run keeps: histories sampled at the history interval and field
//! frames at the output interval.
use serde::Serialize;
/// The energy account at one time (Belytschko, Liu, Moran, Elkhodary 2014, §6.2.3). The works of
/// the internal and external forces are integrated with the trapezoidal rule over each
/// increment's displacement; the constraints' with their impulses (see [`crate::integrate`]).
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize)]
pub struct Energies {
/// ½ vᵀ M v.
pub kinetic: f64,
/// Work of the material stresses: the strain energy for an elastic material.
pub internal: f64,
/// Work of the hourglass forces (stored in the stiffness form, dissipated in the viscous one).
pub hourglass: f64,
/// Work of the bulk viscosity (dissipated).
pub viscous: f64,
/// Work of the loads.
pub external: f64,
/// Work of the boundary conditions and rigid walls on the model.
pub constraint: f64,
/// kinetic + internal + hourglass + viscous − external − constraint − the initial kinetic
/// energy: zero when energy is conserved.
pub balance: f64,
}
impl Energies {
/// The balance relative to the largest energy in the account.
pub fn relative_error(&self, initial_kinetic: f64) -> f64 {
let scale = [initial_kinetic, self.kinetic, self.internal + self.hourglass + self.viscous, self.external.abs(), self.constraint.abs()]
.into_iter()
.fold(0.0, f64::max);
if scale > 0.0 { self.balance.abs() / scale } else { 0.0 }
}
}
/// The fields at one time. Vectors that the step's output does not request are empty.
#[derive(Debug, Clone, PartialEq)]
pub struct Frame {
pub time: f64,
/// Per node in [`ExplicitResult::nodes`] order.
pub displacement: Vec<[f64; 3]>,
pub velocity: Vec<[f64; 3]>,
/// Per element in [`ExplicitResult::elements`] order (one-point elements: one value each):
/// xx, yy, zz, xy, xz, yz, engineering shear strains.
pub stress: Vec<[f64; 6]>,
pub strain: Vec<[f64; 6]>,
}
/// Quantities sampled through the run.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct History {
pub time: Vec<f64>,
/// The time increment taken at each sample.
pub increment: Vec<f64>,
pub energies: Vec<Energies>,
/// Total momentum M v.
pub momentum: Vec<[f64; 3]>,
/// Sum of the forces the boundary conditions and rigid walls exert on the model.
pub constraint_force: Vec<[f64; 3]>,
/// The nodes with histories, and per sample their displacement and velocity (in this order).
pub nodes: Vec<u32>,
pub displacement: Vec<Vec<[f64; 3]>>,
pub velocity: Vec<Vec<[f64; 3]>>,
}
/// An element that limits the stable time increment.
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct ControllingElement {
pub label: u32,
/// Its critical increment at rest (no bulk viscosity), before the scale factor.
pub increment: f64,
}
/// The result of an explicit dynamic step.
#[derive(Debug, Clone, PartialEq)]
pub struct ExplicitResult {
pub step: String,
/// Node labels with degrees of freedom, sorted.
pub nodes: Vec<u32>,
/// Element labels, sorted.
pub elements: Vec<u32>,
pub frames: Vec<Frame>,
pub history: History,
/// The elements with the smallest critical increments (at most ten), smallest first.
pub controlling: Vec<ControllingElement>,
/// The first time increment taken (the scale times the smallest critical increment).
pub initial_increment: f64,
pub increments: u64,
/// The time reached: the step's end time unless the run stopped at the increment cap.
pub time: f64,
pub completed: bool,
pub total_mass: f64,
/// The energy account at the end.
pub energy: Energies,
pub initial_kinetic: f64,
pub warnings: Vec<String>,
}