forked from 1b-agent/fieldcraft-upstream
solver: mortar contact in 2D models
Plane strain, plane stress and axisymmetric models take contact pairs and general (self) contact on the linear edges of tri3 and quad4 elements: edges oriented out of their elements, the edge mortar of fem with the thickness or 2 pi r in the weights, the element's area over the edge's length as the thickness under it, and the in-plane rows of the tangent blocks. Verified by the contact patch test in plane strain and axisymmetric (to 1e-9 small sliding, 1e-4 finite), Hertz's line contact on quad4 edges (Johnson 1985, 4.3; equal to the hex8 model's to 1e-5), and a hybrid rubber C-ring closing on itself that moves node for node as the one-layer solid ring. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
6baa189231
commit
227c4544ed
20 files changed
+1579
-81
No files matched your search
@@ -1,7 +1,8 @@
|
||||
//! `interaction.contact.*`: contact definitions (pairs of surfaces, general contact) and the
|
||||
//! search for surfaces that touch; `interaction.tie*`: ties between non-matching meshes.
|
||||
//!
|
||||
//! A contact's surfaces are sets of element faces ([`fieldcraft_model::contact`]). The commands
|
||||
//! A contact's surfaces are sets of element faces, or of element edges in a 2D model
|
||||
//! ([`fieldcraft_model::contact`]). The commands
|
||||
//! take the friction, penalty scale and initial-penetration choice of the model's form; `edit`
|
||||
//! replaces a definition with the model's JSON form, `delete` removes it.
|
||||
//!
|
||||
|
||||
@@ -388,13 +388,16 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
// Contacts: their surfaces are element faces of solids.
|
||||
// Contacts: their surfaces are element faces of solids, or edges of 2D elements.
|
||||
for (name, c) in &self.contacts {
|
||||
for set in c.surfaces.sets() {
|
||||
let faces = &set_of(set).faces;
|
||||
let solid = |f: &crate::ElementFace| self.elements.get(&f.element).is_some_and(|e| e.kind.dimension() == 3);
|
||||
if faces.is_empty() {
|
||||
d.error(format!("contact `{name}`"), format!("set `{set}` has no element faces (a contact surface is a set of faces)"));
|
||||
if faces.is_empty() && resolved.planar_edges(set).is_empty() {
|
||||
d.error(
|
||||
format!("contact `{name}`"),
|
||||
format!("set `{set}` has no element faces (a contact surface is a set of faces, or of 2D element edges)"),
|
||||
);
|
||||
} else if !faces.iter().all(solid) {
|
||||
d.error(
|
||||
format!("contact `{name}`"),
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
//! A contact is a model definition, active in every step that can take it (explicit dynamics,
|
||||
//! and nonlinear static steps; the linear analyses refuse contact by name). Its
|
||||
//! surfaces are sets with element
|
||||
//! faces, so they survive remeshing like a pressure load does:
|
||||
//! faces (in a 2D model, element edges), so they survive remeshing like a pressure load does:
|
||||
//! - a **pair**: the faces of `main` against those of `secondary`, both ways (each surface's
|
||||
//! nodes are kept out of the other's faces, so neither surface is privileged);
|
||||
//! - **general** contact: every face against every other face of one surface (single-surface or
|
||||
//! self contact, for folding and buckling), or of every exterior face of the model's solid
|
||||
//! elements when no surface is named.
|
||||
//! elements (in a 2D model every exterior edge of its 2D elements) when no surface is named.
|
||||
//!
|
||||
//! **Friction** is Coulomb's law with a coefficient that falls from its static to its kinetic
|
||||
//! value with the slip rate γ̇: μ = μ_k + (μ_s − μ_k) e^{−d γ̇} (the exponential decay of
|
||||
|
||||
+147
-61
@@ -81,17 +81,25 @@
|
||||
//! and that it does not reach through more than half an element (the far side of a thin wall),
|
||||
//! so two faces pressing together are paired both ways, symmetrically: self-contact of folds.
|
||||
//!
|
||||
//! **2D models.** In plane strain, plane stress and axisymmetric models the surfaces are element
|
||||
//! edges (sets' edges; general contact defaults to every exterior edge of the 2D elements),
|
||||
//! integrated by [`fieldcraft_fem::mortar::integrate_edges`] with the out-of-plane measure
|
||||
//! (thickness, or 2π r) in the weights, so A_A, g̃_A and the forces are those of the whole ring
|
||||
//! or slab. Each edge is oriented so that its normal points out of its element; the element's
|
||||
//! thickness under it is its area over the edge's length. Points keep z = 0 and the 2D nodes have
|
||||
//! no Z translation, so the tangent blocks keep their in-plane rows.
|
||||
//!
|
||||
//! **Scope.** Linear solid faces (tri3 and quad4 faces: tet4, wedge6 and hex8 elements) in 3D
|
||||
//! models, with Coulomb friction ([`Interface::friction`]). Quadratic faces, 2D models and
|
||||
//! arc-length steps are refused by name.
|
||||
//! models and linear edges (tri3 and quad4 elements) in 2D ones, with Coulomb friction
|
||||
//! ([`Interface::friction`]). Quadratic faces and edges and arc-length steps are refused by name.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use fieldcraft_fem::contact::{Aabb, Grid, bounds};
|
||||
use fieldcraft_fem::dual::{Dual, Hyper, Real, VARS};
|
||||
use fieldcraft_fem::frame::{Vec3, dot, norm, unit};
|
||||
use fieldcraft_fem::mortar::{area_vector, integrate};
|
||||
use fieldcraft_model::{Contact, ContactSurfaces, ElementFace, ElementKind, Friction, InitialPenetration, Model, Resolved};
|
||||
use fieldcraft_fem::mortar::{Sample, area_vector, edge_area_vector, integrate, integrate_edges};
|
||||
use fieldcraft_model::{Contact, ContactSurfaces, ElementEdge, ElementFace, ElementKind, Extent, Friction, InitialPenetration, Model, Resolved};
|
||||
use fieldcraft_parallel as parallel;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -136,7 +144,8 @@ const GRAIN: usize = 16;
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Face {
|
||||
pub(crate) nodes: Vec<usize>,
|
||||
pub(crate) dofs: Vec<[usize; 3]>,
|
||||
/// X, Y and Z translations per corner (a 2D model's nodes have no Z).
|
||||
pub(crate) dofs: Vec<[Option<usize>; 3]>,
|
||||
/// The nodes of its element (never in contact with the face).
|
||||
owner: Vec<usize>,
|
||||
/// The penetration beyond which a point has passed through, and its element's thickness.
|
||||
@@ -166,6 +175,8 @@ pub struct Interface {
|
||||
fixed: Option<Vec<Vec<Fixed>>>,
|
||||
/// Coulomb friction (frictionless: none).
|
||||
friction: Option<Friction>,
|
||||
/// A 2D model's out-of-plane measure (its surfaces are edges); `None`: solid faces.
|
||||
extent: Option<Extent>,
|
||||
}
|
||||
|
||||
/// A contact's state carried from one converged increment to the next: per secondary node the
|
||||
@@ -277,7 +288,7 @@ pub struct ContactResult {
|
||||
struct PairTerms {
|
||||
/// Mesh slots of the pair's corners (secondary first) and their degrees of freedom.
|
||||
nodes: Vec<usize>,
|
||||
dofs: Vec<[usize; 3]>,
|
||||
dofs: Vec<[Option<usize>; 3]>,
|
||||
/// Secondary node indices (in [`Interface::nodes`]) of the secondary corners.
|
||||
corners: Vec<usize>,
|
||||
/// ∂g̃_a/∂x per secondary corner (24 variables; corner c of the pair at 3c, main corners from 12).
|
||||
@@ -290,20 +301,42 @@ struct PairTerms {
|
||||
/// translations (row-major, node by node).
|
||||
pub type Blocks = Vec<(Vec<usize>, Vec<f64>)>;
|
||||
|
||||
fn position(mesh: &Mesh, u: &[f64], node: usize, dofs: &[usize; 3]) -> Vec3 {
|
||||
fn position(mesh: &Mesh, u: &[f64], node: usize, dofs: &[Option<usize>; 3]) -> Vec3 {
|
||||
let x = mesh.xyz.get(node).copied().unwrap_or_default();
|
||||
[0, 1, 2].map(|k| x[k] + u.get(dofs[k]).copied().unwrap_or(0.0))
|
||||
[0, 1, 2].map(|k| x[k] + dofs[k].and_then(|d| u.get(d)).copied().unwrap_or(0.0))
|
||||
}
|
||||
|
||||
/// The element's volume (hex8, tet4) for the thickness under a face.
|
||||
/// The element's volume (hex8, tet4), or a 2D element's area (tri3, quad4), for the thickness
|
||||
/// under a face or edge.
|
||||
fn volume(kind: ElementKind, x: &[Vec3]) -> Option<f64> {
|
||||
match kind {
|
||||
ElementKind::Hex8 => <[Vec3; 8]>::try_from(x).ok().and_then(|a| fieldcraft_fem::uniform::Hex8::hex8(&a).ok()).map(|k| k.volume),
|
||||
ElementKind::Tet4 => <[Vec3; 4]>::try_from(x).ok().and_then(|a| fieldcraft_fem::uniform::Tet4::tet4(&a).ok()).map(|k| k.volume),
|
||||
ElementKind::Tri3 | ElementKind::Quad4 => {
|
||||
// The shoelace formula over the corners in the XY plane.
|
||||
let doubled: f64 = x.iter().zip(x.iter().cycle().skip(1)).map(|(a, b)| a[0] * b[1] - a[1] * b[0]).sum();
|
||||
Some(0.5 * doubled.abs())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The overlap integration of a face pair (`extent`: `None`) or an edge pair of a 2D model.
|
||||
fn overlap<T: Real>(extent: Option<Extent>, xs: &[[T; 3]], xm: &[[T; 3]], f: impl FnMut(&Sample<T>)) -> Option<()> {
|
||||
match extent {
|
||||
None => integrate(xs, xm, f),
|
||||
Some(e) => integrate_edges(xs, xm, e, f),
|
||||
}
|
||||
}
|
||||
|
||||
/// A face's area vector, or an edge's ([`edge_area_vector`]) in a 2D model.
|
||||
fn area_of(extent: Option<Extent>, x: &[Vec3]) -> Option<Vec3> {
|
||||
match extent {
|
||||
None => area_vector(x),
|
||||
Some(e) => edge_area_vector(x, e),
|
||||
}
|
||||
}
|
||||
|
||||
fn size(x: &[Vec3]) -> f64 {
|
||||
let b = bounds(x, 0.0);
|
||||
(0..3).map(|k| b[1][k] - b[0][k]).fold(0.0, f64::max)
|
||||
@@ -315,37 +348,86 @@ pub(crate) fn faces(model: &Model, resolved: &Resolved, mesh: &Mesh, owner: &str
|
||||
face_list(model, mesh, owner, resolved.sets.get(set).map(|s| s.faces.as_slice()).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// The edges of set `set` of a 2D model for definition `owner`, refused unless they are linear
|
||||
/// edges of analysed 2D elements.
|
||||
fn edges(model: &Model, resolved: &Resolved, mesh: &Mesh, owner: &str, set: &str) -> Result<Vec<Face>> {
|
||||
edge_list(model, mesh, owner, resolved.sets.get(set).map(|s| s.edges.as_slice()).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// The faces `list` for definition `owner`, as [`faces`].
|
||||
fn face_list(model: &Model, mesh: &Mesh, owner: &str, list: &[ElementFace]) -> Result<Vec<Face>> {
|
||||
let mut out = Vec::with_capacity(list.len());
|
||||
for f in list {
|
||||
let at = |what: String| Error::Invalid(format!("{owner}: element {} face {}: {what}", f.element, f.face));
|
||||
let el = mesh.element(f.element).ok_or_else(|| at("the element is not a solid element of the analysis".into()))?;
|
||||
let labels = model.face_nodes(*f).ok_or_else(|| at("no such face".into()))?;
|
||||
if !matches!(labels.len(), 3 | 4) {
|
||||
return Err(Error::NotSupported(format!(
|
||||
"{owner} on the faces of {} elements (element {}) in implicit analyses; it takes the linear faces of tet4, wedge6 and hex8 elements",
|
||||
el.kind.name(),
|
||||
f.element
|
||||
)));
|
||||
}
|
||||
let nodes: Vec<usize> = labels.iter().map(|l| mesh.node(*l).ok_or_else(|| at(format!("node {l} is not analysed")))).collect::<Result<_>>()?;
|
||||
let dofs = nodes
|
||||
.iter()
|
||||
.map(|n| translations(mesh, *n).and_then(|d| <[usize; 3]>::try_from(d).map_err(|_| Error::Internal("solid node translations".into()))))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let x: Vec<Vec3> = nodes.iter().map(|n| mesh.xyz.get(*n).copied().unwrap_or_default()).collect();
|
||||
let area = area_vector(&x).map_or(0.0, norm);
|
||||
let thickness = volume(el.kind, &el.xyz).filter(|v| *v > 0.0 && area > 0.0).map_or(size(&x), |v| v / area);
|
||||
if !(thickness > 0.0 && thickness.is_finite()) {
|
||||
return Err(at("the face has no size".into()));
|
||||
}
|
||||
let shear = (el.d[3][3] + el.d[4][4] + el.d[5][5]) / 3.0;
|
||||
let mut owner = el.nodes.clone();
|
||||
owner.sort_unstable();
|
||||
out.push(Face { nodes, dofs, owner, depth: DEPTH_FRACTION * thickness, thickness, stiffness: PENALTY_FACTOR * 3.0 * shear / thickness });
|
||||
list.iter()
|
||||
.map(|f| {
|
||||
let at = |what: String| Error::Invalid(format!("{owner}: element {} face {}: {what}", f.element, f.face));
|
||||
let labels = model.face_nodes(*f).ok_or_else(|| at("no such face".into()))?;
|
||||
surface_face(mesh, owner, f.element, labels, false, at)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The edges `list` of a 2D model for definition `owner`, as [`edges`].
|
||||
fn edge_list(model: &Model, mesh: &Mesh, owner: &str, list: &[ElementEdge]) -> Result<Vec<Face>> {
|
||||
list.iter()
|
||||
.map(|e| {
|
||||
let at = |what: String| Error::Invalid(format!("{owner}: element {} edge {}: {what}", e.element, e.edge));
|
||||
let labels = model.edge_nodes(*e).ok_or_else(|| at("no such edge".into()))?;
|
||||
surface_face(mesh, owner, e.element, labels, true, at)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// One face (or, `edge`, one edge of a 2D element) of element `element` with nodes `labels`.
|
||||
fn surface_face(mesh: &Mesh, owner: &str, element: u32, labels: Vec<u32>, edge: bool, at: impl Fn(String) -> Error) -> Result<Face> {
|
||||
let el = mesh.element(element).ok_or_else(|| at("the element is not a solid or 2D element of the analysis".into()))?;
|
||||
let (linear, takes) = if edge {
|
||||
(labels.len() == 2 && el.kind.dimension() == 2, "the linear edges of tri3 and quad4 elements")
|
||||
} else {
|
||||
(matches!(labels.len(), 3 | 4) && el.kind.dimension() == 3, "the linear faces of tet4, wedge6 and hex8 elements")
|
||||
};
|
||||
if !linear {
|
||||
let what = if edge { "edges" } else { "faces" };
|
||||
return Err(Error::NotSupported(format!(
|
||||
"{owner} on the {what} of {} elements (element {element}) in implicit analyses; it takes {takes}",
|
||||
el.kind.name()
|
||||
)));
|
||||
}
|
||||
Ok(out)
|
||||
let mut nodes: Vec<usize> = labels.iter().map(|l| mesh.node(*l).ok_or_else(|| at(format!("node {l} is not analysed")))).collect::<Result<_>>()?;
|
||||
let mut x: Vec<Vec3> = nodes.iter().map(|n| mesh.xyz.get(*n).copied().unwrap_or_default()).collect();
|
||||
if edge {
|
||||
// Outward: the edge's normal (t_y, −t_x) away from its element's centroid.
|
||||
let count = el.xyz.len().max(1) as f64;
|
||||
let centroid: Vec3 = std::array::from_fn(|k| el.xyz.iter().map(|p| p[k]).sum::<f64>() / count);
|
||||
let (Some(a), Some(b)) = (x.first(), x.get(1)) else { return Err(at("the edge has no size".into())) };
|
||||
let mid = [0.5 * (a[0] + b[0]) - centroid[0], 0.5 * (a[1] + b[1]) - centroid[1]];
|
||||
if (b[1] - a[1]) * mid[0] - (b[0] - a[0]) * mid[1] < 0.0 {
|
||||
nodes.reverse();
|
||||
x.reverse();
|
||||
}
|
||||
}
|
||||
let dofs = nodes
|
||||
.iter()
|
||||
.map(|n| {
|
||||
let d = translations(mesh, *n)?;
|
||||
match d.as_slice() {
|
||||
[a, b, c] => Ok([Some(*a), Some(*b), Some(*c)]),
|
||||
[a, b] if edge => Ok([Some(*a), Some(*b), None]),
|
||||
_ => Err(Error::Internal("node translations".into())),
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
// The face's area, or the edge's length.
|
||||
let area = match x.as_slice() {
|
||||
[a, b] if edge => norm([b[0] - a[0], b[1] - a[1], 0.0]),
|
||||
_ => area_vector(&x).map_or(0.0, norm),
|
||||
};
|
||||
let thickness = volume(el.kind, &el.xyz).filter(|v| *v > 0.0 && area > 0.0).map_or(size(&x), |v| v / area);
|
||||
if !(thickness > 0.0 && thickness.is_finite()) {
|
||||
return Err(at(format!("the {} has no size", if edge { "edge" } else { "face" })));
|
||||
}
|
||||
let shear = (el.d[3][3] + el.d[4][4] + el.d[5][5]) / 3.0;
|
||||
let mut owner = el.nodes.clone();
|
||||
owner.sort_unstable();
|
||||
Ok(Face { nodes, dofs, owner, depth: DEPTH_FRACTION * thickness, thickness, stiffness: PENALTY_FACTOR * 3.0 * shear / thickness })
|
||||
}
|
||||
|
||||
/// A face pair of small-sliding contact: its mortar integrals in the reference configuration,
|
||||
@@ -401,9 +483,9 @@ struct Found {
|
||||
}
|
||||
|
||||
/// The weighted gaps g̃_a of one face pair (corners `xs`, `xm`) over any scalar.
|
||||
fn weighted_gaps<T: Real>(xs: &[[T; 3]], xm: &[[T; 3]]) -> Option<[T; 4]> {
|
||||
fn weighted_gaps<T: Real>(extent: Option<Extent>, xs: &[[T; 3]], xm: &[[T; 3]]) -> Option<[T; 4]> {
|
||||
let mut g = [T::cst(0.0); 4];
|
||||
integrate(xs, xm, |p| {
|
||||
overlap(extent, xs, xm, |p| {
|
||||
let w = p.weight * p.gap();
|
||||
for a in 0..4 {
|
||||
g[a] = g[a] + w * p.secondary[a];
|
||||
@@ -418,9 +500,9 @@ fn weighted_gaps<T: Real>(xs: &[[T; 3]], xm: &[[T; 3]]) -> Option<[T; 4]> {
|
||||
/// corners from 0, main corners from 12), with the integrals on the current configuration: the
|
||||
/// rate form of the mortar slip, B_a = [∫ Φ_a N_i P, −∫ Φ_a N_j P] (McDevitt & Laursen 2000; Puso &
|
||||
/// Laursen 2004), P = I − n n.
|
||||
fn slip_operator(xs: &[Vec3], xm: &[Vec3]) -> Option<[[[f64; VARS]; 3]; 4]> {
|
||||
fn slip_operator(extent: Option<Extent>, xs: &[Vec3], xm: &[Vec3]) -> Option<[[[f64; VARS]; 3]; 4]> {
|
||||
let mut b = [[[0.0; VARS]; 3]; 4];
|
||||
integrate(xs, xm, |p| {
|
||||
overlap(extent, xs, xm, |p| {
|
||||
let n = p.normal;
|
||||
for a in 0..4 {
|
||||
let wa = p.weight * p.secondary[a];
|
||||
@@ -470,17 +552,20 @@ impl Interface {
|
||||
|
||||
fn new(model: &Model, resolved: &Resolved, mesh: &Mesh, name: &str, contact: &Contact) -> Result<Interface> {
|
||||
let owner = format!("contact `{name}`");
|
||||
let extent = mesh.theory.map(|t| t.extent());
|
||||
// A set's faces or (2D) edges, or every exterior one of the model.
|
||||
let surface = |set: Option<&String>| match (set, extent) {
|
||||
(Some(s), None) => faces(model, resolved, mesh, &owner, s),
|
||||
(Some(s), Some(_)) => edges(model, resolved, mesh, &owner, s),
|
||||
(None, None) => face_list(model, mesh, &owner, &model.exterior_faces()),
|
||||
(None, Some(_)) => edge_list(model, mesh, &owner, &model.exterior_edges()),
|
||||
};
|
||||
let (secondary, main) = match &contact.surfaces {
|
||||
ContactSurfaces::Pair { main, secondary } => {
|
||||
(faces(model, resolved, mesh, &owner, secondary)?, faces(model, resolved, mesh, &owner, main)?)
|
||||
}
|
||||
ContactSurfaces::Pair { main, secondary } => (surface(Some(secondary))?, surface(Some(main))?),
|
||||
// Single surface: every face against every other one that faces it and shares no node
|
||||
// with its element (its neighbours), both ways.
|
||||
ContactSurfaces::General { surface } => {
|
||||
let list = match surface {
|
||||
Some(set) => faces(model, resolved, mesh, &owner, set)?,
|
||||
None => face_list(model, mesh, &owner, &model.exterior_faces())?,
|
||||
};
|
||||
ContactSurfaces::General { surface: set } => {
|
||||
let list = surface(set.as_ref())?;
|
||||
(list.clone(), list)
|
||||
}
|
||||
};
|
||||
@@ -493,7 +578,7 @@ impl Interface {
|
||||
// ∫ Φ_a over the face itself (the face against its own mirror image).
|
||||
let mirror: Vec<Vec3> = x.iter().rev().copied().collect();
|
||||
let mut own = [0.0; 4];
|
||||
integrate(&x, &mirror, |p| {
|
||||
overlap(extent, &x, &mirror, |p| {
|
||||
for a in 0..4 {
|
||||
own[a] += p.weight * p.secondary[a];
|
||||
}
|
||||
@@ -528,6 +613,7 @@ impl Interface {
|
||||
offset,
|
||||
fixed: None,
|
||||
friction: (!contact.friction.is_frictionless()).then_some(contact.friction),
|
||||
extent,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -609,7 +695,7 @@ impl Interface {
|
||||
.filter_map(|f| {
|
||||
let xm = xm.get(f.main)?;
|
||||
let (mut d, mut m, mut normal) = ([[0.0; 4]; 4], [[0.0; 4]; 4], [0.0; 3]);
|
||||
integrate(xs, xm, |p| {
|
||||
overlap(self.extent, xs, xm, |p| {
|
||||
normal = p.normal;
|
||||
for a in 0..4 {
|
||||
for i in 0..4 {
|
||||
@@ -631,12 +717,12 @@ impl Interface {
|
||||
let main_x: Vec<Vec<Vec3>> = self.main.iter().map(|f| Interface::corners(mesh, u, f)).collect();
|
||||
let boxes: Vec<Aabb> = self.main.iter().zip(&main_x).map(|(f, x)| bounds(x, reach * f.depth)).collect();
|
||||
let grid = Grid::new(&boxes, 4 * boxes.len() + 64);
|
||||
let main_normals: Vec<Option<Vec3>> = main_x.iter().map(|x| area_vector(x).filter(|a| norm(*a) > 0.0)).collect();
|
||||
let main_normals: Vec<Option<Vec3>> = main_x.iter().map(|x| area_of(self.extent, x).filter(|a| norm(*a) > 0.0)).collect();
|
||||
Ok(parallel::map_grained(self.secondary.len(), GRAIN, |i| {
|
||||
let mut found = Vec::new();
|
||||
let Some(f) = self.secondary.get(i) else { return keep(&[], &main_x, found) };
|
||||
let xs = Interface::corners(mesh, u, f);
|
||||
let Some(ns) = area_vector(&xs).filter(|a| norm(*a) > 0.0) else { return keep(&xs, &main_x, found) };
|
||||
let Some(ns) = area_of(self.extent, &xs).filter(|a| norm(*a) > 0.0) else { return keep(&xs, &main_x, found) };
|
||||
let mut candidates = Vec::new();
|
||||
grid.candidates_in(&bounds(&xs, 0.0), &mut candidates);
|
||||
for &c in &candidates {
|
||||
@@ -649,7 +735,7 @@ impl Interface {
|
||||
continue;
|
||||
}
|
||||
let (mut gap, mut support, mut deepest, mut any) = ([0.0; 4], [0.0; 4], f64::INFINITY, false);
|
||||
integrate(&xs, xm, |p| {
|
||||
overlap(self.extent, &xs, xm, |p| {
|
||||
any = true;
|
||||
let g = p.gap();
|
||||
deepest = deepest.min(g);
|
||||
@@ -741,7 +827,7 @@ impl Interface {
|
||||
slipping: Vec::new(),
|
||||
};
|
||||
for (f, corners) in self.secondary.iter().zip(&corner_index) {
|
||||
let Some(a) = area_vector(&Interface::corners(mesh, u, f)) else { continue };
|
||||
let Some(a) = area_of(self.extent, &Interface::corners(mesh, u, f)) else { continue };
|
||||
for i in corners {
|
||||
if let Some(v) = state.normal.get_mut(*i) {
|
||||
(0..3).for_each(|k| v[k] += a[k]);
|
||||
@@ -786,7 +872,7 @@ impl Interface {
|
||||
((0..corners.len()).map(|a| fixed.gradient(a)).collect(), None)
|
||||
} else if tangent {
|
||||
let (vs, vm) = variables(&xs, &xm, Hyper::var);
|
||||
let Some(g) = weighted_gaps(&vs, &vm) else { return Ok(None) };
|
||||
let Some(g) = weighted_gaps(self.extent, &vs, &vm) else { return Ok(None) };
|
||||
let mut h = vec![0.0; fieldcraft_fem::dual::PACKED];
|
||||
for (ga, p) in g.iter().zip(&pressure) {
|
||||
h.iter_mut().zip(&ga.h).for_each(|(x, y)| *x += p * y);
|
||||
@@ -794,7 +880,7 @@ impl Interface {
|
||||
(g.iter().take(corners.len()).map(|ga| ga.g).collect::<Vec<_>>(), Some(h))
|
||||
} else {
|
||||
let (vs, vm) = variables(&xs, &xm, Dual::var);
|
||||
let Some(g) = weighted_gaps(&vs, &vm) else { return Ok(None) };
|
||||
let Some(g) = weighted_gaps(self.extent, &vs, &vm) else { return Ok(None) };
|
||||
(g.iter().take(corners.len()).map(|ga| ga.g).collect(), None)
|
||||
};
|
||||
let nodes = f.nodes.iter().chain(&m.nodes).copied().collect();
|
||||
@@ -815,7 +901,7 @@ impl Interface {
|
||||
let mut on = [0.0; 3];
|
||||
for k in 0..3 {
|
||||
let f = -p * gv[k];
|
||||
if let Some(x) = internal.get_mut(dofs[k]) {
|
||||
if let Some(x) = dofs[k].and_then(|d| internal.get_mut(d)) {
|
||||
*x += f;
|
||||
}
|
||||
on[k] = -f;
|
||||
@@ -917,7 +1003,7 @@ impl Interface {
|
||||
let (rs, rm) = (Interface::corners(mesh, reference, f), Interface::corners(mesh, reference, m));
|
||||
let operator: Vec<[[f64; VARS]; 3]> = match self.fixed.as_ref().and_then(|f| f.get(s)).and_then(|l| l.get(k)) {
|
||||
Some(fixed) => (0..corners.len()).map(|a| fixed.slip_jacobian(a)).collect(),
|
||||
None => slip_operator(&xs, &xm)?.into_iter().take(corners.len()).collect(),
|
||||
None => slip_operator(self.extent, &xs, &xm)?.into_iter().take(corners.len()).collect(),
|
||||
};
|
||||
let mut delta = [0.0; VARS];
|
||||
for (i, (x, r)) in xs.iter().zip(&rs).enumerate() {
|
||||
@@ -929,7 +1015,7 @@ impl Interface {
|
||||
let slips: Vec<(Vec3, [[f64; VARS]; 3])> =
|
||||
operator.into_iter().map(|b| (b.map(|row| row.iter().zip(&delta).map(|(x, y)| x * y).sum()), b)).collect();
|
||||
let nodes: Vec<usize> = f.nodes.iter().chain(&m.nodes).copied().collect();
|
||||
let dofs: Vec<[usize; 3]> = f.dofs.iter().chain(&m.dofs).copied().collect();
|
||||
let dofs: Vec<[Option<usize>; 3]> = f.dofs.iter().chain(&m.dofs).copied().collect();
|
||||
Some((nodes, dofs, corners.clone(), slips))
|
||||
});
|
||||
let terms: Vec<_> = terms.into_iter().flatten().collect();
|
||||
@@ -997,7 +1083,7 @@ impl Interface {
|
||||
for (c, d) in dofs.iter().enumerate() {
|
||||
let on: Vec3 = std::array::from_fn(|l| (0..3).map(|k| t[k] * jac[k].get(var(corners.len(), c, l)).copied().unwrap_or(0.0)).sum());
|
||||
for l in 0..3 {
|
||||
if let Some(x) = internal.get_mut(d[l]) {
|
||||
if let Some(x) = d[l].and_then(|d| internal.get_mut(d)) {
|
||||
*x -= on[l];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ use crate::constraints::Reduction;
|
||||
use crate::contact::{self, ContactResult, Interface};
|
||||
use crate::ldlt::{Kind, Symmetric};
|
||||
use crate::sparse::{Csr, NodeGraph, add_block, add_entry};
|
||||
use crate::system::{Mesh, add_element_thermal_loads, loads, temperature_loads, translations};
|
||||
use crate::system::{Mesh, add_element_thermal_loads, loads, temperature_loads, translation_block, translations};
|
||||
use crate::{Error, Limits, Result};
|
||||
|
||||
/// One Newton iteration of an increment.
|
||||
@@ -445,8 +445,10 @@ impl Problem<'_> {
|
||||
}
|
||||
});
|
||||
if let Some(k) = k.as_mut() {
|
||||
// Contact blocks are over X, Y and Z per node; a 2D model keeps the in-plane rows.
|
||||
let kinds = self.mesh.translation_kinds();
|
||||
for (nodes, data) in &blocks {
|
||||
add_block(k, layout, nodes, self.mesh.translation_kinds(), data)?;
|
||||
add_block(k, layout, nodes, kinds, &translation_block(data, nodes.len(), kinds))?;
|
||||
}
|
||||
}
|
||||
let mut history = Vec::with_capacity(self.mesh.elements.len());
|
||||
@@ -945,13 +947,10 @@ fn check_supported(model: &Model, mesh: &Mesh, step: &Step, settings: &Nonlinear
|
||||
return Err(at(format!("centrifugal loads (load `{name}`), which depend on the deformed position")));
|
||||
}
|
||||
plasticity(model, step, settings, warnings)?;
|
||||
if let Some(name) = model.contacts().keys().next() {
|
||||
if mesh.theory.is_some() {
|
||||
return Err(at(format!("contact (`{name}`) in 2D models")));
|
||||
}
|
||||
if settings.arc_length.is_some() {
|
||||
return Err(at(format!("contact (`{name}`) with arc-length control")));
|
||||
}
|
||||
if let Some(name) = model.contacts().keys().next()
|
||||
&& settings.arc_length.is_some()
|
||||
{
|
||||
return Err(at(format!("contact (`{name}`) with arc-length control")));
|
||||
}
|
||||
if settings.nonlinear_geometry
|
||||
&& let Some(name) = model.couplings().keys().next()
|
||||
|
||||
@@ -700,3 +700,109 @@ fn a_rubber_bellows_folds_onto_itself() {
|
||||
assert!(c.closed() > 0, "the folds touch");
|
||||
assert!(c.penetration < 1e-3 * wall, "{}", c.penetration);
|
||||
}
|
||||
|
||||
/// The C-ring of [`c_ring`] as a plane strain model (quad4, hybrid, 0.2 thick: the hex8 layer's
|
||||
/// thickness) with general contact on every exterior edge.
|
||||
fn planar_c_ring(closing: f64, threads: usize) -> (Blocks, StaticResult) {
|
||||
let mut b = Blocks::default();
|
||||
let (n, open) = (36usize, 5f64.to_radians());
|
||||
let angle = move |i: f64| open + i * (2.0 * std::f64::consts::PI - 2.0 * open);
|
||||
let label = |i: usize, j: usize| 1 + (i + (n + 1) * j) as u32;
|
||||
for j in 0..=2 {
|
||||
for i in 0..=n {
|
||||
let (t, r) = (angle(i as f64 / n as f64), 1.5 - 0.25 * j as f64);
|
||||
let x = [r * t.cos(), r * t.sin(), 0.0];
|
||||
b.model.set_node(label(i, j), x).unwrap();
|
||||
b.xyz.insert(label(i, j), x);
|
||||
}
|
||||
}
|
||||
let mut next = 0;
|
||||
for j in 0..2 {
|
||||
for i in 0..n {
|
||||
next += 1;
|
||||
// Counter-clockwise in the plane: along the outer arc, then inwards and back.
|
||||
b.model.set_element(next, ElementKind::Quad4, vec![label(i, j), label(i + 1, j), label(i + 1, j + 1), label(i, j + 1)]).unwrap();
|
||||
}
|
||||
}
|
||||
let all: Vec<u32> = b.model.elements().keys().copied().collect();
|
||||
b.model.add_set("all", Set { elements: all, ..Set::default() }).unwrap();
|
||||
let rubber = Material {
|
||||
elastic: Some(Elastic::Isotropic { young: 3.0, poisson: 0.499 }),
|
||||
hyperelastic: Some(Hyperelastic::MooneyRivlin { c01: 0.1 }),
|
||||
..Material::default()
|
||||
};
|
||||
b.model.add_material("Rubber", rubber).unwrap();
|
||||
let section = Section {
|
||||
set: "all".into(),
|
||||
material: "Rubber".into(),
|
||||
kind: SectionKind::PlaneStrain { thickness: 0.2 },
|
||||
formulation: Formulation { hybrid: true, ..Formulation::default() },
|
||||
};
|
||||
b.model.add_section("ring", section).unwrap();
|
||||
let settings = NonlinearStatic { initial_increment: 0.1, max_increments: 200, ..NonlinearStatic::default() };
|
||||
b.model.add_step(Step::new("Step", Procedure::NonlinearStatic(settings)), None).unwrap();
|
||||
b.model.add_contact("self", Contact::new(ContactSurfaces::General { surface: None })).unwrap();
|
||||
let step = 2.0 * std::f64::consts::PI - 2.0 * open;
|
||||
let at = |x: [f64; 3], i: f64| {
|
||||
((x[0] * x[0] + x[1] * x[1]).sqrt() - 1.5).abs() < 1e-9
|
||||
&& (x[1].atan2(x[0]).rem_euclid(2.0 * std::f64::consts::PI) - angle(i)).abs() < 1e-6 * step
|
||||
};
|
||||
let near_end = 2.0 / n as f64;
|
||||
b.bc("upper", [None, Some(-closing), None], move |x| at(x, near_end));
|
||||
b.bc("lower", [None, Some(closing), None], move |x| at(x, 1.0 - near_end));
|
||||
b.bc("back", [Some(0.0), Some(0.0), None], move |x| at(x, 0.5));
|
||||
let r = fieldcraft_parallel::with_threads(threads, || solve_nonlinear(&b.model, Some("Step"), &Limits::default()).unwrap());
|
||||
(b, r)
|
||||
}
|
||||
|
||||
/// 2D self-contact: the plane strain ring closes on itself like the one-layer solid ring with
|
||||
/// its z held (the same problem), the edges' mortar pressing as the faces' does; bit for bit on
|
||||
/// any thread count.
|
||||
#[test]
|
||||
fn a_plane_strain_ring_closes_on_itself_as_the_solid_ring_does() {
|
||||
let (b2, r2) = planar_c_ring(0.15, 1);
|
||||
let (b3, r3) = c_ring(true, 0.15);
|
||||
let (c2, c3) = (&r2.contacts[0], &r3.contacts[0]);
|
||||
assert!(c2.closed() > 0 && c2.penetration < 1e-4, "{} {}", c2.closed(), c2.penetration);
|
||||
// Each edge node closes where its face nodes do (two per edge node, at z = 0 and 0.2).
|
||||
assert_eq!(2 * c2.closed(), c3.closed());
|
||||
// The same motion: every node of the plane against the solid's node at z = 0.
|
||||
let mut checked = 0;
|
||||
for (l, x) in &b2.xyz {
|
||||
let Some((m, _)) = b3.xyz.iter().find(|(_, y)| (y[0] - x[0]).abs() < 1e-9 && (y[1] - x[1]).abs() < 1e-9 && y[2] == 0.0) else { continue };
|
||||
let (u, v) = (r2.displacement[r2.nodes.binary_search(l).unwrap()], r3.displacement[r3.nodes.binary_search(m).unwrap()]);
|
||||
assert!((u[0] - v[0]).abs() < 1e-5 && (u[1] - v[1]).abs() < 1e-5, "node {l}: {u:?} {v:?}");
|
||||
checked += 1;
|
||||
}
|
||||
assert_eq!(checked, b2.xyz.len());
|
||||
let (_, many) = planar_c_ring(0.15, 4);
|
||||
assert_eq!(r2.displacement, many.displacement);
|
||||
assert_eq!(r2.contacts, many.contacts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_2d_contact_refuses() {
|
||||
// Quadratic edges.
|
||||
let (mut b, _) = planar_c_ring(0.0, 1);
|
||||
let mut m = Model::default();
|
||||
for (i, x) in [[0., 0.], [1., 0.], [1., 1.], [0., 1.], [0.5, 0.], [1., 0.5], [0.5, 1.], [0., 0.5]].iter().enumerate() {
|
||||
m.set_node(i as u32 + 1, [x[0], x[1], 0.0]).unwrap();
|
||||
}
|
||||
m.set_element(1, ElementKind::Quad8, (1..=8).collect()).unwrap();
|
||||
m.add_set("all", Set { elements: vec![1], ..Set::default() }).unwrap();
|
||||
m.add_material("Mat", Material { elastic: Some(Elastic::Isotropic { young: 1.0, poisson: 0.3 }), ..Material::default() }).unwrap();
|
||||
m.add_section(
|
||||
"s",
|
||||
Section { set: "all".into(), material: "Mat".into(), kind: SectionKind::PlaneStrain { thickness: 1.0 }, formulation: Formulation::default() },
|
||||
)
|
||||
.unwrap();
|
||||
m.add_step(Step::new("Step", Procedure::NonlinearStatic(NonlinearStatic::default())), None).unwrap();
|
||||
m.add_contact("self", Contact::new(ContactSurfaces::General { surface: None })).unwrap();
|
||||
let e = solve_nonlinear(&m, Some("Step"), &Limits::default()).unwrap_err().to_string();
|
||||
assert!(e.contains("edges of quad8") && e.contains("tri3 and quad4"), "{e}");
|
||||
// A plane strain ring with arc-length control.
|
||||
let arc = NonlinearStatic { arc_length: Some(ArcLength::default()), ..NonlinearStatic::default() };
|
||||
b.model.replace_step(Step { procedure: Procedure::NonlinearStatic(arc), ..b.model.step("Step").unwrap().clone() }).unwrap();
|
||||
let e = solve_nonlinear(&b.model, Some("Step"), &Limits::default()).unwrap_err().to_string();
|
||||
assert!(e.contains("arc-length"), "{e}");
|
||||
}
|
||||
+21
-4
@@ -468,8 +468,20 @@ contact patch test. General (single-surface) contact uses one face list, a set's
|
||||
exterior face of the solids, as both sides: each face against every other one facing it that
|
||||
shares no node with its element (its neighbours) and that it does not reach through more than
|
||||
half an element (the far side of a thin wall), paired both ways, so the folds of one body press
|
||||
on each other symmetrically. Linear faces (tri3, quad4: tet4, wedge6, hex8); quadratic faces, 2D
|
||||
models and arc-length steps are refused by name.
|
||||
on each other symmetrically. Linear faces (tri3, quad4: tet4, wedge6, hex8), and in 2D models
|
||||
(plane strain, plane stress, axisymmetric) the linear edges of tri3 and quad4 elements (sets'
|
||||
edges; general contact defaults to every exterior edge); quadratic faces and edges and
|
||||
arc-length steps are refused by name.
|
||||
|
||||
**2D models**: each edge is oriented with its normal (t_y, −t_x) out of its element; the main
|
||||
edge is projected along the secondary edge's normal onto its line, which maps main points to the
|
||||
secondary parameter linearly, so the overlap is an interval integrated with three-point
|
||||
Gauss–Legendre (exact for the degree-3 Φ_a N_b r of the axisymmetric patch test). The
|
||||
out-of-plane measure (the thickness, or 2πr at the secondary point) is in the weights, so A_A,
|
||||
g̃_A and the forces are totals over the slab or the ring, as the 2D elements' are (B. Yang,
|
||||
T. A. Laursen, X. Meng, IJNME 62:1183–1225, 2005). Points keep z = 0 and the same 24 variables
|
||||
as faces, and the tangent blocks keep their in-plane rows. The element's thickness under an edge
|
||||
is its area over the edge's length.
|
||||
|
||||
**Segmentation** (`fieldcraft_fem::mortar`): both faces are projected along the secondary
|
||||
face's unit normal onto its plane, the main polygon is clipped by the secondary one
|
||||
@@ -540,7 +552,9 @@ entries, and the results display draws `contact_pressure`, `contact_gap`, `conta
|
||||
`contact_shear` and `contact_slip`.
|
||||
|
||||
**Verification** (`crates/solver/tests/contact.rs`, `tests/oracle/nl-contact-patch`,
|
||||
`tests/oracle/nl-hertz-cylinder`): the contact patch test on non-matching hex8 and tet4 meshes
|
||||
`tests/oracle/nl-hertz-cylinder`, and their 2D forms `nl-contact-patch-plane-strain`,
|
||||
`nl-contact-patch-axisymmetric`, `nl-hertz-cylinder-plane-strain`): the contact patch test on
|
||||
non-matching hex8, tet4 and (plane strain and axisymmetric) quad4 meshes
|
||||
(uniform σ_zz, pressure equal to it, no lateral stress: to 10⁻⁹ with small sliding, 10⁻⁴ with
|
||||
finite sliding, where the gaps the augmentation leaves enter through their geometric terms);
|
||||
Hertz's line contact, plane strain (peak pressure within 0.3%, the pressure at two nodes within
|
||||
@@ -550,7 +564,10 @@ Newton's quadratic rate near the solution with finite sliding; Coulomb friction
|
||||
blocks (stick well below the cone with the forces of glued blocks, slip on it carrying μ N to 0.1%
|
||||
with small sliding and 3% with finite sliding, where the block turns a little, and μ_k N once
|
||||
slipping; `tests/oracle/nl-friction-sliding-blocks`); initial overclosure adjusted and
|
||||
refused; bit-for-bit results on one and four threads; the refusals.
|
||||
refused; bit-for-bit results on one and four threads; the refusals. In 2D: the patch tests pass
|
||||
to 10⁻⁹ (small sliding) and 10⁻⁴ (finite), Hertz's line contact on quad4 edges matches the
|
||||
hex8 model to 10⁻⁵, and a hybrid Mooney–Rivlin C-ring closing on itself in plane strain moves as
|
||||
the one-layer solid ring with z held, node for node to 10⁻⁵.
|
||||
|
||||
## Ties (non-matching meshes)
|
||||
|
||||
|
||||
+43
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the policy is [verification.md](verification.md). Every case is our own model, built with FieldCraft commands from the problem's published description, and compared with the reference value its `expected.json` cites, within the stated tolerance and for the stated reason. Accepted results are frozen in each case's `fixture.json`: a later change that moves one beyond its band (10⁻⁹ of its size) fails CI even while the reference still passes. No reference value was produced with a commercial product.
|
||||
|
||||
**232 of 238 quantities pass** (6 known failure(s)) in 66 active cases: 57 theory, 9 published benchmark, 0 experiment; 10 pending cases wait for capabilities still being built.
|
||||
**270 of 276 quantities pass** (6 known failure(s)) in 70 active cases: 61 theory, 9 published benchmark, 0 experiment; 10 pending cases wait for capabilities still being built.
|
||||
|
||||
## Theory: closed-form solutions, patch tests and mesh invariants
|
||||
|
||||
@@ -95,6 +95,36 @@ Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the
|
||||
| [nl-contact-patch](../tests/oracle/nl-contact-patch/) | Finite: the contact pressure at the secondary node (0.5, 0.5, 1) over −σ_zz of element 19 | 1 | 0.99998 | -0.00172% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch](../tests/oracle/nl-contact-patch/) | Finite: the integrated contact pressure over the reaction at the pushed top | 1 | 1 | < 1e-7% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch](../tests/oracle/nl-contact-patch/) | Small: σ_zz of element 19 (uniaxial stress E ε = −10 for the 1% squeeze) | -10 | -9.9996 | 0.00426% | 0.01% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: σ_yy of element 4 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: σ_yy of element 6 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: σ_yy of element 1 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: σ_yy of element 10 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: σ_xx of element 6 over its σ_yy (uniaxial stress: 0) | 0 | ≈ 0 | ≈ 0 | ± 1.0000e-6 | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: the contact pressure at the secondary node (1.5, 1) over −σ_yy of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: the integrated contact pressure over the reaction at the pushed top | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: σ_yy of element 4 over that of element 7 | 1 | 1 | 0.0000126% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: σ_yy of element 6 over that of element 7 | 1 | 0.99997 | -0.00325% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: σ_yy of element 1 over that of element 7 | 1 | 0.99999 | -0.00149% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: σ_yy of element 10 over that of element 7 | 1 | 0.99998 | -0.00185% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: σ_xx of element 6 over its σ_yy (uniaxial stress: 0) | 0 | -6.2023e-6 | -6.2e-6 | ± 2.0000e-4 | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: the contact pressure at the secondary node (1.5, 1) over −σ_yy of element 7 | 1 | 0.99997 | -0.00285% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Finite: the integrated contact pressure over the reaction at the pushed top | 1 | 1 | < 1e-7% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/) | Small: σ_yy of element 7 (uniaxial stress E ε = −10 for the 1% squeeze) | -10 | -9.9996 | 0.00426% | 0.01% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: σ_yy of element 4 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: σ_yy of element 6 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: σ_yy of element 1 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: σ_yy of element 10 over that of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: σ_xx of element 6 over its σ_yy (uniaxial stress: 0) | 0 | ≈ 0 | ≈ 0 | ± 1.0000e-6 | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: the contact pressure at the secondary node (0.5, 1) over −σ_yy of element 7 | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: the integrated contact pressure over the reaction at the pushed top | 1 | 1 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: σ_yy of element 4 over that of element 7 | 1 | 1 | 0.0000708% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: σ_yy of element 6 over that of element 7 | 1 | 0.99999 | -0.00087% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: σ_yy of element 1 over that of element 7 | 1 | 1 | -0.000312% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: σ_yy of element 10 over that of element 7 | 1 | 1 | -0.000428% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: σ_xx of element 6 over its σ_yy (uniaxial stress: 0) | 0 | ≈ 0 | ≈ 0 | ± 2.0000e-4 | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: the contact pressure at the secondary node (0.5, 1) over −σ_yy of element 7 | 1 | 0.99999 | -0.00075% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Finite: the integrated contact pressure over the reaction at the pushed top | 1 | 1 | < 1e-7% | 0.02% | pass | matches |
|
||||
| [nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/) | Small: σ_yy of element 7 (plane strain uniaxial stress E ε/(1 − ν²) = −10.989 for the 1% squeeze) | -10.989 | -10.988 | 0.00516% | 0.01% | pass | matches |
|
||||
| [nl-cylinder-inflation](../tests/oracle/nl-cylinder-inflation/) | radial displacement of the bore (10, 0, 0) at the pressure that inflates it to a = 15 | 5 | 5.004 | 0.0793% | 0.5% | pass | matches |
|
||||
| [nl-cylinder-inflation](../tests/oracle/nl-cylinder-inflation/) | radial displacement of the outer wall (20, 0, 0) | 2.9129 | 2.9173 | 0.152% | 0.5% | pass | matches |
|
||||
| [nl-elastica-cantilever](../tests/oracle/nl-elastica-cantilever/) | tip deflection δ_v/L at P L²/(E I) = 1 | 0.30172 | 0.30226 | 0.179% | 1% | pass | matches |
|
||||
@@ -114,6 +144,12 @@ Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the
|
||||
| [nl-hertz-cylinder](../tests/oracle/nl-hertz-cylinder/) | the contact pressure at the secondary node at x = 0.36701 (node 8) | 1959 | 2078.4 | 6.09% | 10% | pass | matches |
|
||||
| [nl-hertz-cylinder](../tests/oracle/nl-hertz-cylinder/) | the contact pressure at the secondary node at x = 0.63325 (node 11, beyond the half-width b = 0.5) | 0 | ≈ 0 | ≈ 0 | ± 1 | pass | matches |
|
||||
| [nl-hertz-cylinder](../tests/oracle/nl-hertz-cylinder/) | the vertical stress σ_yy under the centre of contact at depth 0.47127 (node 713) | -2099.2 | -2059.8 | 1.87% | 4% | pass | matches |
|
||||
| [nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/) | the largest contact pressure p₀ (at x = 0) | 2884.6 | 2892 | 0.255% | 2% | pass | matches |
|
||||
| [nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/) | the force across the contact (half model, 1 mm thick) | 1132.8 | 1133 | 0.0228% | 0.2% | pass | matches |
|
||||
| [nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/) | the contact pressure at the secondary node at x = 0.29594 | 2325.1 | 2257.9 | -2.89% | 5% | pass | matches |
|
||||
| [nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/) | the contact pressure at the secondary node at x = 0.36701 | 1959 | 2078.4 | 6.09% | 10% | pass | matches |
|
||||
| [nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/) | the contact pressure at the secondary node at x = 0.63325 (beyond the half-width b = 0.5) | 0 | ≈ 0 | ≈ 0 | ± 1 | pass | matches |
|
||||
| [nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/) | the vertical stress σ_yy under the centre of contact at depth 0.47127 | -2099.2 | -2059.8 | 1.87% | 4% | pass | matches |
|
||||
| [nl-hybrid-incompressible](../tests/oracle/nl-hybrid-incompressible/) | axial Cauchy stress, stretch λ = 2, ν = 0.4999 | 3.4988 | 3.4988 | 0.000000135% | 1e-6 rel. | pass | matches |
|
||||
| [nl-hybrid-incompressible](../tests/oracle/nl-hybrid-incompressible/) | lateral displacement at (1, 1, 1): l − 1 | -0.29281 | -0.29281 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-j2-uniaxial](../tests/oracle/nl-j2-uniaxial/) | axial Cauchy stress at a logarithmic strain of 0.2 | 642.74 | 642.74 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
@@ -125,6 +161,8 @@ Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the
|
||||
| [nl-mooney-rivlin-modes](../tests/oracle/nl-mooney-rivlin-modes/) | Cauchy stress across, in the held direction, pure shear λ = 2 | 1.5067 | 1.5067 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-mooney-rivlin-tube](../tests/oracle/nl-mooney-rivlin-tube/) | radial displacement of the bore (10, 0, 0) at the pressure that inflates it to a = 15 at λ_z = 1.5 | 5 | 5.0311 | 0.622% | 1% | pass | matches |
|
||||
| [nl-mooney-rivlin-tube](../tests/oracle/nl-mooney-rivlin-tube/) | radial displacement of the outer wall (20, 0, 0): b − B, a small difference of radii | 0.61553 | 0.64339 | 2.8e-2 | ± 0.04 | pass | matches |
|
||||
| [nl-mooney-rivlin-tube-axisymmetric](../tests/oracle/nl-mooney-rivlin-tube-axisymmetric/) | radial displacement of the bore (10, 0, 0) at the pressure that inflates it to a = 15 at λ_z = 1.5 | 5 | 5.0329 | 0.658% | 1% | pass | matches |
|
||||
| [nl-mooney-rivlin-tube-axisymmetric](../tests/oracle/nl-mooney-rivlin-tube-axisymmetric/) | radial displacement of the outer wall (20, 0, 0): b − B, a small difference of radii | 0.61553 | 0.64473 | 2.9e-2 | ± 0.04 | pass | matches |
|
||||
| [nl-neo-hookean-uniaxial](../tests/oracle/nl-neo-hookean-uniaxial/) | axial Cauchy stress at the corner (1, 1, 1), stretch λ = 2 | 8.0601 | 8.0601 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-neo-hookean-uniaxial](../tests/oracle/nl-neo-hookean-uniaxial/) | lateral displacement u_y at the corner (1, 1, 1), l − 1 | -0.18686 | -0.18686 | < 1e-7% | 1e-6 rel. | pass | matches |
|
||||
| [nl-patch-hex8](../tests/oracle/nl-patch-hex8/) | u_x at inner node 22 | 0.13583 | 0.13583 | < 1e-7% | 1e-8 rel. | pass | matches |
|
||||
@@ -291,14 +329,18 @@ Generated by `cargo xtask oracle` from [`tests/oracle/`](../tests/oracle/); the
|
||||
- **[nafems-le1-plane-stress](../tests/oracle/nafems-le1-plane-stress/)** (benchmark): NAFEMS LE1 elliptic membrane with 2D plane stress elements (quad8). Source: NAFEMS, The Standard NAFEMS Benchmarks, TNSB Rev. 3, October 1990, test LE1 (target σ_yy = 92.7 MPa at D); as reproduced in the Abaqus 2017 Benchmarks Guide, 'LE1: Plane stress elements—elliptic membrane'. Tolerances — syy_D: the target is published to three figures; the stress extrapolated to the edge on a converged mesh adds a few tenths of a percent
|
||||
- **[nafems-le10](../tests/oracle/nafems-le10/)** (benchmark): NAFEMS LE10 thick plate under pressure (hex20). Source: NAFEMS, The Standard NAFEMS Benchmarks, TNSB Rev. 3, October 1990, test LE10 (target σ_yy = −5.38 MPa at D); as reproduced in the Abaqus 2017 Benchmarks Guide, 'LE10: Thick plate under pressure'. Tolerances — syy_D: the target is published to three figures; at D the stress is extrapolated to a corner where the hole edge meets the loaded face, and published converged solutions scatter by 1–2% (Abaqus Benchmarks Guide fine meshes: −5.51 to −5.89)
|
||||
- **[nl-contact-patch](../tests/oracle/nl-contact-patch/)** (theory): Contact patch test: two blocks with non-matching meshes in uniaxial compression (implicit mortar contact). Source: The contact patch test: a contact discretisation must transmit a uniform pressure across a non-matching interface exactly; Taylor & Papadopoulos, On a patch test for contact problems in two dimensions, Nonlinear Computational Mechanics, Springer 1991, 690–702; Crisfield, Re-visiting the contact patch test, IJNME 48:435–449, 2000; passed by mortar methods: Puso & Laursen, CMAME 193:601–629, 2004. Uniaxial stress between frictionless symmetry planes is uniform in both blocks. Tolerances — small_lower_corner: small sliding is linear and its mortar integrals exact on these rectangular faces: a uniform stress is the discrete solution up to the convergence tolerance (10⁻¹⁰); small_lower_centre: as for the corner element; small_lower_base: as for the corner element; small_upper_top: as for the corner element; small_lateral: frictionless symmetry planes and a frictionless interface leave the blocks free to expand sideways; the non-matching interface must not hold them; small_pressure: the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface; small_force: the pressure carries the whole load across the interface; finite_lower_corner: finite sliding is exact up to the geometric terms of the gaps the augmentation leaves (10⁻⁵ of the element thickness) and the default convergence tolerances; finite_lower_centre: as for the corner element; finite_lower_base: as for the corner element; finite_upper_top: as for the corner element; finite_lateral: frictionless symmetry planes and a frictionless interface leave the blocks free to expand sideways; the non-matching interface must not hold them; finite_pressure: the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface; finite_force: the pressure carries the whole load across the interface; small_stress: the squeeze less the penetration the augmented Lagrangian leaves (up to 10⁻⁵ of the element thickness, a few 10⁻⁵ of the squeeze)
|
||||
- **[nl-contact-patch-axisymmetric](../tests/oracle/nl-contact-patch-axisymmetric/)** (theory): Contact patch test, axisymmetric: two rings with non-matching edges in axial compression (implicit mortar contact). Source: The contact patch test: a contact discretisation must transmit a uniform pressure across a non-matching interface exactly; Taylor & Papadopoulos, On a patch test for contact problems in two dimensions, Nonlinear Computational Mechanics, Springer 1991, 690–702; passed by 2D mortar methods: Yang, Laursen & Meng, IJNME 62:1183–1225, 2005. Uniaxial stress between frictionless supports is uniform in both blocks. Tolerances — small_lower_corner: small sliding is linear and its edge mortar integrals exact (degree-3 Gauss on the overlaps): the uniform stress is the discrete solution up to the convergence tolerance; small_lower_far: as for the corner element; small_lower_base: as for the corner element; small_upper_top: as for the corner element; small_lateral: a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them; small_pressure: the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface; small_force: the pressure carries the whole load across the interface (both are totals over the circumference); finite_lower_corner: finite sliding is exact up to the geometric terms of the gaps the augmentation leaves (10⁻⁵ of the element thickness) and the default convergence tolerances; finite_lower_far: as for the corner element; finite_lower_base: as for the corner element; finite_upper_top: as for the corner element; finite_lateral: a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them; finite_pressure: the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface; finite_force: the pressure carries the whole load across the interface (both are totals over the circumference); small_stress: the squeeze less the penetration the augmented Lagrangian leaves (up to 10⁻⁵ of the element thickness)
|
||||
- **[nl-contact-patch-plane-strain](../tests/oracle/nl-contact-patch-plane-strain/)** (theory): Contact patch test in plane strain: two blocks with non-matching edges in uniaxial compression (implicit mortar contact). Source: The contact patch test: a contact discretisation must transmit a uniform pressure across a non-matching interface exactly; Taylor & Papadopoulos, On a patch test for contact problems in two dimensions, Nonlinear Computational Mechanics, Springer 1991, 690–702; passed by 2D mortar methods: Yang, Laursen & Meng, IJNME 62:1183–1225, 2005. Uniaxial stress between frictionless supports is uniform in both blocks. Tolerances — small_lower_corner: small sliding is linear and its edge mortar integrals exact (degree-3 Gauss on the overlaps): the uniform stress is the discrete solution up to the convergence tolerance; small_lower_far: as for the corner element; small_lower_base: as for the corner element; small_upper_top: as for the corner element; small_lateral: a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them; small_pressure: the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface; small_force: the pressure carries the whole load across the interface (both are totals over the thickness); finite_lower_corner: finite sliding is exact up to the geometric terms of the gaps the augmentation leaves (10⁻⁵ of the element thickness) and the default convergence tolerances; finite_lower_far: as for the corner element; finite_lower_base: as for the corner element; finite_upper_top: as for the corner element; finite_lateral: a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them; finite_pressure: the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface; finite_force: the pressure carries the whole load across the interface (both are totals over the thickness); small_stress: the squeeze less the penetration the augmented Lagrangian leaves (up to 10⁻⁵ of the element thickness)
|
||||
- **[nl-cylinder-inflation](../tests/oracle/nl-cylinder-inflation/)** (theory): Inflation of a thick neo-Hookean cylinder by a follower pressure (plane strain). Source: Finite inflation of an incompressible neo-Hookean thick-walled tube in plane strain: r² = R² + a² − A², σ_θ − σ_r = μ (λ² − λ⁻²) with λ = r/R, integrated through the wall: p = (μ/2)[(ln λ_a² − λ_a⁻²) − (ln λ_b² − λ_b⁻²)] (R. S. Rivlin, Large elastic deformations of isotropic materials VI, Phil. Trans. R. Soc. A 242:173–195, 1949; R. W. Ogden, Non-Linear Elastic Deformations, Dover 1997, §5.3.2). Tolerances — ua: the closed form is incompressible and our material nearly so (κ/μ = 1000, a volume change of order p/κ ≈ 4·10⁻⁴); hybrid hex20, 6 × 16, follow the wall's strain gradient well inside; ub: as at the bore
|
||||
- **[nl-elastica-cantilever](../tests/oracle/nl-elastica-cantilever/)** (theory): Large-deflection cantilever under a dead tip load against the elastica (solids). Source: The elastica of a cantilever under a vertical end load: K. E. Bisshopp, D. C. Drucker, Large deflection of cantilever beams, Quarterly of Applied Mathematics 3(3):272–275, 1945 (elliptic-integral solution); values computed by integrating θ″ = −(P/EI) cos θ by shooting (our own RK4, 20000 steps), agreeing with the table of K. Mattiasson, Numerical results from large deflection beam and frame problems analysed by means of elliptic integrals, IJNME 17(1):145–153, 1981, to all six digits. Tolerances — v1: hex20, 40 × 2 through the depth, L/h = 20: shear deformation (0.6 (h/L)² ≈ 0.15 % of the deflection) and the clamp's local restraint, both absent from the elastica; u1: as δ_v; the shortening is the smaller, second-order quantity, so its relative error is about twice the deflection's; v2: hex20, 40 × 2 through the depth, L/h = 20: shear deformation (0.6 (h/L)² ≈ 0.15 % of the deflection) and the clamp's local restraint, both absent from the elastica; u2: as δ_v; the shortening is the smaller, second-order quantity, so its relative error is about twice the deflection's; v5: hex20, 40 × 2 through the depth, L/h = 20: shear deformation (0.6 (h/L)² ≈ 0.15 % of the deflection) and the clamp's local restraint, both absent from the elastica; u5: as δ_v; the shortening is the smaller, second-order quantity, so its relative error is about twice the deflection's; v10: hex20, 40 × 2 through the depth, L/h = 20: shear deformation (0.6 (h/L)² ≈ 0.15 % of the deflection) and the clamp's local restraint, both absent from the elastica; u10: as δ_v; the shortening is the smaller, second-order quantity, so its relative error is about twice the deflection's
|
||||
- **[nl-friction-sliding-blocks](../tests/oracle/nl-friction-sliding-blocks/)** (theory): Coulomb friction: a block pressed on another and slid (implicit mortar contact). Source: Coulomb's law of dry friction: a sliding interface carries μ_k times the normal force against the slip (C. A. Coulomb, Théorie des machines simples, Mém. Math. Phys. Acad. Sci. 10:161–332, 1785; P. Wriggers, Computational Contact Mechanics, 2nd ed., Springer 2006, §5.2). Tolerances — kinetic: once the interface slips its friction is μ_k N exactly (the return map puts every slipping node's traction on the cone); the force opposes the upper block's motion; balance: the friction carries the whole sideways load; slipping: a sideways demand far beyond μ N slips the whole interface
|
||||
- **[nl-hertz-cylinder](../tests/oracle/nl-hertz-cylinder/)** (theory): Hertz line contact: a cylinder pressed on a flat (implicit mortar contact, plane strain). Source: H. Hertz, Über die Berührung fester elastischer Körper, J. reine angew. Math. 92:156–171, 1882; K. L. Johnson, Contact Mechanics, Cambridge University Press 1985, §4.3 (two-dimensional contact of cylindrical bodies, eqs. 4.45–4.49): b = √(4PR/(πE*)), p₀ = 2P/(πb), with 1/E* = 2(1 − ν²)/E for two bodies of one material and R the cylinder's radius on a flat. Tolerances — peak_pressure: p₀ = 2P/(πb) for the load per length P = 2265.57 N/mm, b = 0.5 mm; the contact width spans seven elements of the graded mesh; normal_force: the pressure carries the whole load; the soft springs that hold the indenter before contact take a millionth; pressure_x0296: p₀ √(1 − x²/b²) at the node; the nodal pressure (the node's normal force over the area it carries) averages the distribution over the node's elements; pressure_x0367: as at node 7, nearer the edge, where the elements round off the square-root fall of the pressure to zero at b; pressure_x0633: outside the contact the surfaces are apart: no pressure (an element beyond b, since the edge element carries part of the last closed node's pressure); syy_depth: σ_z = −p₀ / √(1 + z²/b²) on the axis of a half-plane (Johnson 1985, eq. 4.49); the foundation is ten half-widths wide and deep, not a half-plane, and the stress is the nodal average of the elements around the node
|
||||
- **[nl-hertz-cylinder-plane-strain](../tests/oracle/nl-hertz-cylinder-plane-strain/)** (theory): Hertz line contact: a cylinder pressed on a flat (implicit mortar contact of 2D edges, plane strain). Source: H. Hertz, Über die Berührung fester elastischer Körper, J. reine angew. Math. 92:156–171, 1882; K. L. Johnson, Contact Mechanics, Cambridge University Press 1985, §4.3 (two-dimensional contact of cylindrical bodies, eqs. 4.45–4.49): b = √(4PR/(πE*)), p₀ = 2P/(πb), with 1/E* = 2(1 − ν²)/E for two bodies of one material and R the cylinder's radius on a flat. Tolerances — peak_pressure: p₀ = 2P/(πb) for the load per length P = 2265.57 N/mm, b = 0.5 mm; the contact width spans seven elements of the graded mesh; normal_force: the pressure carries the whole load; the soft springs that hold the indenter before contact take a millionth; pressure_x0296: p₀ √(1 − x²/b²) at the node; the nodal pressure (the node's normal force over the area it carries) averages the distribution over the node's elements; pressure_x0367: as at node 7, nearer the edge, where the elements round off the square-root fall of the pressure to zero at b; pressure_x0633: outside the contact the surfaces are apart: no pressure (an element beyond b, since the edge element carries part of the last closed node's pressure); syy_depth: σ_z = −p₀ / √(1 + z²/b²) on the axis of a half-plane (Johnson 1985, eq. 4.49); the foundation is ten half-widths wide and deep, not a half-plane, and the stress is the nodal average of the elements around the node
|
||||
- **[nl-hybrid-incompressible](../tests/oracle/nl-hybrid-incompressible/)** (theory): Nearly incompressible hybrid block under prescribed stretch. Source: Uniaxial stress of the decoupled compressible neo-Hookean energy W = μ/2 (Ī₁ − 3) + κ/2 (J − 1)² (P. J. Flory, Trans. Faraday Soc. 57:829–838, 1961): σ = μ J^(−5/3) (λ² − I₁/3) + κ (J − 1) with the lateral stretch found so σ_yy = 0 (bisection; E = 3, ν = 0.4999: μ = 1.0000667, κ = 5000). Tolerances — sigma: a homogeneous deformation every hybrid hex8 represents exactly; the 27-element mesh makes the global mixed tangent indefinite in the first iterations (LDLᵀ): the tolerance admits the Newton tolerance and rounding; lateral: a homogeneous deformation every hybrid hex8 represents exactly; the 27-element mesh makes the global mixed tangent indefinite in the first iterations (LDLᵀ): the tolerance admits the Newton tolerance and rounding
|
||||
- **[nl-j2-uniaxial](../tests/oracle/nl-j2-uniaxial/)** (theory): Uniaxial tension of a J2 bilinear-hardening cube to large strain (implicit, finite strain). Source: J2 plasticity with linear isotropic hardening in uniaxial stress (Simo and Hughes, Computational Inelasticity, Springer 1998, §2.3) in the logarithmic strain, which Simo's exponential return map (CMAME 99:61–112, 1992) integrates exactly on a proportional path: Kirchhoff stress τ = σ_y + E_t (ε − σ_y/E), Cauchy stress σ = τ/J with ln J = τ/(3K) (Hencky's volume law). Tolerances — sigma: a homogeneous deformation the element represents exactly, reached on a proportional path: the tolerance only admits the Newton tolerance and rounding; plastic_strain: ε_p = (τ − σ_y)/H, as for the stress
|
||||
- **[nl-mooney-rivlin-modes](../tests/oracle/nl-mooney-rivlin-modes/)** (theory): Mooney–Rivlin block in uniaxial, equibiaxial and pure-shear tension. Source: Treloar's homogeneous modes of rubber testing (L. R. G. Treloar, The Physics of Rubber Elasticity, 3rd ed., Oxford 1975, ch. 10) for the decoupled compressible energy W = C₁₀ (Ī₁ − 3) + C₀₁ (Ī₂ − 3) + κ/2 (J − 1)²: principal Cauchy stresses σᵢ = (λᵢ/J) ∂W/∂λᵢ with the free stretch found so the free faces carry no stress (bisection; E = 3, ν = 0.49, C₀₁ = 0.2, C₁₀ = μ/2 − C₀₁). Tolerances — uniaxial: a homogeneous deformation the hybrid hex20 represents exactly: the tolerance admits the Newton tolerance and rounding; uniaxial_lateral: a homogeneous deformation the hybrid hex20 represents exactly: the tolerance admits the Newton tolerance and rounding; equibiaxial: a homogeneous deformation the hybrid hex20 represents exactly: the tolerance admits the Newton tolerance and rounding; shear_xx: a homogeneous deformation the hybrid hex20 represents exactly: the tolerance admits the Newton tolerance and rounding; shear_yy: a homogeneous deformation the hybrid hex20 represents exactly: the tolerance admits the Newton tolerance and rounding
|
||||
- **[nl-mooney-rivlin-tube](../tests/oracle/nl-mooney-rivlin-tube/)** (theory): Inflation of a stretched Mooney–Rivlin tube (follower pressure). Source: Finite inflation and extension of an incompressible Mooney–Rivlin tube (R. S. Rivlin, Large elastic deformations of isotropic materials VI, Phil. Trans. R. Soc. A 242:173–195, 1949; R. W. Ogden, Non-Linear Elastic Deformations, Dover 1997, §5.3): at axial stretch λ_z, r² = a² + (R² − A²)/λ_z, σ_θ − σ_r = 2 (λ_θ² − λ_r²)(C₁₀ + C₀₁ λ_z²) with λ_θ = r/R, λ_r = 1/(λ_θ λ_z), integrated through the wall: p = ∫ (σ_θ − σ_r) dr/r (Simpson, 2·10⁵ intervals). Tolerances — ua: 16 × 6 hybrid hex20 around the quarter; ν = 0.4995 stands in for incompressibility (a volume change of order p/κ ≈ 10⁻³ shifts the radii by about 0.1 %), and the tube is stretched axially by its ends, which a nearly incompressible solid takes with larger pressures than the plane-strain neo-Hookean tube; ub: 0.2 % of the outer radius b = 20.616: the same volume change of order 10⁻³ moves b by about 0.03, which is 4.5 % of b − B; the incompressible kinematics r² = a² + (R² − A²)/λ_z amplify it there
|
||||
- **[nl-mooney-rivlin-tube-axisymmetric](../tests/oracle/nl-mooney-rivlin-tube-axisymmetric/)** (theory): Inflation of a stretched Mooney–Rivlin tube (axisymmetric hybrid quad4). Source: Finite inflation and extension of an incompressible Mooney–Rivlin tube (R. S. Rivlin, Large elastic deformations of isotropic materials VI, Phil. Trans. R. Soc. A 242:173–195, 1949; R. W. Ogden, Non-Linear Elastic Deformations, Dover 1997, §5.3): at axial stretch λ_z, r² = a² + (R² − A²)/λ_z, σ_θ − σ_r = 2 (λ_θ² − λ_r²)(C₁₀ + C₀₁ λ_z²) with λ_θ = r/R, λ_r = 1/(λ_θ λ_z), integrated through the wall: p = ∫ (σ_θ − σ_r) dr/r (Simpson, 2·10⁵ intervals). Tolerances — ua: 16 × 6 hybrid hex20 around the quarter; ν = 0.4995 stands in for incompressibility (a volume change of order p/κ ≈ 10⁻³ shifts the radii by about 0.1 %), and the tube is stretched axially by its ends, which a nearly incompressible solid takes with larger pressures than the plane-strain neo-Hookean tube; ub: 0.2 % of the outer radius b = 20.616: the same volume change of order 10⁻³ moves b by about 0.03, which is 4.5 % of b − B; the incompressible kinematics r² = a² + (R² − A²)/λ_z amplify it there
|
||||
- **[nl-neo-hookean-uniaxial](../tests/oracle/nl-neo-hookean-uniaxial/)** (theory): Uniaxial stretch of a neo-Hookean block to twice its length. Source: Closed form of the compressible neo-Hookean model W = μ/2 (Ī₁ − 3) + κ/2 (J − 1)² in uniaxial stress (G. A. Holzapfel, Nonlinear Solid Mechanics, Wiley 2000, §6.5, eq. 6.148 with the volumetric term of §6.4): σ_yy = μ J^(−5/3)(l² − Ī/3) + κ(J − 1) = 0 fixes the lateral stretch l, then σ_xx = μ J^(−5/3)(λ² − l²). Tolerances — sigma: a homogeneous deformation every element represents exactly: the tolerance only admits rounding and the Newton tolerance; lateral: a homogeneous deformation every element represents exactly: the tolerance only admits rounding and the Newton tolerance
|
||||
- **[nl-patch-hex8](../tests/oracle/nl-patch-hex8/)** (theory): Nonlinear patch test, hex8: a homogeneous finite deformation of a distorted mesh. Source: The patch test (B. M. Irons, A. Razzaque, in A. K. Aziz (ed.), The Mathematical Foundations of the Finite Element Method, Academic Press 1972, 557–587) at finite strain: a boundary moved by x = F X makes F the solution everywhere (J. Bonet, R. D. Wood, Nonlinear Continuum Mechanics for Finite Element Analysis, 2nd ed., Cambridge 2008, §8); the neo-Hookean Cauchy stress σ = μ J^(−5/3) dev b + κ (J − 1) I of that F. Tolerances — ux: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; uy: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; uz: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; sxx: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; syy: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; szz: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; sxy: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; sxz: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance; syz: a patch test is passed exactly: the tolerance admits rounding and the Newton tolerance
|
||||
- **[nl-rigid-rotation](../tests/oracle/nl-rigid-rotation/)** (theory): A quarter turn strains and stresses nothing (objectivity). Source: Objectivity of the total Lagrangian formulation and of the hyperelastic stress: a rigid motion x = R X + c gives C = I and σ = 0 whatever the rotation (Bonet & Wood 2008, §3.4 and §5.2; T. Belytschko, W. K. Liu, B. Moran, Nonlinear Finite Elements for Continua and Structures, 2nd ed., Wiley 2014, §5.5). Tolerances — ux: the inner nodes follow the rigid rotation exactly; uy: the inner nodes follow the rigid rotation exactly; uz: the inner nodes follow the rigid rotation exactly; von_mises: zero up to rounding of stresses of order E = 1000: a 90° turn leaves no strain
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Contact patch test, axisymmetric (implicit mortar contact, non-matching edges)
|
||||
|
||||
The axisymmetric form of `nl-contact-patch`: two rings of unit square section, 1 ≤ r ≤ 2, stacked
|
||||
along the axis y and meshed independently (3 × 2 quad4 below, graded in r; 2 × 2 above), so the
|
||||
nodes of the interface at y = 1 do not match. A frictionless contact pair joins the lower ring's
|
||||
top edge (main) and the upper ring's bottom edge (secondary). The base y = 0 is held in y and
|
||||
the top y = 2 pushed down by 0.02 (E = 1000, ν = 0.3), in a `Small` (small sliding) and a `Finite`
|
||||
(finite sliding) nonlinear static step. Axial compression with free radial expansion is a
|
||||
uniform uniaxial stress σ_yy in both rings (u_r = −ν ε r, so the hoop strain equals the radial
|
||||
one), carried by a uniform contact pressure; the edge mortar integrals weight by 2πr, so the
|
||||
patch test checks that the radius enters the gaps and the forces consistently.
|
||||
@@ -0,0 +1,432 @@
|
||||
{
|
||||
"title": "Contact patch test, axisymmetric: two rings with non-matching edges in axial compression (implicit mortar contact)",
|
||||
"kind": "theory",
|
||||
"source": "The contact patch test: a contact discretisation must transmit a uniform pressure across a non-matching interface exactly; Taylor & Papadopoulos, On a patch test for contact problems in two dimensions, Nonlinear Computational Mechanics, Springer 1991, 690–702; passed by 2D mortar methods: Yang, Laursen & Meng, IJNME 62:1183–1225, 2005. Uniaxial stress between frictionless supports is uniform in both blocks",
|
||||
"quantities": [
|
||||
{
|
||||
"id": "small_lower_corner",
|
||||
"quantity": "Small: σ_yy of element 4 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
4
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "small sliding is linear and its edge mortar integrals exact (degree-3 Gauss on the overlaps): the uniform stress is the discrete solution up to the convergence tolerance"
|
||||
},
|
||||
{
|
||||
"id": "small_lower_far",
|
||||
"quantity": "Small: σ_yy of element 6 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "small_lower_base",
|
||||
"quantity": "Small: σ_yy of element 1 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "small_upper_top",
|
||||
"quantity": "Small: σ_yy of element 10 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
10
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "small_lateral",
|
||||
"quantity": "Small: σ_xx of element 6 over its σ_yy (uniaxial stress: 0)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/0",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 0.0,
|
||||
"tolerance": {
|
||||
"absolute": 1e-06
|
||||
},
|
||||
"reason": "a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them"
|
||||
},
|
||||
{
|
||||
"id": "small_pressure",
|
||||
"quantity": "Small: the contact pressure at the secondary node (1.5, 1) over −σ_yy of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"at": [
|
||||
[
|
||||
1.5,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface"
|
||||
},
|
||||
{
|
||||
"id": "small_force",
|
||||
"quantity": "Small: the integrated contact pressure over the reaction at the pushed top",
|
||||
"measure": {
|
||||
"from": "small",
|
||||
"pointer": "/contacts/0/normal_force",
|
||||
"over": {
|
||||
"command": "results.reactions",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"sets": [
|
||||
"top"
|
||||
]
|
||||
},
|
||||
"pointer": "/sets/0/force/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "the pressure carries the whole load across the interface (both are totals over the circumference)"
|
||||
},
|
||||
{
|
||||
"id": "finite_lower_corner",
|
||||
"quantity": "Finite: σ_yy of element 4 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
4
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "finite sliding is exact up to the geometric terms of the gaps the augmentation leaves (10⁻⁵ of the element thickness) and the default convergence tolerances"
|
||||
},
|
||||
{
|
||||
"id": "finite_lower_far",
|
||||
"quantity": "Finite: σ_yy of element 6 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "finite_lower_base",
|
||||
"quantity": "Finite: σ_yy of element 1 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "finite_upper_top",
|
||||
"quantity": "Finite: σ_yy of element 10 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
10
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "finite_lateral",
|
||||
"quantity": "Finite: σ_xx of element 6 over its σ_yy (uniaxial stress: 0)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/0",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 0.0,
|
||||
"tolerance": {
|
||||
"absolute": 0.0002
|
||||
},
|
||||
"reason": "a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them"
|
||||
},
|
||||
{
|
||||
"id": "finite_pressure",
|
||||
"quantity": "Finite: the contact pressure at the secondary node (1.5, 1) over −σ_yy of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"at": [
|
||||
[
|
||||
1.5,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface"
|
||||
},
|
||||
{
|
||||
"id": "finite_force",
|
||||
"quantity": "Finite: the integrated contact pressure over the reaction at the pushed top",
|
||||
"measure": {
|
||||
"from": "finite",
|
||||
"pointer": "/contacts/0/normal_force",
|
||||
"over": {
|
||||
"command": "results.reactions",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"sets": [
|
||||
"top"
|
||||
]
|
||||
},
|
||||
"pointer": "/sets/0/force/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "the pressure carries the whole load across the interface (both are totals over the circumference)"
|
||||
},
|
||||
{
|
||||
"id": "small_stress",
|
||||
"quantity": "Small: σ_yy of element 7 (uniaxial stress E ε = −10 for the 1% squeeze)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
},
|
||||
"value": -10.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0001
|
||||
},
|
||||
"reason": "the squeeze less the penetration the augmented Lagrangian leaves (up to 10⁻⁵ of the element thickness)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
|
||||
"values": {
|
||||
"finite_force": {
|
||||
"band": 1.000000000000021e-9,
|
||||
"value": 1.0000000000000209
|
||||
},
|
||||
"finite_lateral": {
|
||||
"band": 0.0002,
|
||||
"value": -6.202292989414768e-6
|
||||
},
|
||||
"finite_lower_base": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999851288470675
|
||||
},
|
||||
"finite_lower_corner": {
|
||||
"band": 1.0000001256632718e-9,
|
||||
"value": 1.0000001256632718
|
||||
},
|
||||
"finite_lower_far": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999675186278648
|
||||
},
|
||||
"finite_pressure": {
|
||||
"band": 1e-9,
|
||||
"value": 0.999971468750515
|
||||
},
|
||||
"finite_upper_top": {
|
||||
"band": 1e-9,
|
||||
"value": 0.999981532712154
|
||||
},
|
||||
"small_force": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999044
|
||||
},
|
||||
"small_lateral": {
|
||||
"band": 1e-6,
|
||||
"value": 3.4213832901728455e-15
|
||||
},
|
||||
"small_lower_base": {
|
||||
"band": 1.0000000000000207e-9,
|
||||
"value": 1.0000000000000207
|
||||
},
|
||||
"small_lower_corner": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999943
|
||||
},
|
||||
"small_lower_far": {
|
||||
"band": 1.0000000000000418e-9,
|
||||
"value": 1.0000000000000417
|
||||
},
|
||||
"small_pressure": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999998981
|
||||
},
|
||||
"small_stress": {
|
||||
"band": 1e-8,
|
||||
"value": -9.999573821916737
|
||||
},
|
||||
"small_upper_top": {
|
||||
"band": 1.0000000000000296e-9,
|
||||
"value": 1.0000000000000295
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{"command": "mesh.block", "params": {"body": "Lower", "kind": "quad4", "corners": [[1.0, 0, 0], [2.0, 0, 0], [2.0, 1, 0], [1.0, 1, 0]], "divisions": [3, 2], "bias": [2.0, 1.0], "edges": [{"corners": [1, 2], "name": "bottom"}, {"corners": [3, 4], "name": "top"}, {"corners": [4, 1], "name": "x0"}]}},
|
||||
{"command": "mesh.block", "params": {"body": "Upper", "kind": "quad4", "corners": [[1.0, 1, 0], [2.0, 1, 0], [2.0, 2, 0], [1.0, 2, 0]], "divisions": [2, 2], "edges": [{"corners": [1, 2], "name": "bottom"}, {"corners": [3, 4], "name": "top"}, {"corners": [4, 1], "name": "x0"}], "merge": 0}},
|
||||
{"command": "model.set.create", "params": {"name": "lower", "geometry": [{"body": "Lower", "face": "area"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "upper", "geometry": [{"body": "Upper", "face": "area"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "lower_top", "geometry": [{"body": "Lower", "edge": "top"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "upper_bottom", "geometry": [{"body": "Upper", "edge": "bottom"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "base", "geometry": [{"body": "Lower", "edge": "bottom"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "top", "geometry": [{"body": "Upper", "edge": "top"}]}},
|
||||
{"command": "model.material.create", "params": {"name": "Mat", "elastic": {"type": "isotropic", "young": 1000.0, "poisson": 0.3}}},
|
||||
{"command": "model.section.create", "params": {"name": "lower", "set": "lower", "material": "Mat", "kind": {"type": "axisymmetric"}}},
|
||||
{"command": "model.section.create", "params": {"name": "upper", "set": "upper", "material": "Mat", "kind": {"type": "axisymmetric"}}},
|
||||
{"command": "interaction.contact.pair", "params": {"name": "interface", "main": "lower_top", "secondary": "upper_bottom"}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "base", "type": "displacement", "target": {"set": "base"}, "values": [null, 0, null, null, null, null]}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "push", "type": "displacement", "target": {"set": "top"}, "values": [null, -0.02, null, null, null, null]}},
|
||||
{"command": "model.step.create", "params": {"name": "Small", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false, "tolerances": {"force": 1e-10, "displacement": 1e-08, "energy": 1e-16}}, "boundary_conditions": ["base", "push"], "loads": []}},
|
||||
{"command": "model.step.create", "params": {"name": "Finite", "procedure": {"type": "nonlinear_static"}, "boundary_conditions": ["base", "push"], "loads": []}},
|
||||
{"command": "solve.nonlinear", "params": {"step": "Small"}, "id": "small"},
|
||||
{"command": "solve.nonlinear", "params": {"step": "Finite"}, "id": "finite"}
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
# Contact patch test in plane strain (implicit mortar contact, non-matching edges)
|
||||
|
||||
The 2D form of `nl-contact-patch`: two unit squares stacked along y in plane strain (2 mm thick),
|
||||
meshed independently: 3 × 2 quad4 below (graded in x), 2 × 2 above, so the nodes of the interface
|
||||
at y = 1 do not match. A frictionless contact pair joins the lower block's top edge (main) and
|
||||
the upper block's bottom edge (secondary). The edge x = 0 is a symmetry line, the base y = 0 is
|
||||
held in y, and the top y = 2 is pushed down by 0.02 (E = 1000, ν = 0.3) in two nonlinear static
|
||||
steps: `Small` without nonlinear geometry (small sliding) and `Finite` with it (finite sliding).
|
||||
The exact state is uniaxial stress σ_yy (σ_zz = ν σ_yy out of plane), uniform in both blocks,
|
||||
with a uniform contact pressure equal to it.
|
||||
@@ -0,0 +1,432 @@
|
||||
{
|
||||
"title": "Contact patch test in plane strain: two blocks with non-matching edges in uniaxial compression (implicit mortar contact)",
|
||||
"kind": "theory",
|
||||
"source": "The contact patch test: a contact discretisation must transmit a uniform pressure across a non-matching interface exactly; Taylor & Papadopoulos, On a patch test for contact problems in two dimensions, Nonlinear Computational Mechanics, Springer 1991, 690–702; passed by 2D mortar methods: Yang, Laursen & Meng, IJNME 62:1183–1225, 2005. Uniaxial stress between frictionless supports is uniform in both blocks",
|
||||
"quantities": [
|
||||
{
|
||||
"id": "small_lower_corner",
|
||||
"quantity": "Small: σ_yy of element 4 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
4
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "small sliding is linear and its edge mortar integrals exact (degree-3 Gauss on the overlaps): the uniform stress is the discrete solution up to the convergence tolerance"
|
||||
},
|
||||
{
|
||||
"id": "small_lower_far",
|
||||
"quantity": "Small: σ_yy of element 6 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "small_lower_base",
|
||||
"quantity": "Small: σ_yy of element 1 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "small_upper_top",
|
||||
"quantity": "Small: σ_yy of element 10 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
10
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "small_lateral",
|
||||
"quantity": "Small: σ_xx of element 6 over its σ_yy (uniaxial stress: 0)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/0",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 0.0,
|
||||
"tolerance": {
|
||||
"absolute": 1e-06
|
||||
},
|
||||
"reason": "a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them"
|
||||
},
|
||||
{
|
||||
"id": "small_pressure",
|
||||
"quantity": "Small: the contact pressure at the secondary node (0.5, 1) over −σ_yy of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"at": [
|
||||
[
|
||||
0.5,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface"
|
||||
},
|
||||
{
|
||||
"id": "small_force",
|
||||
"quantity": "Small: the integrated contact pressure over the reaction at the pushed top",
|
||||
"measure": {
|
||||
"from": "small",
|
||||
"pointer": "/contacts/0/normal_force",
|
||||
"over": {
|
||||
"command": "results.reactions",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"sets": [
|
||||
"top"
|
||||
]
|
||||
},
|
||||
"pointer": "/sets/0/force/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 1e-06
|
||||
},
|
||||
"reason": "the pressure carries the whole load across the interface (both are totals over the thickness)"
|
||||
},
|
||||
{
|
||||
"id": "finite_lower_corner",
|
||||
"quantity": "Finite: σ_yy of element 4 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
4
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "finite sliding is exact up to the geometric terms of the gaps the augmentation leaves (10⁻⁵ of the element thickness) and the default convergence tolerances"
|
||||
},
|
||||
{
|
||||
"id": "finite_lower_far",
|
||||
"quantity": "Finite: σ_yy of element 6 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "finite_lower_base",
|
||||
"quantity": "Finite: σ_yy of element 1 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "finite_upper_top",
|
||||
"quantity": "Finite: σ_yy of element 10 over that of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
10
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "as for the corner element"
|
||||
},
|
||||
{
|
||||
"id": "finite_lateral",
|
||||
"quantity": "Finite: σ_xx of element 6 over its σ_yy (uniaxial stress: 0)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/0",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
6
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
}
|
||||
},
|
||||
"value": 0.0,
|
||||
"tolerance": {
|
||||
"absolute": 0.0002
|
||||
},
|
||||
"reason": "a frictionless interface (and symmetry plane) leaves the blocks free to expand sideways; the non-matching interface must not hold them"
|
||||
},
|
||||
{
|
||||
"id": "finite_pressure",
|
||||
"quantity": "Finite: the contact pressure at the secondary node (0.5, 1) over −σ_yy of element 7",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"at": [
|
||||
[
|
||||
0.5,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure",
|
||||
"over": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "the mortar multipliers are the nodal values of the contact pressure, uniform and equal to the stress across the interface"
|
||||
},
|
||||
{
|
||||
"id": "finite_force",
|
||||
"quantity": "Finite: the integrated contact pressure over the reaction at the pushed top",
|
||||
"measure": {
|
||||
"from": "finite",
|
||||
"pointer": "/contacts/0/normal_force",
|
||||
"over": {
|
||||
"command": "results.reactions",
|
||||
"params": {
|
||||
"step": "Finite",
|
||||
"sets": [
|
||||
"top"
|
||||
]
|
||||
},
|
||||
"pointer": "/sets/0/force/1",
|
||||
"scale": -1.0
|
||||
}
|
||||
},
|
||||
"value": 1.0,
|
||||
"tolerance": {
|
||||
"relative": 0.0002
|
||||
},
|
||||
"reason": "the pressure carries the whole load across the interface (both are totals over the thickness)"
|
||||
},
|
||||
{
|
||||
"id": "small_stress",
|
||||
"quantity": "Small: σ_yy of element 7 (plane strain uniaxial stress E ε/(1 − ν²) = −10.989 for the 1% squeeze)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Small",
|
||||
"elements": [
|
||||
7
|
||||
]
|
||||
},
|
||||
"pointer": "/elements/0/stress/0/1"
|
||||
},
|
||||
"value": -10.989010989010989,
|
||||
"tolerance": {
|
||||
"relative": 0.0001
|
||||
},
|
||||
"reason": "the squeeze less the penetration the augmented Lagrangian leaves (up to 10⁻⁵ of the element thickness)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
|
||||
"values": {
|
||||
"finite_force": {
|
||||
"band": 1.0000000000022103e-9,
|
||||
"value": 1.0000000000022102
|
||||
},
|
||||
"finite_lateral": {
|
||||
"band": 0.0002,
|
||||
"value": -1.8586502985840345e-6
|
||||
},
|
||||
"finite_lower_base": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999968799408236
|
||||
},
|
||||
"finite_lower_corner": {
|
||||
"band": 1.0000007080194146e-9,
|
||||
"value": 1.0000007080194144
|
||||
},
|
||||
"finite_lower_far": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999913032274111
|
||||
},
|
||||
"finite_pressure": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999924972272739
|
||||
},
|
||||
"finite_upper_top": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999957165083484
|
||||
},
|
||||
"small_force": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999859
|
||||
},
|
||||
"small_lateral": {
|
||||
"band": 1e-6,
|
||||
"value": -8.925527731170228e-15
|
||||
},
|
||||
"small_lower_base": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999801
|
||||
},
|
||||
"small_lower_corner": {
|
||||
"band": 1.0000000000000059e-9,
|
||||
"value": 1.0000000000000058
|
||||
},
|
||||
"small_lower_far": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999546
|
||||
},
|
||||
"small_pressure": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999142
|
||||
},
|
||||
"small_stress": {
|
||||
"band": 1.098901098901099e-8,
|
||||
"value": -10.988443575433445
|
||||
},
|
||||
"small_upper_top": {
|
||||
"band": 1e-9,
|
||||
"value": 0.9999999999999762
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{"command": "mesh.block", "params": {"body": "Lower", "kind": "quad4", "corners": [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]], "divisions": [3, 2], "bias": [2.0, 1.0], "edges": [{"corners": [1, 2], "name": "bottom"}, {"corners": [3, 4], "name": "top"}, {"corners": [4, 1], "name": "x0"}]}},
|
||||
{"command": "mesh.block", "params": {"body": "Upper", "kind": "quad4", "corners": [[0, 1, 0], [1, 1, 0], [1, 2, 0], [0, 2, 0]], "divisions": [2, 2], "edges": [{"corners": [1, 2], "name": "bottom"}, {"corners": [3, 4], "name": "top"}, {"corners": [4, 1], "name": "x0"}], "merge": 0}},
|
||||
{"command": "model.set.create", "params": {"name": "lower", "geometry": [{"body": "Lower", "face": "area"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "upper", "geometry": [{"body": "Upper", "face": "area"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "lower_top", "geometry": [{"body": "Lower", "edge": "top"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "upper_bottom", "geometry": [{"body": "Upper", "edge": "bottom"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "x0", "geometry": [{"body": "Lower", "edge": "x0"}, {"body": "Upper", "edge": "x0"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "base", "geometry": [{"body": "Lower", "edge": "bottom"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "top", "geometry": [{"body": "Upper", "edge": "top"}]}},
|
||||
{"command": "model.material.create", "params": {"name": "Mat", "elastic": {"type": "isotropic", "young": 1000.0, "poisson": 0.3}}},
|
||||
{"command": "model.section.create", "params": {"name": "lower", "set": "lower", "material": "Mat", "kind": {"type": "plane_strain", "thickness": 2.0}}},
|
||||
{"command": "model.section.create", "params": {"name": "upper", "set": "upper", "material": "Mat", "kind": {"type": "plane_strain", "thickness": 2.0}}},
|
||||
{"command": "interaction.contact.pair", "params": {"name": "interface", "main": "lower_top", "secondary": "upper_bottom"}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "x0", "type": "displacement", "target": {"set": "x0"}, "values": [0, null, null, null, null, null]}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "base", "type": "displacement", "target": {"set": "base"}, "values": [null, 0, null, null, null, null]}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "push", "type": "displacement", "target": {"set": "top"}, "values": [null, -0.02, null, null, null, null]}},
|
||||
{"command": "model.step.create", "params": {"name": "Small", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false, "tolerances": {"force": 1e-10, "displacement": 1e-08, "energy": 1e-16}}, "boundary_conditions": ["x0", "base", "push"], "loads": []}},
|
||||
{"command": "model.step.create", "params": {"name": "Finite", "procedure": {"type": "nonlinear_static"}, "boundary_conditions": ["x0", "base", "push"], "loads": []}},
|
||||
{"command": "solve.nonlinear", "params": {"step": "Small"}, "id": "small"},
|
||||
{"command": "solve.nonlinear", "params": {"step": "Finite"}, "id": "finite"}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
# Hertz line contact in plane strain (implicit mortar contact of 2D edges)
|
||||
|
||||
The 2D form of `nl-hertz-cylinder`: the same steel cylinder of radius R = 10 mm on a steel block
|
||||
(E = 210 GPa, ν = 0.3; mm, N, MPa), now a plane strain model of unit thickness meshed with quad4:
|
||||
the cylinder's lower part a 2.5 mm wide block whose bottom edge is the circular arc (20 × 10,
|
||||
graded towards the contact), on a 5 × 5 mm foundation (24 × 14), symmetric about x = 0. A
|
||||
pressure on the indenter's top edge carries the load P = 2265.57 N/mm (per length of the whole
|
||||
cylinder) for Hertz's half-width b = 0.5 mm and peak pressure p₀ = 2884.6 MPa. Soft springs hold
|
||||
the indenter's top until the contact closes. One nonlinear static step without nonlinear
|
||||
geometry: small sliding, edge-to-edge mortar contact of the round edge (secondary) on the flat
|
||||
(main) by an augmented Lagrangian. The references and tolerances are the 3D case's, so the two
|
||||
discretisations of one problem are checked against one closed form.
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"title": "Hertz line contact: a cylinder pressed on a flat (implicit mortar contact of 2D edges, plane strain)",
|
||||
"kind": "theory",
|
||||
"source": "H. Hertz, Über die Berührung fester elastischer Körper, J. reine angew. Math. 92:156–171, 1882; K. L. Johnson, Contact Mechanics, Cambridge University Press 1985, §4.3 (two-dimensional contact of cylindrical bodies, eqs. 4.45–4.49): b = √(4PR/(πE*)), p₀ = 2P/(πb), with 1/E* = 2(1 − ν²)/E for two bodies of one material and R the cylinder's radius on a flat",
|
||||
"quantities": [
|
||||
{
|
||||
"id": "peak_pressure",
|
||||
"quantity": "the largest contact pressure p₀ (at x = 0)",
|
||||
"measure": {
|
||||
"from": "solve",
|
||||
"pointer": "/contacts/0/max_pressure/value"
|
||||
},
|
||||
"value": 2884.6153846153843,
|
||||
"tolerance": {
|
||||
"relative": 0.02
|
||||
},
|
||||
"reason": "p₀ = 2P/(πb) for the load per length P = 2265.57 N/mm, b = 0.5 mm; the contact width spans seven elements of the graded mesh"
|
||||
},
|
||||
{
|
||||
"id": "normal_force",
|
||||
"quantity": "the force across the contact (half model, 1 mm thick)",
|
||||
"measure": {
|
||||
"from": "solve",
|
||||
"pointer": "/contacts/0/normal_force"
|
||||
},
|
||||
"value": 1132.7858125924733,
|
||||
"tolerance": {
|
||||
"relative": 0.002
|
||||
},
|
||||
"reason": "the pressure carries the whole load; the soft springs that hold the indenter before contact take a millionth"
|
||||
},
|
||||
{
|
||||
"id": "pressure_x0296",
|
||||
"quantity": "the contact pressure at the secondary node at x = 0.29594",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Press",
|
||||
"at": [
|
||||
[
|
||||
0.29594,
|
||||
0.00438,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure"
|
||||
},
|
||||
"value": 2325.066800691437,
|
||||
"tolerance": {
|
||||
"relative": 0.05
|
||||
},
|
||||
"reason": "p₀ √(1 − x²/b²) at the node; the nodal pressure (the node's normal force over the area it carries) averages the distribution over the node's elements"
|
||||
},
|
||||
{
|
||||
"id": "pressure_x0367",
|
||||
"quantity": "the contact pressure at the secondary node at x = 0.36701",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Press",
|
||||
"at": [
|
||||
[
|
||||
0.36701,
|
||||
0.006737,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure"
|
||||
},
|
||||
"value": 1959.0164149439815,
|
||||
"tolerance": {
|
||||
"relative": 0.1
|
||||
},
|
||||
"reason": "as at node 7, nearer the edge, where the elements round off the square-root fall of the pressure to zero at b"
|
||||
},
|
||||
{
|
||||
"id": "pressure_x0633",
|
||||
"quantity": "the contact pressure at the secondary node at x = 0.63325 (beyond the half-width b = 0.5)",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Press",
|
||||
"at": [
|
||||
[
|
||||
0.63325,
|
||||
0.02007,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/contact/0/pressure"
|
||||
},
|
||||
"value": 0.0,
|
||||
"tolerance": {
|
||||
"absolute": 1.0
|
||||
},
|
||||
"reason": "outside the contact the surfaces are apart: no pressure (an element beyond b, since the edge element carries part of the last closed node's pressure)"
|
||||
},
|
||||
{
|
||||
"id": "syy_depth",
|
||||
"quantity": "the vertical stress σ_yy under the centre of contact at depth 0.47127",
|
||||
"measure": {
|
||||
"command": "solve.probe",
|
||||
"params": {
|
||||
"step": "Press",
|
||||
"at": [
|
||||
[
|
||||
0.0,
|
||||
-0.47127,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"pointer": "/nodes/0/stress/1"
|
||||
},
|
||||
"value": -2099.1580228637245,
|
||||
"tolerance": {
|
||||
"relative": 0.04
|
||||
},
|
||||
"reason": "σ_z = −p₀ / √(1 + z²/b²) on the axis of a half-plane (Johnson 1985, eq. 4.49); the foundation is ten half-widths wide and deep, not a half-plane, and the stress is the nodal average of the elements around the node"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
|
||||
"values": {
|
||||
"normal_force": {
|
||||
"band": 1.1330442926242355e-6,
|
||||
"value": 1133.0442926242354
|
||||
},
|
||||
"peak_pressure": {
|
||||
"band": 2.8919633998947767e-6,
|
||||
"value": 2891.9633998947766
|
||||
},
|
||||
"pressure_x0296": {
|
||||
"band": 2.325066800691437e-6,
|
||||
"value": 2257.8514097926386
|
||||
},
|
||||
"pressure_x0367": {
|
||||
"band": 2.078406343070884e-6,
|
||||
"value": 2078.406343070884
|
||||
},
|
||||
"pressure_x0633": {
|
||||
"band": 1.0,
|
||||
"value": -0.0
|
||||
},
|
||||
"syy_depth": {
|
||||
"band": 2.0991580228637247e-6,
|
||||
"value": -2059.829517894975
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
[
|
||||
{"command": "mesh.block", "params": {"body": "Indenter", "kind": "quad4", "corners": [[0, 0, 0], [2.5, 0.3175416344814579, 0], [2.5, 3.0, 0], [0, 3.0, 0]], "divisions": [20, 10], "bias": [8, 8], "edges": [{"corners": [1, 2], "arc": {"center": [0, 10.0, 0]}, "name": "round"}, {"corners": [3, 4], "name": "top"}, {"corners": [4, 1], "name": "sym"}]}},
|
||||
{"command": "mesh.block", "params": {"body": "Foundation", "kind": "quad4", "corners": [[0, -5, 0], [5, -5, 0], [5, 0, 0], [0, 0, 0]], "divisions": [24, 14], "bias": [10, 0.1], "edges": [{"corners": [1, 2], "name": "base"}, {"corners": [3, 4], "name": "flat"}, {"corners": [4, 1], "name": "sym"}], "merge": 0}},
|
||||
{"command": "model.set.create", "params": {"name": "indenter", "geometry": [{"body": "Indenter", "face": "area"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "foundation", "geometry": [{"body": "Foundation", "face": "area"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "round", "geometry": [{"body": "Indenter", "edge": "round"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "flat", "geometry": [{"body": "Foundation", "edge": "flat"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "top", "geometry": [{"body": "Indenter", "edge": "top"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "base", "geometry": [{"body": "Foundation", "edge": "base"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "sym", "geometry": [{"body": "Indenter", "edge": "sym"}, {"body": "Foundation", "edge": "sym"}]}},
|
||||
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 210000.0, "poisson": 0.3}}},
|
||||
{"command": "model.section.create", "params": {"name": "indenter", "set": "indenter", "material": "Steel", "kind": {"type": "plane_strain", "thickness": 1.0}}},
|
||||
{"command": "model.section.create", "params": {"name": "foundation", "set": "foundation", "material": "Steel", "kind": {"type": "plane_strain", "thickness": 1.0}}},
|
||||
{"command": "interaction.contact.pair", "params": {"name": "hertz", "main": "flat", "secondary": "round"}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "sym", "type": "displacement", "target": {"set": "sym"}, "values": [0, null, null, null, null, null]}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "base", "type": "displacement", "target": {"set": "base"}, "values": [null, 0, null, null, null, null]}},
|
||||
{"command": "model.spring.create", "params": {"name": "hold", "type": "spring", "first": {"set": "top"}, "direction": {"dof": 2}, "stiffness": 0.01}},
|
||||
{"command": "model.load.create", "params": {"name": "press", "type": "pressure", "set": "top", "magnitude": 453.1143250369893}},
|
||||
{"command": "model.step.create", "params": {"name": "Press", "procedure": {"type": "nonlinear_static", "nonlinear_geometry": false, "max_iterations": 60}, "boundary_conditions": ["sym", "base"], "loads": ["press"]}},
|
||||
{"command": "solve.nonlinear", "params": {"step": "Press"}, "id": "solve"}
|
||||
]
|
||||
Reference in new issue
Block a user