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:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 16:30:44 -04:00
1 parent 6baa189231
commit 227c4544ed
20 files changed
+1579 -81

No files matched your search

@@ -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"}
]