P6-b: the fictitious added mass for the partitioned loop — rtx-fea NonlinearDynamicStepper::set_added_lumped_mass (a lumped per-DOF mass in the Newmark inertial residual and effective tangent, never the consistent mass or the rest state; zero = the plain stepper bit for bit) with its pin (compensated step reproduces the plain step to 2.5e-9, uncompensated moves it 11 %); the FSI2 overset harness carries RTX_FSI2O_FICT_MASS=α (α × ρ_f π (c/2)² spread over the wetted nodes) and adds the compensating load M_f ü_k of the previous subiterate to every structure solve (predictor and passes), printed in the header
CI / Format Check (push) Failing after 7s
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Clippy Check (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 5s
Performance Benchmarks / Run Benchmarks (push) Failing after 6s
Documentation / Build User Guide (push) Successful in 6s
Documentation / Build API Documentation (push) Failing after 25s
CI / Build CPU-Only (Explicit) (push) Failing after 1m15s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL
This commit is contained in:
Omar Sobh
2026-09-15 08:30:55 -05:00
co-authored by Claude Fable 5.1
parent e54729241d
commit a2086a59de
3 changed files with 289 additions and 5 deletions
@@ -46,13 +46,13 @@
//! the stepper.
use super::{AnalysisConfig, ConvergenceCriteria};
use crate::assembly::SparseMatrix;
use crate::assembly::dof_mapping::{AdvancedDofNumbering, DofComponent, DofMappingStrategy};
use crate::assembly::SparseMatrix;
use crate::boundary::{BoundaryCondition, BoundaryConditionSet};
use crate::elements::total_lagrangian::{self, saint_venant_kirchhoff};
use crate::elements::{ElementMatrixComputer, StandardFiniteElement};
use crate::error::{AnalysisError, FeaResult};
use crate::materials::{MaterialDatabase, reduced_constitutive};
use crate::materials::{reduced_constitutive, MaterialDatabase};
use crate::mesh::{Mesh, NodeId};
use crate::solvers::{BandedLu, LinearSolver, SolverOptions};
use nalgebra::{DMatrix, DVector, Vector3};
@@ -264,6 +264,11 @@ pub struct NonlinearDynamicStepper<'a> {
caches: Vec<ElementCache>,
/// Free-free consistent mass, for consistent initial accelerations.
mass_free: SparseMatrix,
/// A lumped mass added per DOF (global numbering) — the partitioned
/// coupling's fictitious added mass (`set_added_lumped_mass`): it enters
/// the Newmark inertial residual and the effective tangent, never the
/// consistent mass or the rest state. Zero by default.
added_mass: DVector<f64>,
/// The body-force part of the external force (constant).
external_body: DVector<f64>,
/// Body force plus the current nodal forces.
@@ -414,7 +419,9 @@ impl<'a> NonlinearDynamicStepper<'a> {
}
}
let added_mass = DVector::zeros(total_dofs);
let mut stepper = Self {
added_mass,
analysis,
dof_numbering,
free_dofs,
@@ -447,6 +454,20 @@ impl<'a> NonlinearDynamicStepper<'a> {
}
}
/// Set the lumped mass added to every DOF of each node (the coupling
/// loop's fictitious added mass, `docs/overset_metal_campaign.md` §5.17
/// in omni-cortex): `(M + M_f) ü = F + M_f ü_k` contracts at any mass
/// ratio and leaves the fixed point unchanged when the caller adds the
/// load `M_f ü_k` of the previous subiterate. Entries not listed keep
/// their value; `0.0` restores the plain stepper bit for bit.
pub fn set_added_lumped_mass(&mut self, entries: &[(NodeId, f64)]) {
for (node, m) in entries {
for dof in self.dof_numbering.get_node_dofs(*node) {
self.added_mass[dof] = *m;
}
}
}
/// The state at rest under the *current* external force: `u = v = 0`,
/// the acceleration consistent with `M a0 = F_ext - f_int(0)`.
pub fn rest_state(&mut self) -> FeaResult<DynamicState> {
@@ -775,6 +796,13 @@ impl<'a> NonlinearDynamicStepper<'a> {
}
}
if with_tangent {
for (dof, &m) in self.added_mass.iter().enumerate() {
if m != 0.0 {
if let Some(free) = self.free_index[dof] {
tangent.add_entry(free, free, inv_beta_dt2 * m)?;
}
}
}
tangent.finalize()?;
}
Ok((internal, tangent))
@@ -796,6 +824,13 @@ impl<'a> NonlinearDynamicStepper<'a> {
}
}
}
for (dof, &m) in self.added_mass.iter().enumerate() {
if m != 0.0 {
if let Some(free) = self.free_index[dof] {
out[free] += m * a_full[dof];
}
}
}
out
}
}