From 8819ff787cb8e65747ddfe446a0e92ecb58534c1 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 15 Oct 2026 08:19:10 -0400 Subject: [PATCH] engine: a prescribed displacement or temperature satisfies the loads stage (#356) The setup checklist asked for a load on rubber-seal, driven only by a prescribed displacement, while model.check called it ready. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/engine/src/progress.rs | 15 ++++++++++++++- crates/model/src/bcs.rs | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/progress.rs b/crates/engine/src/progress.rs index 90c40e4..425120b 100644 --- a/crates/engine/src/progress.rs +++ b/crates/engine/src/progress.rs @@ -128,6 +128,8 @@ impl Session { // Models without elements (point masses, fluid domains) need no material or section. let elementless = analysable && m.elements().is_empty(); let modal_only = !m.steps().is_empty() && m.steps().iter().all(|st| matches!(st.procedure, Procedure::Frequency { .. })); + // A model driven by a prescribed displacement or temperature needs no load either. + let driven = m.boundary_conditions().values().any(|bc| bc.drives()); let mut next_given = false; Stage::ALL .into_iter() @@ -137,7 +139,7 @@ impl Session { let optional = match stage { Stage::Prepare => true, Stage::Material | Stage::Section => elementless, - Stage::Loads => modal_only, + Stage::Loads => modal_only || driven, _ => false, }; let command = r.provider(); @@ -234,6 +236,17 @@ mod tests { assert_eq!((status(&s, Stage::Results), to_json(&s.progress())["next"]["command"].clone()), (Next, json!("results.contour"))); } + /// A model driven by a prescribed displacement (rubber-seal) needs no load: the checklist + /// agrees with the model check and offers the solve next. + #[test] + fn a_displacement_driven_model_needs_no_load() { + let mut s = Session::default(); + s.run_script(&crate::sample::rubber_seal()).unwrap(); + assert!(s.model.loads().is_empty()); + assert_eq!(status(&s, Stage::Loads), Optional); + assert_eq!(status(&s, Stage::Solve), Next, "{:?}", s.progress()); + } + #[test] fn model_check_returns_the_checklist() { let mut s = Session::default(); diff --git a/crates/model/src/bcs.rs b/crates/model/src/bcs.rs index 53b8291..2d9f3a5 100644 --- a/crates/model/src/bcs.rs +++ b/crates/model/src/bcs.rs @@ -172,6 +172,16 @@ impl BoundaryCondition { } } + /// Whether it drives the model on its own, as a load does: a non-zero prescribed + /// displacement or a non-zero prescribed temperature (a support only holds). + pub fn drives(&self) -> bool { + match self { + BoundaryCondition::Displacement { values, .. } => values.iter().flatten().any(|v| *v != 0.0), + BoundaryCondition::Temperature { value, .. } => *value != 0.0, + BoundaryCondition::Fixed { .. } | BoundaryCondition::Symmetry { .. } => false, + } + } + /// Whether it prescribes temperatures (rather than motion). pub fn is_thermal(&self) -> bool { matches!(self, BoundaryCondition::Temperature { .. })