Cracks and crack density
Cracks are inclusions with zero volume — a pore flattened to a surface. MeanFieldHom handles them with the same RVE/ homogenize machinery as ordinary inclusions, but parameterized by crack density instead of volume fraction, and characterized by a crack-opening-displacement (COD) tensor rather than an Eshelby tensor.
The crack-opening-displacement tensor
For a flat crack in an infinite matrix $\mathbb{C}_0$, the displacement jump across the crack faces under remote stress is linear in that stress through the COD tensor B:
using MeanFieldHom
using TensND
using LinearAlgebra
using Plots
gr() # headless backend; GKSwstype is set to "100" in make.jl
E, ν = 30.0, 0.2
k_m, μ_m = E / (3 * (1 - 2ν)), E / (2 * (1 + ν))
C0 = iso_stiffness(k_m, μ_m)
pc = PennyCrack(1.0)
B = cod_tensor(pc, C0)
B[3, 3] # normal-opening component, crack-local frame0.05432488724203361B depends only on the matrix stiffness and the crack's shape (circular penny, elliptic, or ribbon) — not on its size, which enters separately through the crack density.
From size to density: the compliance contribution
A single crack contributes an increment to the macroscopic compliance proportional to its size. For a population of $N$ non-interacting penny cracks per unit volume, each of radius $a$, the (dimensionless) crack density is $\varepsilon = N a^3$ (Budiansky–O'Connell), and the dilute compliance correction is
\[\Delta\mathbb{S} = \frac{4\pi}{3}\,\varepsilon\,\mathbb{H}, \qquad \mathbb{H} = \tfrac{3}{4}\,\hat{\mathbf n}\otimes^{\!s}\mathbf B\otimes^{\!s}\hat{\mathbf n},\]
[31], [32] — $\mathbb{H}$ is the size-independent compliance contribution tensor (compliance_contribution), computed once from B and the crack normal $\hat{\mathbf n}$, and reused unchanged across an entire crack population that shares the same orientation and shape.
H = compliance_contribution(pc, C0)
ΔS = delta_compliance(pc, H, 0.05) # ε = 0.05Cracks in an RVE — the density keyword
Rather than assembling ΔS by hand, add_phase! accepts a crack geometry with a density keyword in place of fraction; the scheme machinery then computes the appropriate contribution internally for whichever scheme is requested:
function build(ρ)
r = RVE(:M)
add_matrix!(r, Ellipsoid(1.0), Dict(:C => C0))
add_phase!(r, :CRACK, PennyCrack(1.0), Dict(:C => C0); density = ρ, symmetrize = IsoSymmetrize())
return r
end
crack_density(build(0.05), :CRACK)0.05symmetrize = IsoSymmetrize() declares a uniform spatial distribution of crack orientations (see the porous benchmark tutorial for the same keyword on ordinary inclusions), so the macroscopic effect of an isotropically-oriented crack population is itself isotropic.
Note: SelfConsistent does not support cracks (its strain-localization tensor becomes singular for zero-volume inclusions); use AsymmetricSelfConsistent instead. See the cracks manual page for the full API, including finite interface stiffness (Sevostianov springs).
Effective modulus vs. crack density
ρs = collect(range(0.0, 0.3; length = 20))
schemes = [
(MoriTanaka(), "MoriTanaka", :black),
(AsymmetricSelfConsistent(; select_best = true), "AsymmetricSelfConsistent", :purple),
(DifferentialScheme(; nsteps = 100), "Differential", :orange),
(PonteCastanedaWillis(), "PCW", :green),
]
plt = plot(;
xlabel = "crack density ρ", ylabel = "k_eff",
legend = :bottomleft, framestyle = :box, size = (760, 480),
)
for (sch, label, color) in schemes
ks = [k_mu(best_fit_iso(homogenize(build(ρ), sch, :C)))[1] for ρ in ρs]
plot!(plt, ρs, ks; label = label, color = color, lw = 2)
end
pltStiffness decreases monotonically with crack density for every scheme, as expected — but at different rates: AsymmetricSelfConsistent degrades fastest because it lets cracks interact through a softening effective medium, exactly as the self-consistent porous estimate did in an earlier tutorial. Crack density plays the same qualitative role here that porosity played there; what differs is the shape (a surface rather than a volume) entering the localization tensor.