← All posts
Engineering · Case study

A crack that lives inside the mesh.

Ordinary finite elements can't represent a crack unless the mesh is cut to fit it — so every time the crack moves, you re-mesh. The eXtended Finite Element Method throws that rule out: the crack runs straight through elements, and a handful of extra unknowns let the displacement jump across it. We built it on physicsbase and pulled the textbook stress-intensity factor out to within 1.5%.

The physicsbase team
July 2026 · 9 min read

A crack is a tear in the displacement field. On one side of it the material moves up, on the other it stays put, and right at the crack face the two sides are simply not connected. Finite elements are built on the opposite assumption — that inside every element the displacement is a smooth polynomial that interpolates its corner nodes. Put those two facts together and you get the oldest headache in computational fracture: to model a crack with plain elements, the crack has to run along element edges, and the mesh has to be rebuilt to follow it as it grows. Decades of fracture codes were, at heart, very sophisticated re-meshers.

The eXtended Finite Element Method (XFEM), introduced by Belytschko and Black and by Moës, Dolbow and Belytschko in 1999, is the idea that made re-meshing optional. Keep a boring, structured mesh. Let the crack slice through the middle of elements wherever it likes. Then, for the elements the crack passes through, add extra degrees of freedom whose job is to carry the jump. The mesh never has to know where the crack is; the enrichment does. This post is about adding that capability to physicsbase — and, in keeping with the others, about exactly where the engine ends and the new physics begins.

The one equation that does the trick

Standard FEM writes the displacement in an element as a sum over its nodes, \( \mathbf{u}^h(\mathbf{x}) = \sum_i N_i(\mathbf{x})\,\mathbf{u}_i \), where the \(N_i\) are the usual shape functions. XFEM keeps that term verbatim and adds a second one:

\[ \mathbf{u}^h(\mathbf{x}) = \underbrace{\sum_{i} N_i(\mathbf{x})\,\mathbf{u}_i}_{\text{ordinary FE}} \;+\; \underbrace{\sum_{j\in\mathcal{E}} N_j(\mathbf{x})\,\big(H(\mathbf{x}) - H(\mathbf{x}_j)\big)\,\mathbf{a}_j}_{\text{crack enrichment}} \]

The new unknowns \(\mathbf{a}_j\) live only on the nodes \(\mathcal{E}\) of elements the crack cuts. The multiplier \(H(\mathbf{x})\) is the Heaviside sign function: \(+1\) on one side of the crack, \(-1\) on the other. Because it flips sign across the crack face, the enriched term can represent a clean discontinuity inside an element, with no element edge in sight. The shift \(-H(\mathbf{x}_j)\) is a tidy bookkeeping trick: it makes the enrichment vanish at the nodes, so the ordinary \(\mathbf{u}_i\) still equal the nodal displacements and boundary conditions stay simple.

That is the whole method, for a crack away from its tip. Two extra numbers per enriched node, multiplied by a sign function. Everything downstream — assembling stiffness, applying loads, solving — is ordinary finite elements. Which is precisely why it sits so naturally on top of an engine that already knows how to do ordinary finite elements.

What physicsbase supplies, and what XFEM adds

The benchmark is the single-edge-notch tension specimen (SENT): a rectangular plate with a horizontal crack cut in from the left edge to a depth a, pulled in uniform tension \(\sigma\). It's the "hello world" of fracture — it has a handbook answer to check against.

The engine's contribution is the physics of a plain elastic quad: the constitutive law and the strain-displacement operator. Everything the crack needs is assembled from those. The request/response boundary is small and sharp:

→ physicsbase: constitutive matrix for a plane-strain quad
from femengine.materials import Material

D = Material(E=210e9, nu=0.3).plane_strain_D()   # (3,3) stress = D · strain

# out: the same 3×3 the engine uses for any plane-strain stress analysis
# [[ 2.83e11  1.21e11  0      ]
#  [ 1.21e11  2.83e11  0      ]
#  [ 0        0        8.08e10 ]]

From there the XFEM layer builds each element's stiffness \( \mathbf{k}_e = \int_{\Omega_e} \mathbf{B}^\top \mathbf{D}\, \mathbf{B}\,\mathrm{d}\Omega \) the usual way — except the strain-displacement matrix \(\mathbf{B}\) grows extra columns for the enriched nodes. Those columns are the ordinary shape-function derivatives multiplied by the shifted sign function:

the XFEM layer — enriched B columns
# standard columns (every node): the usual quad B-operator
B[0, 2*i]   = dNdx[i]
B[1, 2*i+1] = dNdy[i]
B[2, 2*i]   = dNdy[i];  B[2, 2*i+1] = dNdx[i]

# enriched columns (cut-element nodes only): same derivatives × jump
psi = Hs - H_node          # shifted Heaviside: H on this sub-cell minus H at the node
B[0, col]   = dNdx[i] * psi
B[1, col+1] = dNdy[i] * psi
B[2, col]   = dNdy[i] * psi;  B[2, col+1] = dNdx[i] * psi

There is one subtlety the engine can't handle for us, and it's the crux of XFEM integration. The integrand \( \mathbf{B}^\top\mathbf{D}\,\mathbf{B} \) is now discontinuous inside a cut element — ordinary Gauss quadrature, which assumes a smooth integrand, would get the area of each side of the crack wrong. So we split every cut element into two sub-cells, one above the crack and one below, and integrate each separately, with \(H\) held to its constant value (\(+1\) or \(-1\)) on that side:

sub-cell integration of a cut element
for Hs, lo, hi in ((+1.0, 0.0, 1.0), (-1.0, -1.0, 0.0)):   # top half, bottom half
    for gx, gy in gauss_2x2:
        B = Bmat(node_coords, enriched, gx, remap(gy, lo, hi), Hs)
        ke += B.T @ D @ B * detJ * sub_scale        # D straight from physicsbase
The division of labour. physicsbase owns the material law and the shape functions — the parts that are just "an elastic quad," already validated against closed-form stress solutions. XFEM owns the enrichment columns, the sub-cell integration, and which nodes get enriched. The engine never learns what a crack is; it keeps handing back \(\mathbf{D}\) and derivatives, and the enrichment does the rest.

Getting K₁ without touching the crack tip

Engineers don't care about the displacement field for its own sake — they care about the stress-intensity factor \(K_I\), the single number that says how hard the crack tip is being pried open and whether the part will fail. The textbook way to extract it from a simulation is a special contour integral around the tip, which needs additional "branch-function" enrichment there. We deliberately took the cleaner route: the energy release rate.

Griffith's insight is that a crack advances when the structure can release stored elastic energy by growing. The energy released per unit of new crack area is \(G = \mathrm{d}U/\mathrm{d}a\), and for a linear-elastic material it ties directly to the stress-intensity factor:

\[ G = \frac{\mathrm{d}U}{\mathrm{d}a}, \qquad K_I = \sqrt{E'\,G}, \qquad E' = \frac{E}{1-\nu^2}\ \text{(plane strain)} \]

This is a beautiful fit for an engine, because \(U = \tfrac12\,\mathbf{u}^\top \mathbf{K}\,\mathbf{u}\) is just the strain energy of a solved model — something physicsbase already computes. We solve the specimen at two crack lengths, one element shorter and one longer, and take a central difference:

def stress_intensity(W, L, nx, ny, material, a, sigma):
    da = W / nx
    for aa in (a - da, a + da):              # crack one element shorter / longer
        m = XFEMCrack(W, L, nx, ny, material, aa)
        _, U[aa] = m.solve(sigma)            # strain energy of the solved model
    G  = (U[a+da] - U[a-da]) / (2*da)      # energy release rate  dU/da
    return np.sqrt(Eprime(material) * G)     # K_I = sqrt(E' G)

No tip enrichment, no interaction integral, no fragile near-tip stress sampling — just two solves and a finite difference. Because energy is a global quantity, it's far more forgiving than reading the singular stress field right at the tip, which is exactly why this route stays accurate with plain Heaviside enrichment.

What came out

We ran a plate of width \(W=1\), height \(2W\), Young's modulus 210 GPa, \(\nu=0.3\), under unit remote tension. The crack cuts through the middle row of elements; the enriched nodes and the recovered opening look like this:

Animation of the crack propagating through the fixed mesh, the opening widening as the crack deepens.
Every frame is a fresh XFEM solve on the same mesh — only the crack length changes. Watch the crack march through the interior of the elements while the mesh stays put, the opening widening and the displacement field (colour) redistributing as the tip advances. This is the whole point of XFEM: the geometry that moves is the crack, never the mesh.
Left: the crack cutting through a structured mesh with enriched nodes highlighted. Right: the recovered crack-opening displacement field.
Left: the crack (red) runs through the interior of the elements; the blue nodes are the ones that receive Heaviside enrichment, and the mesh is completely oblivious to the crack's path. Right: the crack-opening displacement the enrichment reconstructs, amplified ×4000, over the vertical-displacement field — the classic smooth parabolic opening that closes to zero at the tip.

The number that matters is \(K_I\). The handbook value for this geometry is \(K_I = \sigma\sqrt{\pi a}\,F(a/W)\) with the standard edge-crack polynomial \(F(\alpha) = 1.12 - 0.231\alpha + 10.55\alpha^2 - 21.72\alpha^3 + 30.39\alpha^4\). We compared across three crack depths and two mesh densities:

Crack depth a/WMeshK₁ — XFEMK₁ — handbookError
0.3040 × 411.55451.6115−3.54%
0.4040 × 412.28112.3580−3.26%
0.5040 × 413.39933.5423−4.04%
0.3080 × 811.58191.6115−1.83%
0.4080 × 812.32252.3580−1.50%
0.5080 × 813.46723.5423−2.12%

Two things to notice. First, the answers are close to the handbook across the whole range of crack depths — a few percent, from a method that never once meshed the crack. Second, and more telling, the error halves when the mesh is refined: from ~3.5% at 40×41 to ~1.5% at 80×81. That's the signature of a convergent method — the discretisation error is shrinking toward zero, not sitting at some irreducible modelling bias. The remaining gap is the price of using plain Heaviside enrichment without the extra crack-tip branch functions, and it keeps closing as the mesh gets finer.

Why this one is a good stress test

Topology optimization was one physics in a loop; phase-field fracture was two physics in a loop. XFEM is different again: it's a case where the approximation space itself is modified. The unknowns are no longer just "displacement at nodes" — some of them are jump amplitudes with a sign function baked in, and the quadrature has to respect a discontinuity that ignores element boundaries. An engine that only exposed a fixed catalogue of elements couldn't do this. Because physicsbase exposes the pieces an element is made of — the constitutive matrix, the shape-function derivatives — the enrichment can be layered on top without the engine's cooperation, and the part that is "just an elastic quad" is still the validated code underneath. That's the whole bet of treating simulation as composable primitives rather than a closed list of features.

Honest footnotes

We implemented Heaviside (jump) enrichment only, and placed the crack tip on an element boundary so the tip sits in a single column of nodes — which is why we could skip the near-tip branch-function enrichment that a production XFEM code adds. We recover \(K_I\) from the global energy release rate by finite-differencing the strain energy in the crack length, not from an interaction (M-) integral; the energy route is robust and mesh-convergent but doesn't separate mixed-mode \(K_I\)/\(K_{II}\), and here the loading is pure mode I by symmetry. The formulation is standard and common to the references below; the code is our own and reuses none of the published implementations. The point, as with the other case studies, is narrow: a general-purpose FE engine, handed nothing but its own constitutive matrix and shape functions, reproduces a fracture-mechanics benchmark to within a percent or two — and converges as you refine.

References

  1. T. Belytschko & T. Black, "Elastic crack growth in finite elements with minimal remeshing," Int. J. Numer. Methods Eng. 45 (1999), 601–620.
  2. N. Moës, J. Dolbow & T. Belytschko, "A finite element method for crack growth without remeshing," Int. J. Numer. Methods Eng. 46 (1999), 131–150.
  3. D. Datta, "Introduction to eXtended Finite Element (XFEM) Method" (2013). arXiv:1308.5208
  4. A. R. Jimenez et al., "An eXtended Finite Element Method Implementation in COMSOL Multiphysics: Solid Mechanics" (2021). arXiv:2109.03153
  5. H. Tada, P. C. Paris & G. R. Irwin, The Stress Analysis of Cracks Handbook, 3rd ed. (ASME Press, 2000) — edge-crack geometry factor.
Try it. The full solver is examples/xfem.py — Heaviside-enriched Quad4, sub-cell integration, and the energy-release-rate K₁ on top of physicsbase's Material. Read the docs →