Week 10 — Cohesive Interfaces: Traction–Separation Laws, Submeshes, Snap-back and Path Following
Process Systems Laboratory
Department of Chemical and Biomolecular Engineering, SeoulTech
| Part | Topic | Time |
|---|---|---|
| Quiz on Week 9 (closed-AI) | 10 min | |
| A | Cohesive zones — traction–separation laws, G_c, \sigma_c, the bar with one interface, snap-back | 30 min |
| B | Interfaces between subdomains in DOLFINx 0.11 — submeshes, entity maps, the ‘+’ side, ghost cells | 35 min |
| C | Path following — load, displacement and opening control; the plate with two inclusions | 35 min |
| D | Cohesive springs on every facet (the source) · LLM pitfalls · HW9 | 20 min |
Lab notebook: W10_lab.ipynb (≈ 80 min) · Homework: W10_HW9.ipynb (due before Week 11) · Interfaces in chemical engineering: particle–binder debonding in battery electrodes, coating delamination, fibre–matrix interfaces in membranes and composites
A crack or an interface is a surface across which the displacement may jump, [\![\mathbf u]\!]=\mathbf u_2-\mathbf u_1. A cohesive law \mathbf T([\![\mathbf u]\!]) transmits a traction that first rises, reaches the strength \sigma_c, and then softens to zero; the energy to open it completely is the fracture energy G_c=\int_0^\infty T\,d\delta.
\sum_i\int_{\Omega_i}\boldsymbol\sigma(\mathbf u_i):\boldsymbol\varepsilon(\mathbf v_i)\,dx+\int_\Gamma\mathbf T([\![\mathbf u]\!])\cdot[\![\mathbf v]\!]\,dS=\int_{\partial\Omega_N}\mathbf t\cdot\mathbf v\,ds
| law | T(\delta) | peak | initial stiffness |
|---|---|---|---|
| exponential (the source) | \dfrac{G_c}{\delta_0^2}\,\delta\,e^{-\delta/\delta_0}, \delta_0=\dfrac{G_c}{e\,\sigma_c} | \sigma_c at \delta=\delta_0 | G_c/\delta_0^2 — fixed by G_c, \sigma_c |
| bilinear | K_0\delta up to \sigma_c/K_0, then linear to 0 at \delta_c=2G_c/\sigma_c | \sigma_c | K_0 — a penalty you choose |
| linear spring | K\delta | — | K (an imperfect, elastic interface) |
Intrinsic laws are present from the start (the interface adds a compliance 1/K_0); extrinsic ones are inserted when the stress reaches \sigma_c. Here: intrinsic, vector form \mathbf T=\frac{T(\delta)}{\delta}[\![\mathbf u]\!], \delta=|[\![\mathbf u]\!]| (the source’s choice — it softens in compression as well, see Part C).
Bar (or strip in plane stress) of length L, one interface, end displacement u: uniform stress \sigma=T(\delta) and
u(\delta)=\frac{L}{E}\,T(\delta)+\delta,\qquad \frac{du}{d\delta}=\frac{L}{E}\,T'(\delta)+1 .
After the peak T'<0: if \frac LE\min T'<-1 the end displacement decreases while the crack opens — snap-back. For the exponential law \min T'=-G_ce^{-2}/\delta_0^2:
\text{snap-back}\iff L>L_c=\frac{E\,\delta_0^2\,e^2}{G_c}\qquad(\text{here } E=10,\ G_c=0.01,\ \sigma_c=0.5:\ L_c=0.40).
The elastic energy stored in a long bar exceeds what the interface can dissipate: the bar releases it faster than the interface opens. Brittleness is a property of the structure (L), not of the law alone.
What can control the test?
Load control stops at the peak. Displacement control cannot follow a snap-back. A quantity that grows monotonically — the crack opening — can.
The interface only, not every facet: each subdomain gets its own continuous space on a submesh; the interface integral couples them.
subs = [mesh.create_submesh(msh, 2, ct.find(i)) for i in (1, 2)] # (submesh, EntityMap, vertex map, geometry map)
V = [fem.functionspace(s[0], ("Lagrange", deg, (2,))) for s in subs]; em = [s[1] for s in subs]
dx = ufl.Measure("dx", domain=msh, subdomain_data=ct) # every integral on the PARENT mesh
dS = ufl.Measure("dS", domain=msh, subdomain_data=[(1, data)])(1) # data = [cell_1, local facet, cell_2, local facet, ...]
jmp = u[1]("-") - u[0]("+") # '+' = side 1, '-' = side 2 — by construction of data
F = [inner(sig(u[0]), eps(w[0])) * dx(1) - dot(T(jmp), w[0]("+")) * dS,
inner(sig(u[1]), eps(w[1])) * dx(2) + dot(T(jmp), w[1]("-")) * dS]
problem = NonlinearProblem(F, u, bcs=bcs, entity_maps=em, kind="mpi", petsc_options_prefix="bm_", petsc_options=...)entity_maps tells the assembler where the submesh functions live. Blocked residual and Jacobian, one SNES (kind="mpi").-np 1 and -np 2).With plain meshtags (subdomain_data=mesh.meshtags(msh, 1, facets, 1)) DOLFINx orders the two cells of an interior facet as it likes — then u1("+") is sometimes evaluated in a cell of subdomain 2.
| plate with two inclusions, K=G_c/\delta_0^2, P2 | -np 1 |
-np 2 |
|---|---|---|
| explicit ‘+’ = matrix | 5.48394 | 5.48394 |
| meshtags | 3.77287 (SNES −5) | 4.62581 (SNES −5) |
| linear spring strip (structured mesh) | -np 1 |
-np 2 |
|---|---|---|
| meshtags | 0.022 (= exact, by luck) | 0.05295 (Colab: 0.02308), SNES −5 |
The ordering depends on the mesh and on the partition. A linear solve (the source) gives a wrong number without complaint; SNES at least fails to converge. Build the integration data yourself: for each interface facet, the cell of subdomain 1 first.
Ghost cells. Interior-facet integrals need both cells of a facet on the owning rank. Built-in meshes are created with GhostMode.shared_facet; Gmsh meshes are not:
Without it, the DG model of Part D at -np 2 returned a stiffness of 8\times10^{-14}: the facets on the partition boundary were skipped and the plate fell into two loose pieces.
Garbage collection. A NonlinearProblem replaced in a loop is freed by Python’s cyclic collector at a different moment on each rank; its destructor destroys PETSc objects collectively. One rank waits in the destroy, the other in the next assembly — the program hangs (about half of the -np 2 runs in a test). The module switches automatic collection off and collects explicitly in every constructor (gc.disable(), gc.collect()), i.e. at the same point on all ranks.
| Math | Code |
|---|---|
| \Omega_1,\Omega_2 with their own spaces | mesh.create_submesh(msh, tdim, ct.find(i)) → EntityMap; functionspace(submesh, ...) |
| \int_{\Omega_i}\dots | dx(i) on the parent mesh + entity_maps=[em1, em2] |
| \int_\Gamma\dots, side 1 = ‘+’ | ufl.Measure("dS", domain=msh, subdomain_data=[(1, data)]) with data built from f2c, c2f |
| [\![\mathbf u]\!]=\mathbf u_2-\mathbf u_1 | u[1]("-") - u[0]("+") |
| normal gap | dot(jmp, n("+")), n = ufl.FacetNormal(msh) (outward of side 1) |
| blocked Newton | NonlinearProblem([F1, F2], [u1, u2], entity_maps=em, kind="mpi") |
| a functional of both fields | fem.form(expr * dS, entity_maps=em); vectors: fem.petsc.assemble_vector([g1, g2], kind="mpi") |
Load \lambda (traction on the right edge) becomes an unknown; the mean normal opening g(\mathbf u)=\frac1{|\Gamma|}\int_\Gamma[\![\mathbf u]\!]\cdot\mathbf n_1 is prescribed:
\mathbf R(\mathbf u)-\lambda\mathbf f=\mathbf 0,\qquad g(\mathbf u)=\hat\delta \quad\Rightarrow\quad \mathbf K\mathbf a=-\mathbf R,\ \ \mathbf K\mathbf b=\mathbf f,\ \ \Delta\lambda=\frac{\hat\delta-g(\mathbf u)-\mathbf g\cdot\mathbf a}{\mathbf g\cdot\mathbf b},\ \ \Delta\mathbf u=\mathbf a+\Delta\lambda\,\mathbf b .
Two solves with one factorisation per Newton step — Week 3’s global unknown by static condensation again. g is linear in \mathbf u: its vector \mathbf g is assembled once. The residual and Jacobian come from the NonlinearProblem (solver.computeFunction, solver.computeJacobian).
Never stop exactly at the peak: there the interface stiffness T' is zero, one half of the strip is a free rigid body, \mathbf K is singular — the next step started from it diverged. A step grid that misses \delta_0 by a little works.
The source’s plate (matrix E=10, inclusions E=70, G_c=0.01, \sigma_c=0.5 on the interface only), left u_x=0, bottom u_y=0, traction on the right; P2, h=0.06.
| check | value |
|---|---|
| perfect bond (one CG mesh) / interface spring K\to\infty (10^6) | 5.8322 / 5.8321 |
| interface K=G_c/\delta_0^2=185 (the law’s initial stiffness) | 5.484 (−6 %: the interface compliance) |
| plate with holes / slope after full debonding | 3.51607 / 3.51607 |
| displacement control | runs through (99 steps, ≤ 24 Newton iterations): no snap-back here |
Both ends of the curve are checked by an independent model: the start by the perfectly bonded plate, the end by the plate with holes.
Source chapter 1: DG everywhere, the exponential law on every interior facet (stronger in the bulk, K_{\rm bulk}=295, weaker on the interface), Nitsche for the supports. Linearised initial stiffness (left clamped as in the source, reference 5.95; plotted on the next slide):
| h | 0.12 | 0.06 | 0.03 | 0.015 |
|---|---|---|---|---|
| source code on 0.11 | 3.545 | 2.635 | 1.748 | 1.036 |
| this week’s DG model | 3.550 | 2.625 | 1.744 | — |
| same, 1000\,K | 6.004 | 5.949 | 5.940 | — |
Each facet is a spring in series with a cell: the added compliance \propto1/(Kh) — doubles with every halving of h (0.114, 0.211, 0.404, 0.797). An intrinsic law on all facets needs K\,h/E\gg1; with the source’s K it is 1.8. The source’s “elastic stiffness 2.6 N/mm ✅” is a mesh-dependent artefact; chapter 2’s “4.18 N/mm, stiffer because CG matches nodes” is the ‘+’ trap (reproduced on 0.11: 4.43 with the slightly different Gmsh mesh), with a different left support than chapter 1 (5.95 vs 5.87 for perfect bond).
The stiffness falls with every refinement and never converges; with 1000\,K (Kh/E\gg1) the same model converges to perfect bond.
PETSc.SNES driven directly). 1426 cells instead of 1434 (Gmsh), peak 0.522 at u=0.213 (source 0.534 at 0.22), stops at step 45 with conv = −6 — the plot ends at the peak: the post-peak response is not computed, “snap-back ✅” is an interpretation of a failed solve.PETSc.Vec().createSeq, hand-made block vector); the ‘+’ ordering of the interface facets is not controlled; a linear spring is called “linearized CZM” — there is no softening at all.| Pitfall | Measured symptom (this week) |
|---|---|
| Interface facets from meshtags: ‘+’ not controlled | stiffness 3.77 / 4.63 instead of 5.48; depends on the rank count |
Gmsh mesh without shared_facet ghosts |
interior-facet terms on partition boundaries skipped: stiffness 10^{-13} at -np 2 |
| Problems re-created in a loop under MPI | random hangs in PETSc destructors (garbage collection) |
| A step that lands exactly on the peak | singular tangent, the next Newton step diverges |
| Displacement control through a snap-back | “converged” — on the other branch |
| An isotropic law with no contact | interpenetration, floating debonded inclusions |
| Cohesive springs on all facets, small K | stiffness that falls with mesh refinement |
Theory (the laws, snap-back, artificial compliance) · the bilinear law and its exact strip · brittleness and the strip length · springs on every facet: compliance against h and K · counting the ‘+’ sides · where and when the inclusions debond · an LLM-generated interface script with defects. The whole HW runs in a few minutes on Colab.
| Concept | Key point | DOLFINx 0.11 |
|---|---|---|
| Cohesive law | \sigma_c, G_c=\int T\,d\delta, initial stiffness | ufl.exp, ufl.conditional on \vert[\![\mathbf u]\!]\vert |
| Snap-back | L>E\delta_0^2e^2/G_c (exponential law) | exact bar: u=TL/E+\delta |
| Interface only | one space per submesh, coupled on \Gamma | create_submesh, entity_maps, NonlinearProblem([...], kind="mpi") |
| Traps | ‘+’ side, ghost cells, garbage collection | explicit facet data, GhostMode.shared_facet, gc.collect() |
| Path following | opening control: bordered Newton, two solves | computeFunction, computeJacobian, assemble_vector(kind="mpi") |
| Springs everywhere | compliance \propto1/(Kh) | DG + Nitsche: only with Kh/E\gg1 |
Next week: natural convection (Boussinesq, de Vahl Davis) — and HW10.
