Files
OneCAD-Simulation/crates/explicit/src/lib.rs
T
jyvikoandClaude Opus 5.5 bc75fd08d1 explicit: opt-in mass scaling to a target increment, the added mass reported
An explicit step may ask for mass scaling to a target time increment (on
a set, or the whole model): elements whose stable increment is below it
get the density that reaches it, with their lumped masses, frequency
bound and hourglass scales; adiabatic heating keeps the physical
density. The run reports the elements scaled, the added mass in all and
per section with its fraction, a warning, and the added mass's kinetic
energy in the energy account. Verified on a uniform bar scaled to twice
its increment (added mass 3×, increment doubled, a quarter of the
kinetic energy left to the physical mass). ROADMAP and parity note the
hyperelastic materials and mass scaling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
2026-10-14 14:29:43 -04:00

111 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! FieldCraft's explicit dynamics solver: transient response of a model's explicit dynamic step
//! by central-difference time integration.
//!
//! The method is the explicit central-difference scheme of T. Belytschko, W. K. Liu, B. Moran,
//! K. Elkhodary, *Nonlinear Finite Elements for Continua and Structures*, 2nd ed., Wiley 2014
//! (ch. 6, the flowchart of Box 6.1): with a diagonal (lumped) mass M,
//! a = M⁻¹(f_ext − f_int), v^{n+½} = v^{n−½} + Δt^n a^n, u^{n+1} = u^n + Δt^{n+½} v^{n+½},
//! so a time increment is one pass over the elements and a division per degree of freedom.
//!
//! - **System** ([`system`]): the model is resolved; one-point hexahedra and nodal-pressure
//! tetrahedra with their materials, row-sum lumped masses, prescribed velocities, rigid walls,
//! initial velocities and the constant load vector of the step.
//! - **Elements** ([`elements`]): the uniform-strain hexahedron with Flanagan–Belytschko
//! hourglass control (viscous or stiffness), the Bonet–Burton average nodal pressure
//! tetrahedron, von Neumann–Richtmyer/Wilkins bulk viscosity, and the element critical time
//! increments.
//! - **Contact** ([`contact`]): penalty contact between element faces (pairs both ways, single
//! surface and general contact) with Coulomb friction by a regularised return map, a bucket
//! grid search, and the stiffness from the masses and the stable increment.
//! - **Integration** ([`integrate`]): the time loop, kinematic constraints (rigid walls with
//! friction), energy accounting with a balance check, history samples and field frames
//! ([`result`]).
//!
//! With nonlinear geometry (the default) the elements are updated Lagrangian and the material
//! state is integrated in rate form with an objective stress rate (Jaumann by Hughes–Winget,
//! Green–Naghdi by Flanagan–Taylor); materials are hypoelastic or J2 plastic (bilinear,
//! tabulated, Johnson–Cook) with Johnson–Cook damage and element erosion ([`elements`];
//! docs/solvers/literature.md, Parts F and J). Without it the gradients stay those of the
//! undeformed mesh (small displacements).
//!
//! Elements are updated on threads ([`fieldcraft_parallel`]), each owning its state, and their
//! nodal forces are added in label order as a sequential loop adds them, with no parallel
//! reductions, so the same model gives the same numbers bit for bit on any number of threads;
//! sizes, increments and output are capped by [`Limits`].
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)]
// Small fixed-size vector kernels read as the textbook formulas with index loops.
#![allow(clippy::needless_range_loop)]
pub mod contact;
pub mod discrete;
pub mod elements;
pub mod integrate;
pub mod result;
pub mod system;
pub use elements::Failure;
pub use integrate::solve_explicit;
pub use result::{ContactSummary, ControllingElement, Energies, ErodedElement, ExplicitResult, Frame, History, MassScalingSummary};
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Model(#[from] fieldcraft_model::Error),
#[error("invalid input: {0}")]
Invalid(String),
#[error("not supported yet: {0}")]
NotSupported(String),
/// An element kernel failed (inverted geometry, …).
#[error("element {label}: {source}")]
Element { label: u32, source: fieldcraft_fem::Error },
#[error("limit reached: {0}")]
Limit(String),
/// The solution stopped being finite.
#[error("the solution became unstable: {0}")]
Unstable(String),
/// A broken internal invariant (a bug), reported instead of a panic.
#[error("internal error: {0}")]
Internal(String),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
/// A kernel error, naming the element.
pub fn element(label: u32, source: fieldcraft_fem::Error) -> Error {
Error::Element { label, source }
}
}
/// Caps on the size and length of a run.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Limits {
/// Degrees of freedom of the mesh.
pub max_dofs: usize,
/// Time increments; the step's own cap applies too, whichever is lower.
pub max_increments: u64,
/// Numbers kept in field frames and histories together (8 bytes each).
pub max_output_values: usize,
/// Element faces of every contact surface together.
pub max_contact_faces: usize,
}
impl Limits {
pub const MAX_DOFS: usize = 6_000_000;
pub const MAX_INCREMENTS: u64 = fieldcraft_model::steps::MAX_INCREMENTS;
pub const MAX_OUTPUT_VALUES: usize = 50_000_000;
pub const MAX_CONTACT_FACES: usize = 10_000_000;
}
impl Default for Limits {
fn default() -> Limits {
Limits {
max_dofs: Limits::MAX_DOFS,
max_increments: Limits::MAX_INCREMENTS,
max_output_values: Limits::MAX_OUTPUT_VALUES,
max_contact_faces: Limits::MAX_CONTACT_FACES,
}
}
}