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
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 14:29:43 -04:00
1 parent ce8e23d05b
commit bc75fd08d1
14 files changed
+249 -14

No files matched your search

+2 -2
View File
@@ -68,8 +68,8 @@ pub use sections::{BeamProfile, Extent, Formulation, Integration, Section, Secti
pub use sets::Set;
pub use steps::{
ArcConstraint, ArcLength, BulkViscosity, DisplacementLimit, ExplicitDynamic, Hourglass, HourglassKind, Imperfection, InitialVelocity,
MAX_LOAD_PATH_POINTS, MAX_NONLINEAR_INCREMENTS, MAX_NONLINEAR_ITERATIONS, NonlinearStatic, OutputVariable, Procedure, RigidWall, Step,
StressRate, Tolerances, Transient,
MAX_LOAD_PATH_POINTS, MAX_NONLINEAR_INCREMENTS, MAX_NONLINEAR_ITERATIONS, MassScaling, NonlinearStatic, OutputVariable, Procedure, RigidWall,
Step, StressRate, Tolerances, Transient,
};
pub use units::UnitSystem;
+27
View File
@@ -425,6 +425,22 @@ pub struct ExplicitDynamic {
/// The objective stress rate of large deformation.
#[serde(default)]
pub stress_rate: StressRate,
/// Opt-in mass scaling: the elements whose stable increment is below a target get the
/// density that brings them to it (the added mass is reported).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mass_scaling: Option<MassScaling>,
}
/// Mass scaling to a target time increment: every element (of `set`, or of the whole model) whose
/// stable increment, times the step's scale, is below `target_increment` has its density raised
/// by (target / increment)², which brings it to the target. It changes the dynamics: the added
/// mass is reported, per section, with its share of the kinetic energy.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MassScaling {
pub target_increment: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub set: Option<String>,
}
fn yes() -> bool {
@@ -467,6 +483,7 @@ impl ExplicitDynamic {
bulk_viscosity: BulkViscosity::default(),
nonlinear_geometry: true,
stress_rate: StressRate::default(),
mass_scaling: None,
}
}
@@ -487,6 +504,7 @@ impl ExplicitDynamic {
.map(String::as_str)
.chain(self.initial_velocities.iter().map(|v| v.set.as_str()))
.chain(self.rigid_walls.iter().map(|w| w.set.as_str()))
.chain(self.mass_scaling.iter().filter_map(|m| m.set.as_deref()))
}
pub fn validate(&self) -> Result<()> {
@@ -524,6 +542,12 @@ impl ExplicitDynamic {
}
w.friction.validate().map_err(|e| Error::Invalid(format!("rigid wall on set `{}`: {e}", w.set)))?;
}
if let Some(m) = &self.mass_scaling {
positive("mass scaling target increment", m.target_increment)?;
if m.target_increment > end {
return Err(Error::Invalid(format!("the mass scaling target increment {} is longer than the step", m.target_increment)));
}
}
self.hourglass.validate()?;
self.bulk_viscosity.validate()
}
@@ -1012,6 +1036,9 @@ mod tests {
},
ExplicitDynamic { hourglass: Hourglass { kind: HourglassKind::Viscous, coefficient: 2.0 }, ..base.clone() },
ExplicitDynamic { bulk_viscosity: BulkViscosity { linear: -1.0, quadratic: 1.0 }, ..base.clone() },
ExplicitDynamic { mass_scaling: Some(MassScaling { target_increment: 0.0, set: None }), ..base.clone() },
ExplicitDynamic { mass_scaling: Some(MassScaling { target_increment: f64::NAN, set: None }), ..base.clone() },
ExplicitDynamic { mass_scaling: Some(MassScaling { target_increment: 2.0, set: None }), ..base.clone() },
];
for b in bad {
assert!(Procedure::ExplicitDynamic(b.clone()).validate().is_err(), "{b:?}");