Skip to content

Commit 583c637

Browse files
committed
Initializing improved differentiation interface
Need to add more tests as no changes was required, specifically for diff with state variables + stress iterations
1 parent 93e4475 commit 583c637

10 files changed

Lines changed: 376 additions & 24 deletions

Project.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ authors = ["Knut Andreas Meyer and contributors"]
44
version = "0.2.3"
55

66
[deps]
7+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
78
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
89
Tensors = "48a634ad-e948-5137-8d70-aa71f2a747f4"
910

1011
[compat]
1112
julia = "1.8"
1213

1314
[extras]
14-
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1515
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
16+
TestMaterials = "aa4f4413-326f-4ddd-a53e-c58801ebfeaf"
17+
18+
[sources]
19+
TestMaterials = {path = "test/TestMaterials"}
1620

1721
[targets]
18-
test = ["Test", "ForwardDiff"]
22+
test = ["Test", "TestMaterials"]

src/MaterialModelsBase.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module MaterialModelsBase
22
using Tensors, StaticArrays
3+
using ForwardDiff: ForwardDiff
34
import Base: @kwdef
45

56
# General interface for <:AbstractMaterial
@@ -153,8 +154,8 @@ abstract type AbstractExtraOutput end
153154
struct NoExtraOutput <: AbstractExtraOutput end
154155

155156
include("vector_conversion.jl")
156-
include("differentiation.jl")
157157
include("stressiterations.jl")
158+
include("differentiation.jl")
158159
include("ErrorExceptions.jl")
159160

160161
end

src/differentiation.jl

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ end
1212
1313
A struct that saves all derivative information using a `Matrix{T}` for each derivative,
1414
where `T=get_params_eltype(m)`. The dimensions are obtained from `get_num_tensorcomponents`,
15-
`get_num_statevars`, and `get_num_params`. The values should be updated in `differentiate_material!`
16-
by direct access of the fields, where `σ` is the stress, `ϵ` the strain, `s` and `ⁿs` are the current
15+
`get_num_statevars`, and `get_num_params`. `m` must support `tovector` and `fromvector`, while
16+
the output of `initial_material_state` must support `tovector`, and in addition the element type
17+
of `tovector(initial_material_state(m))` must respect the element type in `tovector(m)` for any `m`.
18+
19+
The values should be updated in `differentiate_material!` by direct access of the fields,
20+
where `σ` is the stress, `ϵ` the strain, `s` and `ⁿs` are the current
1721
and old state variables, and `p` the material parameter vector.
1822
1923
* `dσdϵ`
@@ -29,13 +33,14 @@ function MaterialDerivatives(m::AbstractMaterial)
2933
n_tensor = get_num_tensorcomponents(m)
3034
n_state = get_num_statevars(m)
3135
n_params = get_num_params(m)
36+
dsdp = ForwardDiff.jacobian(p -> tovector(initial_material_state(fromvector(p, m))), tovector(m))
3237
return MaterialDerivatives(
3338
zeros(T, n_tensor, n_tensor), # dσdϵ
3439
zeros(T, n_tensor, n_state), # dσdⁿs
3540
zeros(T, n_tensor, n_params), # dσdp
3641
zeros(T, n_state, n_tensor), # dsdϵ
3742
zeros(T, n_state, n_state), # dsdⁿs
38-
zeros(T, n_state, n_params) # dsdp
43+
dsdp
3944
)
4045
end
4146

@@ -66,4 +71,83 @@ allocate_differentiation_output(::AbstractMaterial) = NoExtraOutput()
6671
Calculate the derivatives and save them in `diff`, see
6772
[`MaterialDerivatives`](@ref) for a description of the fields in `diff`.
6873
"""
69-
function differentiate_material! end
74+
function differentiate_material! end
75+
76+
struct StressStateDerivatives{T}
77+
mderiv::MaterialDerivatives{T}
78+
dϵdp::Matrix{T}
79+
dσdp::Matrix{T}
80+
ϵindex::SMatrix{3, 3, Int} # To allow indexing by (i, j) into only
81+
σindex::SMatrix{3, 3, Int} # saved values to avoid storing unused rows.
82+
# TODO: Reduce the dimensions, for now all entries (even those that are zero) are stored.
83+
end
84+
85+
"""
86+
differentiate_material!(ssd::StressStateDerivatives, stress_state, m, args...)
87+
88+
For material models implementing `material_response(m, args...)` and `differentiate_material!(::MaterialDerivatives, m, args...)`,
89+
this method will work automatically by
90+
1) Calling `σ, dσdϵ, state = material_response(stress_state, m, args...)` (except that `dσdϵ::FourthOrderTensor{dim = 3}` is extracted)
91+
2) Calling `differentiate_material!(ssd.mderiv::MaterialDerivatives, m, args..., dσdϵ::FourthOrderTensor{3})`
92+
3) Updating `ssd` according to the constraints imposed by the `stress_state`.
93+
94+
For material models that directly implement `material_response(stress_state, m, args...)`, this function should be overloaded directly
95+
to calculate the derivatives in `ssd`. Here the user has full control and no modifications occur automatically, however, typically the
96+
(total) derivatives `ssd.dσdp`, `ssd.dϵdp`, and `ssd.mderiv.dsdp` should be updated.
97+
"""
98+
function differentiate_material!(ssd::StressStateDerivatives, stress_state::AbstractStressState, m::AbstractMaterial, args::Vararg{Any,N}) where {N}
99+
σ_full, dσdϵ_full, state, ϵ_full = stress_state_material_response(stress_state, m, args...)
100+
differentiate_material!(ssd.mderiv, m, args..., dσdϵ_full)
101+
dσᶠdϵᶠ_inv = inv(get_unknowns(stress_state, dσdϵ_full)) # f: unknown strain components solved for during stress iterations
102+
ssd.dϵdp[sc, :] .= .-dσᶠdϵᶠ_inv * ssd.mderiv.dσdp[sc, :]
103+
ssd.dσdp[ec, :] .= ssd.mderiv.dσdp .+ ssd.mderiv.dσdϵ[ec, sc] * ssd.dϵdp[sc, :]
104+
ssd.mderiv.dsdp .+= ssd.mderiv.dsdϵ[:, sc] * ssd.dϵdp[sc, :]
105+
return return reduce_tensordim(stress_state, σ_full), reduce_stiffness(stress_state, dσdϵ_full), state, ϵ_full
106+
end
107+
108+
"""
109+
stress_controlled_indices(stress_state::AbstractStressState, ::AbstractTensor)::SVector{N, Int}
110+
111+
Get the `N` indices that are stress-controlled in `stress_state`. The tensor input is used to
112+
determine if a symmetric or full tensor is used.
113+
"""
114+
function stress_controlled_indices end
115+
116+
"""
117+
strain_controlled_indices(stress_state::AbstractStressState, ::AbstractTensor)::SVector{N, Int}
118+
119+
Get the `N` indices that are strain-controlled in `stress_state`. The tensor input is used to
120+
determine if a symmetric or full tensor is used.
121+
"""
122+
function strain_controlled_indices end
123+
124+
# NoIterationState
125+
stress_controlled_indices(::NoIterationState, ::AbstractTensor) = SVector{0,Int}()
126+
strain_controlled_indices(::NoIterationState, ::SymmetricTensor) = @SVector([1, 2, 3, 4, 5, 6])
127+
strain_controlled_indices(::NoIterationState, ::Tensor) = @SVector([1, 2, 3, 4, 5, 6, 7, 8, 9])
128+
129+
# UniaxialStress
130+
stress_controlled_indices(::UniaxialStress, ::SymmetricTensor) = @SVector([2, 3, 4, 5, 6])
131+
stress_controlled_indices(::UniaxialStress, ::Tensor) = @SVector([2, 3, 4, 5, 6, 7, 8, 9])
132+
strain_controlled_indices(::UniaxialStress, ::AbstractTensor) = @SVector([1])
133+
134+
# UniaxialNormalStress
135+
stress_controlled_indices(::UniaxialNormalStress, ::AbstractTensor) = @SVector([2,3])
136+
strain_controlled_indices(::UniaxialNormalStress, ::SymmetricTensor) = @SVector([1, 4, 5, 6])
137+
strain_controlled_indices(::UniaxialNormalStress, ::Tensor) = @SVector([1, 4, 5, 6, 7, 8, 9])
138+
139+
# PlaneStress 12 -> 6, 21 -> 9
140+
stress_controlled_indices(::PlaneStress, ::SymmetricTensor) = @SVector([3, 4, 5])
141+
stress_controlled_indices(::PlaneStress, ::Tensor) = @SVector([3, 4, 5, 7, 8])
142+
strain_controlled_indices(::PlaneStress, ::SymmetricTensor) = @SVector([1, 2, 6])
143+
strain_controlled_indices(::PlaneStress, ::Tensor) = @SVector([1, 2, 6, 9])
144+
145+
# GeneralStressState
146+
stress_controlled_indices(ss::GeneralStressState{Nσ}, ::AbstractTensor) where= controlled_indices_from_tensor(ss.σ_ctrl, true, Val(Nσ))
147+
function strain_controlled_indices(ss::GeneralStressState{Nσ,TT}, ::AbstractTensor) where {Nσ,TT}
148+
N = Tensors.n_components(Tensors.get_base(TT)) -
149+
return controlled_indices_from_tensor(ss.σ_ctrl, false, Val(N))
150+
end
151+
function controlled_indices_from_tensor(ctrl::AbstractTensor, return_if, ::Val{N}) where N
152+
return SVector{N}(i for (i, v) in pairs(tovoigt(SVector, ctrl)) if v == return_if)
153+
end

src/stressiterations.jl

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ See also [`ReducedStressState`](@ref).
2525
material_response(::AbstractStressState, ::AbstractMaterial, args...)
2626

2727
@inline function material_response(stress_state::AbstractStressState, m::AbstractMaterial, args::Vararg{Any,N}) where N
28-
return reduced_material_response(stress_state, m, args...)
28+
σ, dσdϵ, state, ϵ_full = stress_state_material_response(stress_state, m, args...)
29+
return reduce_tensordim(stress_state, σ), reduce_stiffness(stress_state, dσdϵ), state, ϵ_full
2930
end
3031

3132
update_stress_state!(::AbstractStressState, σ) = nothing
@@ -168,7 +169,7 @@ get_tolerance(ss::UniaxialNormalStress) = get_tolerance(ss.settings)
168169
get_max_iter(ss::UniaxialNormalStress) = get_max_iter(ss.settings)
169170

170171
"""
171-
GeneralStressState(σ_ctrl::AbstractTensor{2,3,Bool}, σ::AbstractTensor{2,3,Bool}; kwargs...)
172+
GeneralStressState(σ_ctrl::AbstractTensor{2,3,Bool}, σ::AbstractTensor{2,3}; kwargs...)
172173
173174
Construct a general stress state controlled by `σ_ctrl` whose component is `true` if that
174175
component is stress-controlled and `false` if it is strain-controlled. If stress-controlled,
@@ -252,15 +253,15 @@ reduce_tensordim(::Val{dim}, a::SymmetricTensor{2}) where dim = SymmetricTensor{
252253
reduce_tensordim(::Val{dim}, A::SymmetricTensor{4}) where dim = SymmetricTensor{4,dim}((i,j,k,l)->A[i,j,k,l])
253254

254255

255-
function reduced_material_response(stress_state::NoIterationState,
256+
function stress_state_material_response(stress_state::NoIterationState,
256257
m::AbstractMaterial, ϵ::AbstractTensor, args::Vararg{Any,N}) where N
257258

258259
ϵ_full = expand_tensordim(stress_state, ϵ)
259260
σ, dσdϵ, new_state = material_response(m, ϵ_full, args...)
260-
return reduce_tensordim(stress_state, σ), reduce_tensordim(stress_state, dσdϵ), new_state, ϵ_full
261+
return σ, dσdϵ, new_state, ϵ_full
261262
end
262263

263-
function reduced_material_response(stress_state::IterationState,
264+
function stress_state_material_response(stress_state::IterationState,
264265
m::AbstractMaterial, ϵ::AbstractTensor, args::Vararg{Any,N}) where N
265266

266267
# Newton options
@@ -274,8 +275,7 @@ function reduced_material_response(stress_state::IterationState,
274275
σ_mandel = get_unknowns(stress_state, σ_full)
275276

276277
if norm(σ_mandel) < tol
277-
dσdϵ = reduce_stiffness(stress_state, dσdϵ_full)
278-
return reduce_tensordim(stress_state, σ_full), dσdϵ, new_state, ϵ_full
278+
return σ_full, dσdϵ_full, new_state, ϵ_full
279279
end
280280

281281
dσdϵ_mandel = get_unknowns(stress_state, dσdϵ_full)
@@ -284,14 +284,22 @@ function reduced_material_response(stress_state::IterationState,
284284
throw(NoStressConvergence("Stress iterations with the NewtonSolver did not converge"))
285285
end
286286

287-
reduce_stiffness(::State3D, dσdϵ_full::AbstractTensor{4,3}) = dσdϵ_full
287+
reduce_stiffness(ss::State3D, dσdϵ_full) = reduce_stiffness(Val(3), ss, dσdϵ_full)
288+
reduce_stiffness(ss::State2D, dσdϵ_full) = reduce_stiffness(Val(2), ss, dσdϵ_full)
289+
reduce_stiffness(ss::State1D, dσdϵ_full) = reduce_stiffness(Val(1), ss, dσdϵ_full)
288290

289-
function reduce_stiffness(stress_state, dσdϵ_full::AbstractTensor{4,3})
291+
reduce_stiffness(::Val{3}, ::State3D, dσdϵ_full::AbstractTensor{4,3}) = dσdϵ_full
292+
293+
function reduce_stiffness(::Union{Val{1}, Val{2}}, stress_state::IterationState, dσdϵ_full::AbstractTensor{4,3})
290294
∂σᶠ∂ϵᶠ, ∂σᶠ∂ϵᶜ, ∂σᶜ∂ϵᶠ, ∂σᶜ∂ϵᶜ = extract_substiffnesses(stress_state, dσdϵ_full)
291295
dσᶜdϵᶜ = ∂σᶜ∂ϵᶜ - ∂σᶜ∂ϵᶠ * (∂σᶠ∂ϵᶠ \ ∂σᶠ∂ϵᶜ)
292296
return convert_stiffness(dσᶜdϵᶜ, stress_state, dσdϵ_full)
293297
end
294298

299+
function reduce_stiffness(::Union{Val{1}, Val{2}}, stress_state::NoIterationState, dσdϵ_full::AbstractTensor{4,3})
300+
return reduce_tensordim(stress_state, dσdϵ_full)
301+
end
302+
295303
convert_stiffness(dσᶜdϵᶜ::SMatrix{1,1}, ::State1D, ::SymmetricTensor) = frommandel(SymmetricTensor{4,1}, dσᶜdϵᶜ)
296304
convert_stiffness(dσᶜdϵᶜ::SMatrix{1,1}, ::State1D, ::Tensor) = frommandel(Tensor{4,1}, dσᶜdϵᶜ)
297305
convert_stiffness(dσᶜdϵᶜ::SMatrix{3,3}, ::State2D, ::SymmetricTensor) = frommandel(SymmetricTensor{4,2}, dσᶜdϵᶜ)
@@ -411,7 +419,7 @@ function get_full_tensor(state::GeneralStressState{Nσ}, ::TT, v::SVector{Nσ,T}
411419
end
412420

413421
function get_unknowns(state::GeneralStressState{Nσ}, a::AbstractTensor{2,3,T}) where {Nσ, T}
414-
shear_factor = T(2)
422+
shear_factor = a isa SymmetricTensor ? T(2) : one(T)
415423
s(i,j) = i==j ? one(T) : shear_factor
416424
f(c) = ((i,j) = c; s(i,j)*(a[i,j]-state.σ[i,j]))
417425
return SVector{Nσ}((f(c) for c in state.σm_inds))

src/vector_conversion.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ Puts the Mandel components of `a` into the vector `v`.
8383
"""
8484
function tovector! end
8585

86+
tovector!(v::AbstractVector, ::NoMaterialState; kwargs...) = v
87+
8688
# Tensors.jl implementation
8789
function tovector!(v::AbstractVector, a::SecondOrderTensor; offset = 0)
8890
return tomandel!(v, a; offset)
@@ -107,6 +109,8 @@ Create a tensor with shape of `TT` with entries from the Mandel components in `v
107109
"""
108110
function fromvector end
109111

112+
fromvector(::AbstractVector, ::NoMaterialState; kwargs...) = NoMaterialState()
113+
110114
# Tensors.jl implementation
111115
function fromvector(v::AbstractVector, ::TT; offset = 0) where {TT <: SecondOrderTensor}
112116
return frommandel(Tensors.get_base(TT), v; offset)

0 commit comments

Comments
 (0)