Friction of a granular medium from its contacts
Before this page
Layered inclusions for the spring interface of a composite sphere, Layered sphere §7 for the strain its opening carries, and Self-consistent schemes for the self-consistent estimate.
A sand resists shear in proportion to the pressure it is under: its strength is frictional, and the macroscopic friction coefficient decreases as the sand gets looser. This page reproduces the micromechanical explanation of Maalej et al. (2009): the grains are rigid and only their contacts deform, and each contact obeys a Coulomb friction law. The question is how the friction of the contacts becomes the friction of the assembly, and why porosity matters so much.
The sand is a porous polycrystal. Each grain is a rigid sphere of radius
The normalization of this page
This page keeps the normalization of Maalej et al. (2009), with
1. A rigid grain bonded by springs
using MeanFieldHomogenization, TensND, ForwardDiff, JSON, Printf, Plots
gr() # headless backend; GKSwstype is set to "100" in make.jl
grain(ρ) = LayeredSphere(
(1.0,), (iso_stiffness(Inf, 1.0e20),); # a = 1, rigid core
interfaces = (SpringInterface(1.0, ρ),), # Kₙ = 1, Kₜ = ρ Kₙ
)The bulk modulus is infinite and the shear modulus
A rigid grain does not strain: all the strain of a grain is the opening of its contacts, LayeredSphere counts by default, the opening of the outer interface included.
2. The self-consistent moduli in closed form
The self-consistent estimate
using SymPyPythonCall
@syms k::positive μ::positive Kn::positive Kt::positive a::positive φ::positive
@syms ϵ::positive X::positive ρ::positive
C = iso_stiffness(k, μ)
G_sym = LayeredSphere((a,), (iso_stiffness(1 / ϵ, 1 / ϵ),); interfaces = (SpringInterface(Kn, Kt),))
αG, βG = get_data(gradient_gradient_concentration(G_sym, C, C))
aG, bG = get_data(flux_gradient_concentration(G_sym, C, C))
αP, βP = get_data(gradient_gradient_concentration(Ellipsoid(1.0), iso_stiffness(0, 0), C))
# The lowest order in ϵ of the numerator: the equation of the rigid grain.
function rigid(e)
num, _ = sympy.fraction(sympy.together(e))
c = sympy.Poly(num, ϵ).all_coeffs()
return c[findlast(x -> x != 0, c)]
end
bulk = rigid(3k * ((1 - φ) * αG + φ * αP) - (1 - φ) * aG)
k_sc = simplify(solve(bulk, k)[1])This is eq. (39) of the article,
shear = rigid(subs(2μ * ((1 - φ) * βG + φ * βP) - (1 - φ) * bG, k => k_sc))
factors = [f for (f, _) in sympy.factor_list(subs(shear, μ => X * a * Kn, Kt => ρ * Kn))[2]]All are positive but the last, the cubic (42) of the article, whose positive root is the dimensionless shear modulus
3. The numerical estimate
The same scheme with numbers. The fixed point is approached slowly as
function moduli(φ, ρ)
rve = RVE()
add_phase!(rve, :G, grain(ρ), Dict(:C => iso_stiffness(1.0, 1.0)); fraction = 1 - φ)
add_phase!(rve, :P, Ellipsoid(1.0), Dict(:C => iso_stiffness(0.0, 0.0)); fraction = φ)
start = ForwardDiff.value(ρ) # the scale of Kₜ, also under a Dual
sc = SelfConsistent(init = iso_stiffness(start, start), abstol = 1.0e-16, reltol = 1.0e-13, maxiters = 2000)
return k_mu(homogenize(rve, sc, :C))
end
cubic(X, φ, ρ) = 128X^3 + 16 * (3φ + 2 + 2ρ * (3φ - 1)) * X^2 +
2 * (3 * (3φ - 1) + 2ρ * (12φ - 5)) * X + 3ρ * (2φ - 1)
for (φv, ρv) in ((0.34, 0.055), (0.40, 0.055), (0.45, 1.0e-3))
kv, μv = moduli(φv, ρv)
@printf("φ = %.2f, ρ = %.3g: k = %.6e (eq. 39: %.6e), cubic(μ) / (3ρ) = %.1e\n",
φv, ρv, kv, 4 * (1 - φv) * μv / (3 * (4μv + φv)), cubic(μv, φv, ρv) / (3ρv))
endφ = 0.34, ρ = 0.055: k = 6.085451e-02 (eq. 39: 6.085451e-02), cubic(μ) / (3ρ) = 1.0e-13
φ = 0.40, ρ = 0.055: k = 2.798362e-02 (eq. 39: 2.798362e-02), cubic(μ) / (3ρ) = 8.7e-14
φ = 0.45, ρ = 0.001: k = 2.314930e-04 (eq. 39: 2.314930e-04), cubic(μ) / (3ρ) = 1.2e-134. From the contacts to the macroscopic friction
Two averages of the contact traction are needed, and both follow from the macroscopic stress without solving anything else. The mean normal traction on the contacts is
The derivative is taken by automatic differentiation through the self-consistent iteration, so it is exact to rounding:
function slope(φ, α, ρ)
G = moduli(φ, ρ)[1]
dG = ForwardDiff.derivative(r -> moduli(φ, r)[1], ρ)
return sqrt(9α^2 * φ / (4 * (1 - φ)^3 * (1 - 2φ)) * G^2 / (ρ^2 * dG) - 3φ / (4 * (1 - φ)))
endTwo failure modes are compared by the authors. In a ductile contact the tangential stiffness vanishes as it slides,
In a brittle contact the stiffness ratio keeps its elastic value until failure. The friction coefficient
data = JSON.parsefile(joinpath(pkgdir(MeanFieldHomogenization), "data", "literature", "maalej2009.json"))["quantities"]
α = data["friction_coefficient"]["value"]
ρ_brittle = data["stiffness_ratio_brittle"]["value"]
c_ck = data["caquot_kerisel_coefficient"]["value"]
ductile_limit(φ) = sqrt(α^2 / (2 * (1 - φ)^2 * (φ - 1 / 3)) - 3φ / (4 * (1 - φ)))
function caquot_kerisel(φ)
s = sin(atan(c_ck * (1 - φ) / φ))
return 2 * sqrt(3) * s / (3 - s)
end
φs = range(0.335, 0.495; length = 33) # eq. (57) is singular at φ = 1/2, where χ = 0
T_ductile = [slope(φ, α, 1.0e-6) for φ in φs]
T_brittle = [slope(φ, α, ρ_brittle) for φ in φs]
@printf("ductile, φ = 0.34: T = %.4f at ρ = 1e-6, %.4f in the limit (eq. 58)\n",
slope(0.34, α, 1.0e-6), ductile_limit(0.34))
φf = range(0.335, 0.495; length = 200)
plot(φs, T_ductile; seriestype = :scatter, ms = 3, label = "ductile, ρ = 10⁻⁶",
xlabel = "porosity φ", ylabel = "macroscopic friction T", ylims = (0, 6),
title = "Drucker–Prager slope of a granular medium (α = $α)")
plot!(φf, ductile_limit.(φf); lw = 2, ls = :dash, color = 1, label = "ductile limit, eq. (58)")
plot!(φs, T_brittle; lw = 2, marker = :circle, ms = 2, color = 2, label = "brittle, ρ = $ρ_brittle")
plot!(φf, caquot_kerisel.(φf); lw = 2, color = :gray, label = "Caquot–Kérisel")
The ductile slope grows without bound as the porosity decreases to
The transition between the two modes is a matter of
include(joinpath(pkgdir(MeanFieldHomogenization), "scripts", "common", "docviz.jl"))
φg = range(0.34, 0.5; length = 17)
lρ = range(-4, -0.5; length = 15)
Tg = [min(slope(φ, α, 10.0^l), 6.0) for l in lρ, φ in φg]
plotly_surface(φg, lρ, Tg; uid = "granular-slope-3d", title = "Macroscopic friction T(φ, ρ)",
xlabel = "porosity φ", ylabel = "log₁₀ ρ", zlabel = "T", colorscale = "Viridis",
reversescale = false, height = 480)Where to go next
Grains that are not rigid can fail too, and when they do the criterion is no longer a cone: The strength of a sandstone combines the sliding contacts of this page with a von Mises criterion in the grains, on the same self-consistent polycrystal.