I’ve created two examples that compute the stiffness matrix for a hyperelastic material. The first benchmark successfully matches the performance of the analytical gradient approach. In this implementation, I precompute the shape function values and then calculate the Hessian.
However, I’d like to develop a more general form where I can simply input my deformation vector (called ‘x’ in my example) and compute the Hessian afterward. This is implemented in the second example, but its performance is several times slower.
For those interested: At the end of the response, I’ve included two complete files where both approaches are used to compute the full deformation of the body. Both implementations use the Ferrite.jl tutorial as their foundation.
Version 1
using Ferrite, Tensors, TimerOutputs, ProgressMeter, IterativeSolvers
using StaticArrays
using Enzyme, Chairmarks
function StaticArrays.setindex(t::AbstractTensor, v, i::Int...)
new_data = setindex(t.data,v,i...)
return typeof(t)(new_data)
end
function Tensors.basevec(::Type{V}) where V
_cache = zero(V)
return ntuple(
i -> setindex(_cache,one(eltype(V)),i),Val(length(_cache)))
end
function _grad!(f::F,g,x,pars) where F <:Function
g .= Enzyme.autodiff(Reverse,f,Active(x),Const(pars))[1][1]
nothing
end
@generated function _grad_and_hessian(f,
x::Tensor{2,N,T,L},pars) where {N,L,T}
xbb = basevec(Tensor{2,N,Float64})
g = zero(MMatrix{N,N})
hh = ntuple(_ -> zero(MMatrix{N,N}),Val(L))
return quote
fill!.($hh,0.0)
Enzyme.autodiff(Forward,_grad!,Const(f),
BatchDuplicated($g,$hh),BatchDuplicatedNoNeed(x,$xbb),Const(pars))
return $g,$hh
end
end
function grad_and_hessian(f,x::Tensor{2,N,T,L},
pars = nothing) where {N,L,T}
g,hh = _grad_and_hessian(f,x,pars)
H = Tensor{4,N}(((hh...)...,))
G = Tensor{2,N}((g...,))
return G,H
end
struct NeoHooke
μ::Float64
λ::Float64
end
function Ψ(∇ux,mp::NeoHooke)
μ = mp.μ
λ = mp.λ
F = one(∇ux) + ∇ux
C = tdot(F)
Ic = tr(C)
J = sqrt(det(C))
return μ / 2 * (Ic - 3 - 2log(J)) + λ/2 * (J - 1)^2
end
x = rand(Tensor{2,3,Float64})
G,H = grad_and_hessian(Ψ,x,NeoHooke(1.0,1.0))
mp = NeoHooke(1.0,1.0)
@b grad_and_hessian($Ψ,$x,$mp)
Version 2
using Enzyme
using StaticArrays
using Enzyme
using Chairmarks
using Ferrite
using Ferrite.Tensors: Vec, Tensor, eᵢ, tdot
using StaticArrays: SVector, setindex, SMatrix
using Bumper
struct NeoHooke
μ::Float64
λ::Float64
end
function Ψ(∇ux,mp::NeoHooke)
μ = mp.μ
λ = mp.λ
F = one(∇ux) + ∇ux
C = tdot(F)
Ic = tr(C)
J = sqrt(det(C))
return μ / 2 * (Ic - 3 - 2log(J)) + λ/2 * (J - 1)^2
end
function ham_fun(ue::AbstractVector{T},pars::P) where {T,P}
_,cv,mp = pars
μ = mp.μ
λ = mp.λ
int = zero(T)
for qp in 1:getnquadpoints(cv)
dΩ = getdetJdV(cv, qp)
∇ux = function_gradient(cv, qp, ue)
F = one(∇ux) + ∇ux
C = tdot(F)
Ic = tr(C)
J = sqrt(det(C))
int += (μ / 2 * (Ic - 3 - 2log(J)) + λ/2 * (J - 1)^2)*dΩ
end
int
end
function full_grad!(f::F,g,x,pars) where {F <:Function}
Enzyme.autodiff(Reverse,f,Duplicated(x,g),Const(pars))
end
function _grad_and_hessian!(grad,hess,
f::F,_x::AbstractVector{T},pars::P,vl::Val) where {F,T,P}
#INFO: Bumper Version
@no_escape begin
L = length(_x)
x = @alloc(T, L)
copyto!(x,_x)
xbb = ntuple(i -> begin
v = @alloc(T,L)
v .= zero(T)
v[i] = one(eltype(x))
v
end,vl)
g = @alloc(T,L)
g .= zero(T)
hh = ntuple(_ -> begin
v = @alloc(T,L)
v .= zero(T)
v
end,vl)
Enzyme.autodiff(Forward,full_grad!,Const(f),
BatchDuplicated(g,hh),BatchDuplicatedNoNeed(x,xbb),Const(pars))
copyto!(grad,g)
for i in axes(hess,1), j in axes(hess,2)
hess[i,j] = hh[j][i]
end
nothing
end
# #INFO: Default Version (Slower)
# x = _x
# xbb = ntuple(i -> begin
# v = zero(x)
# v[i] = one(eltype(x))
# v
# end,vl)
# g = grad
# hh = ntuple(i -> zero(x),vl)
# Enzyme.autodiff(Forward,full_grad!,Const(f),
# BatchDuplicated(g,hh),BatchDuplicatedNoNeed(x,xbb),Const(pars))
# for i in axes(hess,1), j in axes(hess,2)
# hess[i,j] = hh[j][i]
# end
end
function grad_and_hessian(f,x,pars)
n = length(x)
grad = zeros(n)
hess = zeros(n,n)
_grad_and_hessian!(grad,hess,f,x,pars,Val(n))
return grad,hess
end
x = rand(12)
# Generate a grid
N = 20
L = 1.0
left = zero(Vec{3})
right = L * ones(Vec{3})
grid = generate_grid(Tetrahedron, (N, N, N), left, right)
# Material parameters
E = 10.0
ν = 0.3
μ = E / (2(1 + ν))
λ = (E * ν) / ((1 + ν) * (1 - 2ν))
mp = NeoHooke(μ, λ)
# Finite element base
ip = Lagrange{RefTetrahedron, 1}()^3
qr = QuadratureRule{RefTetrahedron}(1)
qr_facet = FacetQuadratureRule{RefTetrahedron}(1)
cv = CellValues(qr, ip)
cell = first(CellIterator(grid))
reinit!(cv, cell)
p = (cell,cv,mp)
grad_and_hessian(ham_fun,x,p)
@b grad_and_hessian($ham_fun,$x,$p)
ADfull_Full.jl (5.2 KB)
ADhalf_Full.jl (6.8 KB)