forked from 1b-agent/fieldcraft-upstream
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:
33 files changed
+2757
-65
No files matched your search
@@ -121,29 +121,6 @@ pub struct FrequencyOptions {
|
||||
pub shift: Option<f64>,
|
||||
}
|
||||
|
||||
/// The step named `name`, or the only step of the procedure when no name is given; it must be
|
||||
/// of that procedure.
|
||||
fn select_step<'m>(model: &'m Model, name: Option<&str>, wanted: fn(&Procedure) -> bool, what: &str) -> Result<&'m Step> {
|
||||
let step = match name {
|
||||
Some(n) => model.step(n).ok_or_else(|| Error::Invalid(format!("step `{n}` does not exist")))?,
|
||||
None => {
|
||||
let matching: Vec<&Step> = model.steps().iter().filter(|s| wanted(&s.procedure)).collect();
|
||||
match matching.as_slice() {
|
||||
[one] => *one,
|
||||
[] => return Err(Error::Invalid(format!("the model has no {what} step"))),
|
||||
many => {
|
||||
let names: Vec<&str> = many.iter().map(|s| s.name.as_str()).collect();
|
||||
return Err(Error::Invalid(format!("name the step: the model has {what} steps {}", names.join(", "))));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if !wanted(&step.procedure) {
|
||||
return Err(Error::Invalid(format!("step `{}` is a {} step, not a {what} one", step.name, step.procedure.name())));
|
||||
}
|
||||
Ok(step)
|
||||
}
|
||||
|
||||
/// The assembled matrices and the model's mass.
|
||||
struct Assembled {
|
||||
k: Csr,
|
||||
@@ -218,7 +195,7 @@ fn budget(limits: &Limits, graph: &NodeGraph, matrices: usize) -> Budget {
|
||||
/// Solve a linear static step: the step named `step`, or the model's only static step.
|
||||
pub fn solve_static(model: &Model, step: Option<&str>, limits: &Limits) -> Result<StaticResult> {
|
||||
let resolved = model.resolve()?;
|
||||
let step = select_step(model, step, |p| matches!(p, Procedure::Static {}), "static")?;
|
||||
let step = model.select_step(step, |p| matches!(p, Procedure::Static {}), "static")?;
|
||||
let mesh = Mesh::new(model, &resolved, limits.max_dofs)?;
|
||||
let mut warnings: Vec<String> = resolved.warnings.iter().map(ToString::to_string).collect();
|
||||
let graph = NodeGraph::new(mesh.labels.len(), mesh.elements.iter().map(|e| e.nodes.as_slice()));
|
||||
@@ -337,7 +314,7 @@ const TOLERANCE: f64 = 1e-9;
|
||||
/// lowest natural modes within the step's frequency range.
|
||||
pub fn solve_frequency(model: &Model, step: Option<&str>, options: &FrequencyOptions, limits: &Limits) -> Result<FrequencyResult> {
|
||||
let resolved: Resolved = model.resolve()?;
|
||||
let step = select_step(model, step, |p| matches!(p, Procedure::Frequency { .. }), "frequency")?;
|
||||
let step = model.select_step(step, |p| matches!(p, Procedure::Frequency { .. }), "frequency")?;
|
||||
let Procedure::Frequency { modes, min_hz, max_hz } = step.procedure else {
|
||||
return Err(Error::Internal("frequency step".into()));
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user