Skip to content

Building a fractured-rock material ​

Before this page

Materials, for the contract the material below fulfills, and Fractured permeability, for the conductive cracks it is built from.

FracturedPoroelasticRock is the material of (Barthélémy and Daniel, 2011): two gradients in, two fluxes out, plus a permeability that follows the fracture apertures. The equations it implements are on the coupled poroelastic problem; this page is how to build one and what it returns.

Ingredients ​

an RVE whose crack families are ConductiveCrackmechanics and hydraulics from one microstructure
ω₀the initial aspect ratios — what the cubic law is normalized on
k_matrixthe matrix conductivity: small, but not zero
a schemeSelfConsistent() for a connected network, MoriTanaka() for a dilute one
julia
using MeanFieldHomogenization, TensND

C₀, kₛ, C_f = TensISO{3}(3 * 30.0, 2 * 18.0), 1.0e-18, 4.0e-18
props = Dict(:C => C₀, :K => TensISO{3}(kₛ))

rve = RVE()
add_phase!(rve, :M, Ellipsoid(1.0), props; fraction = :rest)
add_phase!(rve, :F, ConductiveCrack(1.0; conductivity = C_f), props; density = 0.1)

mat   = FracturedPoroelasticRock(rve, MoriTanaka(); ω₀ = (1.0e-3,), k_matrix = kₛ)
cache = MaterialCache()
st    = initial_state(mat)

One call, everything the FE code needs ​

julia
ε = from_tensors(TensND.Tensors.SymmetricTensor{2,3}((i,j) -> i == j == 3 ? -5.0e-4 : 0.0))
r = material_response(mat, (; ε = ε, p = 0.0), st, 0.0; cache = cache)

(σ₃₃ = r.fluxes.σ[3,3], φ = r.fluxes.φ,
 B₃₃ = -r.tangents.σp[3,3], invM = r.tangents.φp,
 ω = apertures(state(r))[1], k = transport_property(mat, state(r))[1,1])
(σ₃₃ = -0.016875000000000005, φ = -0.00018749999999999984, B₃₃ = 0.37499999999999967, invM = 0.006944444444444436, ω = 0.0005523767225540443, k = 1.1128708042848304e-18)

The gradients go in as a NamedTuple, the fluxes and the four tangent blocks come out of one response, and the permeability is read off the new state — transport_property rather than a flux, because it feeds a different balance equation.

A transient flow problem also needs at the start of the step: fluid_content recomputes it from the stored state, so a driver never has to carry it alongside.

julia
Δφ = r.fluxes.φ - fluid_content(mat, st)
-0.00018749999999999984

Compression closes the fracture, pressure reopens it ​

Holding that compressive strain and raising the pore pressure reopens the fracture, and the permeability recovers with it — the coupling the model exists to capture:

(GPa)00.0050.010
5.52·10⁻⁴6.35·10⁻⁴7.18·10⁻⁴
(m²)1.11·10⁻¹⁸1.15·10⁻¹⁸1.20·10⁻¹⁸

Once a family closes it leaves the intact matrix behind: its contributions to the Biot tensor and to the inverse Biot modulus vanish,   and  , and transport_property returns nothing — the fracture carries no flow at all.

Returning nothing is an answer, not a failure

A driver that forwards it straight into a mobility gets a MethodError at the worst possible moment. Test for it — the well test does.

Where to go next ​

Ferrite backend provides the element routines that call this material inside a finite-element loop, and A fractured-reservoir well test runs it on a fractured reservoir.