solver: mortar contact in nonlinear static steps

Frictionless contact pairs enforced by an augmented Lagrangian: nodal pressures from the
mortar weighted gaps, Uzawa updates after each converged increment until the gaps are within
1e-5 of the element thickness, and the consistent tangent (outer products of the weighted
gap gradients and the pressure-weighted Hessians from the automatic differentiation of the
mortar integrals). Finite sliding searches and integrates on the current configuration every
evaluation; without nonlinear geometry the pairs and their mortar integrals are fixed in the
reference configuration (small sliding, linear and exact). The tangent's pattern takes the
face pairs' blocks; the step cuts back when the active set chatters. Initial overclosure is
offset or refused; general contact, 2D models and arc length are refused by name; the linear
analyses refuse contact by name instead of the mesh.

With contact the convergence force scale comes from the converged states, the loads and the
current state. The loads-and-reactions balance check takes the nodal reactions into its scale
(no spurious warning for a part squeezed between held faces).

Tests: the contact patch test on non-matching hex8/tet4 meshes (exact with small sliding,
to 1e-4 with finite sliding), Hertz cylinder on a flat (peak pressure within 3%, half-width
bracketed) with quadratic Newton convergence, overclosure, thread-count determinism and the
refusals; Hertz sphere on a flat (heavy).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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:58:32 -04:00
1 parent 37f338c9e8
commit 8871f6721d
8 files changed
+1392 -25

No files matched your search

+24
View File
@@ -105,6 +105,30 @@ impl NodeGraph {
NodeGraph { adjacency }
}
/// The graph with more blocks, each coupling all its slots (contact: the slots a contact
/// element couples change with the deformation).
pub fn with_blocks<'a>(&self, blocks: impl Iterator<Item = &'a [usize]>) -> NodeGraph {
let mut adjacency = self.adjacency.clone();
let mut touched = Vec::new();
for b in blocks {
for &a in b {
if let Some(row) = adjacency.get_mut(a) {
row.extend_from_slice(b);
touched.push(a);
}
}
}
touched.sort_unstable();
touched.dedup();
for a in touched {
if let Some(row) = adjacency.get_mut(a) {
row.sort_unstable();
row.dedup();
}
}
NodeGraph { adjacency }
}
/// Nonzeros of the dof-level matrix: a dense block per node pair.
pub fn entries(&self, layout: &DofLayout) -> usize {
self.adjacency.iter().enumerate().map(|(a, row)| layout.range(a).len() * row.iter().map(|b| layout.range(*b).len()).sum::<usize>()).sum()