Traction–separation laws#
Overview#
Cohesive zone models — also known as traction–separation laws (TSLs) — describe the constitutive response of an interface as a relation between the displacement jump \(\boldsymbol{\delta} = [\delta_n, \delta_{s1}, \delta_{s2}]\) and the traction \(\boldsymbol{T} = [T_n, T_{s1}, T_{s2}]\) in the local interface frame. The first component is the normal jump (opening / closing); the other two are the in-plane shear jumps. Most TSLs are damage-driven: a scalar damage variable accumulates as the effective separation grows, and the traction degrades from an initial elastic response toward zero at full debonding.
NEML2 provides a catalog of small composable primitives that you
assemble into a TSL via a ComposedModel, so a custom
variant (a different damage formula with the bilinear envelope, the
Camanho–Davila initiation with a new propagation criterion) is
usually an input-file change rather than a new class. A genuinely
new building block — a damage formula not on the list, a new
initiation criterion — still wants a small Model subclass,
following the same pattern as the existing primitives.
The catalog is organized by role:
Kinematic helpers (general-purpose primitives reused across modules):
VecComponents — split the displacement-jump
Vecinto three namedScalarcomponents.MacaulaySplit — split the normal jump into its positive and negative parts \(\langle \delta_n \rangle_\pm\).
ScalarPNorm — weighted \(p\)-norm of an arbitrary number of
Scalarinputs, \(y = \left(\sum_i w_i |x_i|^p + \varepsilon\right)^{1/p}\). With \(p = 2\) and unit weights this is the Euclidean norm, used for the tangential magnitude \(\delta_s = \sqrt{\delta_{s1}^2 + \delta_{s2}^2}\) and the bilinear effective separation \(\delta_m = \sqrt{(\delta_n^+)^2 + \delta_s^2}\).IrreversibleScalar — monotonically-increasing ratchet \(y = \max(y_{n-1}, x)\). Use it to enforce damage irreversibility or any history-max state.
TSL-specific kinematics:
ModeMixity — \(\beta = \delta_s / \delta_n^+\) in the opening branch; \(\beta = 0\) in compression.
Constitutive formulas:
CamanhoDavilaCriticalSeparation — Camanho–Davila mixed-mode critical (damage-onset) separation \(\delta_c(\beta)\).
BenzeggaghKenaneFullSeparation — mixed-mode full (failure) separation under the Benzeggagh–Kenane criterion, \(\delta_f(\beta, \delta_c)\).
PowerLawFullSeparation — mixed-mode full (failure) separation under the Alfano–Crisfield power-law criterion.
Damage-envelope primitives (each emits trial_damage):
BilinearDamage evaluates the bilinear envelope from the effective, critical, and full separations.
SalehaniIraniDamage evaluates the coupled exponential envelope from the three separation components.
Damage-update primitives consume trial_damage, previous-step
damage~1, and the time / time~1 pair (named t and t~1 by
default), then emit irreversible damage. Both accept a non-negative
viscosity \(\eta_v\), defaulting to zero:
BackwardEulerViscousDamage uses \(\alpha = \Delta t / (\eta_v + \Delta t)\).
ExponentialViscousDamage uses \(\alpha = 1 - \exp(-\Delta t / \eta_v)\).
Both updates have the form
The backward-Euler model matches the MOOSE update. The exponential
model analytically integrates the same evolution equation
\(\dot d=(d^*-d)/\eta_v\) while holding \(d^*\) constant over the step.
Positive viscosity delays damage growth, while viscosity = 0
recovers the irreversible rate-independent update exactly. A negative
viscosity is rejected when the updater is built.
Traction-assembly primitives consume the updated damage and emit
the public traction Vec:
OrthotropicLinearTraction provides orthotropic linear elasticity with no damage state.
BilinearTraction assembles the Camanho/Davila-style bilinear traction.
SalehaniIraniTraction assembles the 3D coupled Salehani-Irani traction.
The three responsibilities are separate Model leaves, so an envelope,
damage integration method, or traction assembly can be replaced without
changing the other two. The traction leaves optionally accept a
normal_penetration channel and penalty stiffness to resist
interpenetration.
When multiple leaves use the same material parameter, define it once with a
ScalarConstantParameter and wire each consumer to that model output. This
creates one provider (and one calibration parameter) with multiple DAG edges.
For example, the bilinear examples share K_shared between the initiation,
failure, and traction models; the Salehani-Irani examples share each
characteristic length between the damage and traction models. Repeating a
[Tensors] reference in each leaf supplies equal initial values, but does not
express a single shared calibration parameter.
Tip
Rewiring existing models
In previous versions, BilinearTraction and SalehaniIraniTraction contained the damage
envelope and viscous update internally. Under the decoupled architecture, existing inputs
are rewired by composing envelope -> update -> traction within a ComposedModel,
setting additional_outputs = 'damage' if the damage variable is needed for diagnostics or downstream models.
For SalehaniIraniTraction, normal_characteristic_length and tangential_characteristic_length
should use the same ScalarConstantParameter output in both leaves when they are calibrated.
For fixed values, both leaves can reference the same [Tensors] definition.
Math#
A bilinear cohesive law in the spirit of Camanho & Dávila uses an effective scalar separation
a mode-mixity ratio
a mixed-mode damage-onset (critical) separation \(\delta_c(\beta)\), and a mixed-mode failure separation \(\delta_f(\beta, \delta_c)\). Damage is then defined piecewise from the historical maximum effective separation \(\bar{\delta}_m = \max_{t' \le t} \delta_m(t')\),
so that the traction is
where \(K\) is the penalty stiffness. The Macaulay split on \(\delta_n\) keeps the compressive branch elastic so the interface resists interpenetration without accruing damage. The propagation criterion enters through \(\delta_f\): under the Benzeggagh–Kenane criterion,
while PowerLawFullSeparation implements the Alfano–Crisfield power-law form. The SalehaniIraniDamage envelope replaces the bilinear expression with a coupled exponential: the normal direction enters linearly and the two tangential directions enter quadratically in a single coupling exponent. Its updated damage is then consumed by SalehaniIraniTraction.
Example model composition#
The input below assembles the Camanho–Davila bilinear law (BK criterion) from the primitives above, drives it through a monotonic mixed-mode opening ramp, and pins the result against a reference:
# neml2
# Bilinear mixed-mode TSL with BK propagation criterion under a monotonic
# mixed-mode opening ramp. The jump is taken from below delta_init through
# the softening regime up to past delta_final, exercising the elastic,
# softening, and fully-degraded branches in one trajectory.
[Tensors]
[times]
type = Python
expr = 'linspace(Scalar(0.0).dynamic_batch, Scalar(1.0).dynamic_batch, 40)'
[]
[jumps]
type = Python
expr = 'Vec(torch.linspace(0.0, 1.0, 40, dtype=torch.float64).reshape(40, 1) * torch.tensor([0.05, 0.04, 0.03], dtype=torch.float64))'
[]
[]
[Drivers]
[driver]
type = TransientDriver
model = 'model'
prescribed_time = 'times'
prescribed_Vec_names = 'separation'
prescribed_Vec_values = 'jumps'
[]
[regression]
type = TransientRegression
driver = 'driver'
reference = 'gold/result.pt'
[]
[]
[Models]
[K_shared]
type = ScalarConstantParameter
value = 1000.0
[]
[S_shared]
type = ScalarConstantParameter
value = 10.0
[]
[decompose]
type = VecComponents
from = 'separation'
to = 'signed_normal_separation tangential_separation_1 tangential_separation_2'
[]
[tangential_separation]
type = ScalarPNorm
from = 'tangential_separation_1 tangential_separation_2'
to = 'tangential_separation'
exponent = 2.0
[]
[macaulay_n]
type = MacaulaySplit
from = 'signed_normal_separation'
to_positive = 'normal_separation'
to_negative = 'normal_penetration'
[]
[mode_mixity]
type = ModeMixity
[]
[critical_separation]
type = CamanhoDavilaCriticalSeparation
mode_mixity = 'mode_mixity'
penalty_stiffness = 'K_shared'
normal_strength = 10.0
shear_strength = 'S_shared'
[]
[full_separation]
type = BenzeggaghKenaneFullSeparation
mode_mixity = 'mode_mixity'
critical_separation = 'critical_separation'
penalty_stiffness = 'K_shared'
mode_I_fracture_toughness = 1.0
mode_II_fracture_toughness = 1.0
shear_strength = 'S_shared'
eta = 2.0
[]
[effective_separation]
type = ScalarPNorm
from = 'normal_separation tangential_separation'
to = 'effective_separation'
exponent = 2.0
[]
[damage_envelope]
type = BilinearDamage
critical_separation = 'critical_separation'
full_separation = 'full_separation'
[]
[damage_update]
type = BackwardEulerViscousDamage
[]
[traction]
type = BilinearTraction
normal_penetration = 'normal_penetration'
penalty_stiffness = 'K_shared'
[]
[model]
type = ComposedModel
models = 'decompose tangential_separation macaulay_n effective_separation
damage_envelope damage_update traction'
additional_outputs = 'damage'
[]
[]
Explanation#
Reading the [Models] block top to bottom:
decompose(VecComponents) splits the prescribedseparationVecinto the three scalar channelssigned_normal_separation,tangential_separation_1, andtangential_separation_2. The two tangential channels are then collapsed to a scalar magnitudetangential_separationbytangential_separation(ScalarPNorm with \(p = 2\)), giving \(\delta_s\) from the math above.macaulay_n(MacaulaySplit) takes the signed normal jump and emits its positive part asnormal_separation(\(\langle \delta_n \rangle_+\)) and its negative part asnormal_penetration(\(\langle \delta_n \rangle_- = \delta_n - \langle \delta_n \rangle_+\), i.e. the signed-negative branch that vanishes whenever \(\delta_n \ge 0\)). The positive part drives damage and softening; the negative part is handed to the traction assembly as the elastic penetration channel that keeps compression damage-free.mode_mixity(ModeMixity) computes \(\beta = \delta_s / \langle \delta_n \rangle_+\).critical_separation(CamanhoDavilaCriticalSeparation) evaluates \(\delta_c(\beta)\) from the penalty stiffness \(K\) and the per-mode strengths.full_separation(BenzeggaghKenaneFullSeparation) evaluates \(\delta_f(\beta, \delta_c)\) from the per-mode fracture toughnesses and the BK exponent \(\eta\). To swap to the Alfano–Crisfield criterion, replace this block with PowerLawFullSeparation — every other sub-model stays the same.effective_separationassembles \(\delta_m = \sqrt{(\delta_n^+)^2 + \delta_s^2}\) with another ScalarPNorm.damage_envelope(BilinearDamage) consumes \(\delta_m\), \(\delta_c\), and \(\delta_f\) and emits the bilineartrial_damage.damage_update(BackwardEulerViscousDamage) caps the trial value againstdamage~1, applies the selected viscous update, and emitsdamage. The composed model exposes it throughadditional_outputs = 'damage'for inspection.traction(BilinearTraction) consumes the updated damage, per-component jumps, and elastic penetration channel and assembles the traction vector.
The [Drivers] block prescribes the time grid and the displacement
jump history through a TransientDriver, and a TransientRegression
pins the run against gold/result.pt.
Note
The bilinear and Salehani-Irani graphs carry irreversible state through
their damage-update leaf. Under load–unload–reload schedules the
unloading limb returns elastically along the secant of the current
damaged stiffness, and reloading resumes the softening branch only
after the historical peak separation is exceeded. The bilinear_unload
scenario under
tests/regression/solid_mechanics/traction_separation_law/ exercises
this behavior.
Grain-boundary cavitation (Sham–Needleman)#
Overview#
The laws above degrade an interface through a scalar damage variable driven by the separation history. Creep rupture at a grain boundary works the other way round: the interface carries a physical population of cavities whose area fraction \(f\) and number density \(N\) evolve in time under the traction, and the separation is a consequence of that population rather than its cause. The simplified Sham–Needleman model in NEML2 is this second kind of law.
The primitives are deliberately small so the model can be assembled in
three forms — with nucleation, without it, and with the
nucleation-driving strain rate supplied from outside — by changing
which leaves appear in the [Models] block, not by toggling options:
Cavity-state kinematics:
EffectiveCavityFraction — the GTN-style coalescence map \(f \mapsto f^*\) that accelerates the softening functions once the cavities start to link up.
CavityVolumeOpening — the viscous normal opening \([\![u]\!]_n^v = NV - N_0V_0\), i.e. the smeared cavity volume.
Rates:
DiffusiveCavityGrowthRate — Hull–Rimmer diffusive growth of a single cavity’s volume, \(\dot V\).
CavityGrowthDamageRate — the geometric conversion from \(\dot V\) to an area-fraction rate.
CavityNucleationRate — stress-driven nucleation of new cavities, \(\dot N\).
CavityNucleationDamageRate — the area-fraction rate the newly nucleated cavities contribute.
GrainBoundarySlidingRate — viscous sliding of the boundary, softened by the cavitated fraction.
SlidingDrivenStrainRate — the local strain rate that sliding implies, used as the nucleation driver when no external one is available.
Traction assembly:
ShamNeedlemanTraction — linear springs in series with the cavitation and sliding, with a compressive contact penalty and a separated branch past \(f_F\).
Math#
A cavity of radius \(a\) on a boundary with number density \(N\) occupies
where \(h(\psi)\) is the cavity shape factor (\(h \approx 0.61\) at a tip angle of 75°). Eliminating \(a\) gives both the smeared opening and the geometric conversion used below,
Cavities soften the boundary through the effective fraction \(f^*\), which equals \(f\) until the coalescence threshold \(f_c\) and then rises on a steeper line to \(f_u\) at final separation \(f_F\),
The map saturates past \(f_F\) rather than extrapolating. Keeping the accelerated line going would drive \(f^*\) through one at \(f = f_c + (1 - f_c)(f_F - f_c)/(f_u - f_c)\) — a physical fraction below one, so an ordinary Newton iterate reaches it — and both softening denominators, \(q(f^*)\) and \(1 - f^*\), vanish there together. Saturating bounds every consumer of \(f^*\) by \(f_u < 1\) for any \(f\), so choosing \(f_u\) strictly below one is what keeps the rates finite.
Only the softening functions see \(f^*\); the geometry above keeps the physical \(f\), so the kinematic identity \([\![u]\!]_n^v = NV - N_0V_0\) holds exactly whatever the coalescence map does.
Cavity volume grows by grain-boundary diffusion (Hull–Rimmer),
which combines with the geometric conversion into the Sham–Needleman growth term
Keeping \(\dot V\) and the conversion in separate models is what lets a second growth mechanism be summed onto \(\dot V\) later without restating the geometry.
New cavities nucleate at a rate set by the normal traction and a driving strain rate, and they arrive at the current mean size:
The boundary also slides viscously, softened by the cavitated ligament, and that sliding supplies \(\dot\varepsilon\) when no external strain rate is prescribed:
Finally, each jump splits additively into an elastic and a viscous part, and the spring acts on the elastic part only — the springs are in series with the dashpots. A parallel spring would carry load forever and the interface would never separate completely. Writing \([\![u]\!]_n^v\) for the smeared cavity volume above,
and past final separation, \(f \ge f_F\), the interface carries \(T_n = K_p \min([\![u]\!]_n, 0)\) and \(T_t = 0\) — the compressive penalty survives separation, the tensile and shear load paths do not.
Note
Freezing \(f\), \(N\) and \([\![u]\!]_t^v\) after separation needs no clamping. Every rate above is driven by \(T_n\) or \(T_t\), and on the separated branch \(T_t = 0\) while \(T_n \le 0\) — so the sliding rate, both factors of the nucleation rate, and the Macaulay-bracketed growth rate all vanish identically and the state holds itself.
Warning
The separation test \(f < f_F\) in ShamNeedlemanTraction must read the old (converged) damage state, which in an implicit composition means wiring it explicitly:
[traction]
type = ShamNeedlemanTraction
cavity_fraction = 'cavity_fraction~1'
...
[]
Testing the trial unknown instead puts a jump discontinuity in the backward-Euler residual at \(f_F\). Below the threshold the residual is \(f - f_\text{old} - \Delta t\,\dot f\), which the accelerated softening drives sharply negative as \(f \to f_F^-\); at and above it every rate is switched off and the residual is \(f - f_\text{old} > 0\). The residual steps over zero without touching it, so the first step that would separate the interface has no root and simply fails to converge.
Switching on the converged state removes the discontinuity: the whole step is taken with intact rates, \(f\) is free to pass \(f_F\) (the saturated \(f^*\) keeps every softening denominator bounded while it does), and the next step freezes as described above. Separation is then resolved to within one time step — the usual first-order accuracy of an explicit switch.
Example model composition#
The input below assembles the complete model, integrating \(f\), \(N\) and \([\![u]\!]_t^v\) implicitly. Note that the normal viscous opening is not integrated: it is the algebraic function \(NV(f, N) - N_0V_0\), so the kinematic identity holds exactly at every converged step rather than only to integration order.
# neml2
# Simplified Sham-Needleman grain-boundary cavitation model, complete form.
#
# Three internal variables are integrated implicitly: the cavitated area
# fraction f, the cavity number density N, and the viscous sliding jump.
# The normal viscous opening is *not* integrated -- it is the algebraic
# function NV(f, N) - N0 V0, so the kinematic identity holds exactly at every
# converged step rather than only to integration order.
#
# Driven by a prescribed normal and tangential jump history.
[Tensors]
[times]
type = Python
expr = 'linspace(Scalar(0.0).dynamic_batch, Scalar(200.0).dynamic_batch, 100)'
[]
[normal_jumps]
type = Python
expr = 'Scalar(torch.linspace(0.0, 0.08, 100, dtype=torch.float64).unsqueeze(-1))'
[]
[tangential_jumps]
type = Python
expr = 'Scalar(torch.linspace(0.0, 0.04, 100, dtype=torch.float64).unsqueeze(-1))'
[]
[f0]
type = Python
expr = 'Scalar(torch.tensor(1.0e-2, dtype=torch.float64))'
[]
[N0]
type = Python
expr = 'Scalar(torch.tensor(2.0, dtype=torch.float64))'
[]
[]
[Drivers]
[driver]
type = TransientDriver
model = 'model'
prescribed_time = 'times'
prescribed_Scalar_names = 'normal_jump tangential_jump'
prescribed_Scalar_values = 'normal_jumps tangential_jumps'
ic_Scalar_names = 'cavity_fraction cavity_density'
ic_Scalar_values = 'f0 N0'
[]
[]
[Models]
# --- Softening: the GTN-style coalescence map accelerates f once it passes
# f_c. Only the softening functions see f*; the geometry keeps the physical f.
[effective_fraction]
type = EffectiveCavityFraction
coalescence_fraction = 0.5
failure_fraction = 0.8
ultimate_fraction = 0.99
[]
# --- Kinematics: the viscous normal opening is the smeared cavity volume.
[opening]
type = CavityVolumeOpening
cavity_shape_factor = 0.61
initial_cavity_fraction = 1.0e-2
initial_cavity_density = 2.0
[]
# --- Tractions from springs in series with the cavitation and sliding.
[traction]
type = ShamNeedlemanTraction
# Separation is detected from the converged state at the start of the
# step. Switching on the trial unknown instead puts a jump in the
# residual at f_F and leaves Newton with no root to find.
cavity_fraction = 'cavity_fraction~1'
normal_stiffness = 1000.0
shear_stiffness = 800.0
penalty_stiffness = 2000.0
failure_fraction = 0.8
[]
# --- Cavity growth: Hull-Rimmer diffusive volume rate, then the geometric
# conversion to an area-fraction rate.
[volume_rate]
type = DiffusiveCavityGrowthRate
diffusivity = 1.0e-6
[]
[growth_rate]
type = CavityGrowthDamageRate
cavity_shape_factor = 0.61
[]
# --- Sliding and the local strain rate it drives.
[sliding_rate]
type = GrainBoundarySlidingRate
sliding_viscosity = 4.0
[]
[strain_rate]
type = SlidingDrivenStrainRate
length_scale = 0.5
[]
# --- Nucleation and its contribution to the area fraction.
[nucleation_rate]
type = CavityNucleationRate
nucleation_coefficient = 3.0
reference_stress = 50.0
exponent = 2.0
[]
[nucleation_damage_rate]
type = CavityNucleationDamageRate
[]
[total_damage_rate]
type = ScalarLinearCombination
from = 'cavity_fraction_growth_rate cavity_fraction_nucleation_rate'
to = 'cavity_fraction_rate'
weights = '1 1'
[]
# --- Implicit integration of the three internal variables.
[integrate_f]
type = ScalarBackwardEulerTimeIntegration
variable = 'cavity_fraction'
[]
[integrate_N]
type = ScalarBackwardEulerTimeIntegration
variable = 'cavity_density'
[]
[integrate_slip]
type = ScalarBackwardEulerTimeIntegration
variable = 'viscous_sliding'
[]
[implicit_rate]
type = ComposedModel
models = 'effective_fraction opening traction volume_rate growth_rate sliding_rate strain_rate nucleation_rate nucleation_damage_rate total_damage_rate integrate_f integrate_N integrate_slip'
[]
[]
[EquationSystems]
[eq_sys]
type = NonlinearSystem
model = 'implicit_rate'
unknowns = 'cavity_fraction cavity_density viscous_sliding'
residuals = 'cavity_fraction_residual cavity_density_residual viscous_sliding_residual'
[]
[]
[Solvers]
[newton]
type = Newton
linear_solver = 'lu'
[]
[lu]
type = DenseLU
[]
[]
[Models]
[predictor]
type = ConstantExtrapolationPredictor
unknowns_Scalar = 'cavity_fraction cavity_density viscous_sliding'
[]
[model]
type = ImplicitUpdate
equation_system = 'eq_sys'
solver = 'newton'
predictor = 'predictor'
[]
[]
The nucleation-free variant drops
CavityNucleationRate, CavityNucleationDamageRate
and the ScalarLinearCombination that summed the two
contributions, renames the growth rate straight onto
cavity_fraction_rate, and prescribes cavity_density as a constant
force — two unknowns instead of three. The prescribed-strain-rate
variant drops only SlidingDrivenStrainRate and makes
strain_rate a force, which is the right form when the surrounding
grains are creeping and their strain rate, not the boundary sliding,
sets how fast cavities appear. Both live beside complete.i under
tests/unit/fixtures/sham_needleman/.
See also#
Model composition — the general pattern of stitching primitive
Models together with ComposedModel and letting the dependency resolver wire variable names automatically.Input files — anatomy of the HIT input format used throughout this page.
Elasticity — bulk-elastic primitives often paired with a TSL when modelling a bonded interface in a larger solid-mechanics calculation.
Syntax catalog — the complete autogenerated catalog of every registered
Modeltype, with the full option list for each primitive referenced above.