Skip to content

Commit 6bb4b30

Browse files
authored
Initializing improved differentiation interface (#16)
* Initializing improved differentiation interface Need to add more tests as no changes was required, specifically for diff with state variables + stress iterations * Fix get_unknowns for GeneralStressState with no unknowns * Set min julia to 1.11 to allow using sources * Set min julia to 1.11 to allow using sources (also in Project.toml) * Add devdocs * Add proper tests for differentiation * Make interface stricter * Provide type when creating material state * Fix stress iterations * Better test coverage for GeneralStressState, and fix following move to MaterialModelsTesting.jl of test features * Make docs appear for stress state derivative updates * Define consistent conversion routine naming * Fix spelling * Try adding an ignore to OT
1 parent bd87478 commit 6bb4b30

15 files changed

Lines changed: 425 additions & 314 deletions

.github/workflows/CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
matrix:
2020
version:
2121
- '1'
22-
- '1.8'
22+
- '1.11'
2323
os:
2424
- ubuntu-latest
2525
- macOS-latest

.typos.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
[files]
22
extend-exclude = ["*.toml"]
3+
4+
[default.extend-words]
5+
OT = "OT"

Project.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ authors = ["Knut Andreas Meyer and contributors"]
44
version = "0.2.4"
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]
11-
julia = "1.8"
12+
julia = "1.11"
1213

1314
[extras]
14-
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1515
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
16+
FiniteDiff = "6a86dc24-6348-571c-b903-95158fe2bd41"
17+
MaterialModelsTesting = "882b014b-b96c-4115-8629-e17fb35110d2"
18+
19+
[sources]
20+
MaterialModelsTesting = {url = "https://github.com/KnutAM/MaterialModelsTesting.jl"}
21+
#MaterialModelsTesting = {path = "/Users/knutan/.julia/dev/MaterialModelsTesting"}
1622

1723
[targets]
18-
test = ["Test", "ForwardDiff"]
24+
test = ["Test", "FiniteDiff", "MaterialModelsTesting"]

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ makedocs(;
1919
"Conversion" => "conversion.md",
2020
"Differentiation" => "differentiation.md",
2121
"Implementation" => "implementing.md",
22+
"Devdocs" => "devdocs.md"
2223
],
2324
)
2425

docs/src/conversion.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,33 @@
22
CurrentModule = MaterialModelsBase
33
```
44
# Conversion
5-
`MaterialModelsBase` defines an interface for converting parameters and state variables
5+
`MaterialModelsBase` defines an interface for converting parameters, state variables, and other objects
66
to and from `AbstractVector`s. This is useful when doing parameter identification, or
77
when interfacing with different languages that require information to be passed as arrays.
88

99
These function can be divided into those providing information about the material and state variables, and those doing the actual conversion.
1010

1111
## Information functions
12+
The following should be defined for new materials,
1213
```@docs
1314
get_tensorbase
15+
get_vector_length
16+
get_vector_eltype
17+
```
18+
19+
Whereas the following already have default implementations that should work provided that the above are implemented.
20+
```@docs
1421
get_num_statevars
15-
get_statevar_eltype
16-
get_num_params
17-
get_params_eltype
1822
get_num_tensorcomponents
1923
```
2024

2125
## Conversion functions
26+
The following should be defined for new materials, state variables, and alike
2227
```@docs
2328
tovector!
2429
fromvector
30+
```
31+
whereas the following already have default implementations that should work provided that the above functions are implemented,
32+
```@docs
2533
tovector
2634
```

docs/src/devdocs.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Internal documentation
2+
The following documentation returns to internals of the package and may change
3+
at any time.
4+
5+
```@docs
6+
MaterialModelsBase.stress_controlled_indices
7+
MaterialModelsBase.strain_controlled_indices
8+
```

src/MaterialModelsBase.jl

Lines changed: 11 additions & 7 deletions
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
@@ -24,8 +25,9 @@ export update_stress_state! # For nonzero stress
2425
# For parameter identification and differentiation of materials
2526
export tovector, tovector!, fromvector # Convert to/from `AbstractVector`s
2627
export get_num_tensorcomponents, get_num_statevars # Information about the specific material
27-
export get_num_params, get_params_eltype #
28-
export MaterialDerivatives, differentiate_material! # Differentiation routines
28+
export get_vector_length, get_vector_eltype # Get the length and type when converting objects to vectors
29+
export MaterialDerivatives, StressStateDerivatives # Derivative collections
30+
export differentiate_material! # Differentiation routines
2931
export allocate_differentiation_output #
3032

3133
abstract type AbstractMaterial end
@@ -112,14 +114,16 @@ end
112114
Return the (default) initial `state::AbstractMaterialState`
113115
of the material `m`.
114116
115-
Defaults to the empty `NoMaterialState()`
117+
Defaults to the empty `NoMaterialState{T}()`, where
118+
`T = get_vector_eltype(m)` (which defaults to `Float64`).
116119
"""
117-
function initial_material_state(::AbstractMaterial)
118-
return NoMaterialState()
120+
function initial_material_state(m::AbstractMaterial)
121+
T = get_vector_eltype(m)
122+
return NoMaterialState{T}()
119123
end
120124

121125
abstract type AbstractMaterialState end
122-
struct NoMaterialState <: AbstractMaterialState end
126+
struct NoMaterialState{T} <: AbstractMaterialState end
123127

124128
# Material cache
125129
"""
@@ -153,8 +157,8 @@ abstract type AbstractExtraOutput end
153157
struct NoExtraOutput <: AbstractExtraOutput end
154158

155159
include("vector_conversion.jl")
156-
include("differentiation.jl")
157160
include("stressiterations.jl")
161+
include("differentiation.jl")
158162
include("ErrorExceptions.jl")
159163

160164
end

src/differentiation.jl

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
struct MaterialDerivatives{T}
22
dσdϵ::Matrix{T}
3-
dσdⁿs::Matrix{T}
3+
#dσdⁿs::Matrix{T}
44
dσdp::Matrix{T}
55
dsdϵ::Matrix{T}
6-
dsdⁿs::Matrix{T}
6+
#dsdⁿs::Matrix{T}
77
dsdp::Matrix{T}
88
end
99

10+
function Base.getproperty(d::MaterialDerivatives, key::Symbol)
11+
if key === :dσdⁿs || key === :dsdⁿs
12+
error("You are probably assuming MaterialModelsBase v0.2 behavior for differentiation")
13+
else
14+
@inline getfield(d, key)
15+
end
16+
end
17+
1018
"""
1119
MaterialDerivatives(m::AbstractMaterial)
1220
1321
A struct that saves all derivative information using a `Matrix{T}` for each derivative,
14-
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
22+
where `T=get_vector_eltype(m)`. The dimensions are obtained from `get_num_tensorcomponents`,
23+
`get_num_statevars`, and `get_vector_length`. `m` must support `tovector` and `fromvector`, while
24+
the output of `initial_material_state` must support `tovector`, and in addition the element type
25+
of `tovector(initial_material_state(m))` must respect the element type in `tovector(m)` for any `m`.
26+
27+
The values should be updated in `differentiate_material!` by direct access of the fields,
28+
where `σ` is the stress, `ϵ` the strain, `s` and `ⁿs` are the current
1729
and old state variables, and `p` the material parameter vector.
1830
1931
* `dσdϵ`
@@ -25,17 +37,16 @@ and old state variables, and `p` the material parameter vector.
2537
2638
"""
2739
function MaterialDerivatives(m::AbstractMaterial)
28-
T = get_params_eltype(m)
40+
T = get_vector_eltype(m)
2941
n_tensor = get_num_tensorcomponents(m)
3042
n_state = get_num_statevars(m)
31-
n_params = get_num_params(m)
43+
n_params = get_vector_length(m)
44+
dsdp = ForwardDiff.jacobian(p -> tovector(initial_material_state(fromvector(p, m))), tovector(m))
3245
return MaterialDerivatives(
3346
zeros(T, n_tensor, n_tensor), # dσdϵ
34-
zeros(T, n_tensor, n_state), # dσdⁿs
3547
zeros(T, n_tensor, n_params), # dσdp
3648
zeros(T, n_state, n_tensor), # dsdϵ
37-
zeros(T, n_state, n_state), # dsdⁿs
38-
zeros(T, n_state, n_params) # dsdp
49+
dsdp
3950
)
4051
end
4152

@@ -65,5 +76,108 @@ allocate_differentiation_output(::AbstractMaterial) = NoExtraOutput()
6576
6677
Calculate the derivatives and save them in `diff`, see
6778
[`MaterialDerivatives`](@ref) for a description of the fields in `diff`.
79+
80+
differentiate_material!(ssd::StressStateDerivatives, stress_state, m, args...)
81+
82+
For material models implementing `material_response(m, args...)` and `differentiate_material!(::MaterialDerivatives, m, args...)`,
83+
this method will work automatically by
84+
1) Calling `σ, dσdϵ, state = material_response(stress_state, m, args...)` (except that `dσdϵ::FourthOrderTensor{dim = 3}` is extracted)
85+
2) Calling `differentiate_material!(ssd.mderiv::MaterialDerivatives, m, args..., dσdϵ::FourthOrderTensor{3})`
86+
3) Updating `ssd` according to the constraints imposed by the `stress_state`.
87+
88+
For material models that directly implement `material_response(stress_state, m, args...)`, this function should be overloaded directly
89+
to calculate the derivatives in `ssd`. Here the user has full control and no modifications occur automatically, however, typically the
90+
(total) derivatives `ssd.dσdp`, `ssd.dϵdp`, and `ssd.mderiv.dsdp` should be updated.
91+
The exact interface of the `StressStateDerivatives` datastructure is still not fixed, and may change in the future.
6892
"""
69-
function differentiate_material! end
93+
function differentiate_material! end
94+
95+
struct StressStateDerivatives{T}
96+
mderiv::MaterialDerivatives{T}
97+
dϵdp::Matrix{T}
98+
dσdp::Matrix{T}
99+
ϵindex::SMatrix{3, 3, Int} # To allow indexing by (i, j) into only
100+
σindex::SMatrix{3, 3, Int} # saved values to avoid storing unused rows.
101+
# TODO: Reduce the dimensions, for now all entries (even those that are zero) are stored.
102+
end
103+
104+
function StressStateDerivatives(::AbstractStressState, m::AbstractMaterial)
105+
mderiv = MaterialDerivatives(m)
106+
np = get_vector_length(m)
107+
nt = get_num_tensorcomponents(m) # Should be changed to only save non-controlled entries
108+
dϵdp = zeros(nt, np)
109+
dσdp = zeros(nt, np)
110+
# Should be changed to only save non-controlled entries
111+
vo = Tensors.DEFAULT_VOIGT_ORDER[3]
112+
if nt == 6
113+
index = SMatrix{3, 3, Int}(min(vo[i, j], vo[j, i]) for i in 1:3, j in 1:3)
114+
else
115+
index = SMatrix{3, 3, Int}(vo)
116+
end
117+
return StressStateDerivatives(mderiv, dϵdp, dσdp, index, index)
118+
end
119+
120+
function differentiate_material!(ssd::StressStateDerivatives, stress_state::AbstractStressState, m::AbstractMaterial, ϵ::AbstractTensor, args::Vararg{Any,N}) where {N}
121+
σ_full, dσdϵ_full, state, ϵ_full = stress_state_material_response(stress_state, m, ϵ, args...)
122+
differentiate_material!(ssd.mderiv, m, ϵ_full, args..., dσdϵ_full)
123+
124+
if isa(stress_state, NoIterationState)
125+
copy!(ssd.dσdp, ssd.mderiv.dσdp)
126+
fill!(ssd.dϵdp, 0)
127+
else
128+
sc = stress_controlled_indices(stress_state, ϵ)
129+
ec = strain_controlled_indices(stress_state, ϵ)
130+
dσᶠdϵᶠ_inv = inv(get_unknowns(stress_state, dσdϵ_full)) # f: unknown strain components solved for during stress iterations
131+
ssd.dϵdp[sc, :] .= .-dσᶠdϵᶠ_inv * ssd.mderiv.dσdp[sc, :]
132+
ssd.dσdp[ec, :] .= ssd.mderiv.dσdp[ec, :] .+ ssd.mderiv.dσdϵ[ec, sc] * ssd.dϵdp[sc, :]
133+
ssd.mderiv.dsdp .+= ssd.mderiv.dsdϵ[:, sc] * ssd.dϵdp[sc, :]
134+
end
135+
return reduce_tensordim(stress_state, σ_full), reduce_stiffness(stress_state, dσdϵ_full), state, ϵ_full
136+
end
137+
138+
"""
139+
stress_controlled_indices(stress_state::AbstractStressState, ::AbstractTensor)::SVector{N, Int}
140+
141+
Get the `N` indices that are stress-controlled in `stress_state`. The tensor input is used to
142+
determine if a symmetric or full tensor is used.
143+
"""
144+
function stress_controlled_indices end
145+
146+
"""
147+
strain_controlled_indices(stress_state::AbstractStressState, ::AbstractTensor)::SVector{N, Int}
148+
149+
Get the `N` indices that are strain-controlled in `stress_state`. The tensor input is used to
150+
determine if a symmetric or full tensor is used.
151+
"""
152+
function strain_controlled_indices end
153+
154+
# NoIterationState
155+
stress_controlled_indices(::NoIterationState, ::AbstractTensor) = SVector{0,Int}()
156+
strain_controlled_indices(::NoIterationState, ::SymmetricTensor) = @SVector([1, 2, 3, 4, 5, 6])
157+
strain_controlled_indices(::NoIterationState, ::Tensor) = @SVector([1, 2, 3, 4, 5, 6, 7, 8, 9])
158+
159+
# UniaxialStress
160+
stress_controlled_indices(::UniaxialStress, ::SymmetricTensor) = @SVector([2, 3, 4, 5, 6])
161+
stress_controlled_indices(::UniaxialStress, ::Tensor) = @SVector([2, 3, 4, 5, 6, 7, 8, 9])
162+
strain_controlled_indices(::UniaxialStress, ::AbstractTensor) = @SVector([1])
163+
164+
# UniaxialNormalStress
165+
stress_controlled_indices(::UniaxialNormalStress, ::AbstractTensor) = @SVector([2,3])
166+
strain_controlled_indices(::UniaxialNormalStress, ::SymmetricTensor) = @SVector([1, 4, 5, 6])
167+
strain_controlled_indices(::UniaxialNormalStress, ::Tensor) = @SVector([1, 4, 5, 6, 7, 8, 9])
168+
169+
# PlaneStress 12 -> 6, 21 -> 9
170+
stress_controlled_indices(::PlaneStress, ::SymmetricTensor) = @SVector([3, 4, 5])
171+
stress_controlled_indices(::PlaneStress, ::Tensor) = @SVector([3, 4, 5, 7, 8])
172+
strain_controlled_indices(::PlaneStress, ::SymmetricTensor) = @SVector([1, 2, 6])
173+
strain_controlled_indices(::PlaneStress, ::Tensor) = @SVector([1, 2, 6, 9])
174+
175+
# GeneralStressState
176+
stress_controlled_indices(ss::GeneralStressState{Nσ}, ::AbstractTensor) where= controlled_indices_from_tensor(ss.σ_ctrl, true, Val(Nσ))
177+
function strain_controlled_indices(ss::GeneralStressState{Nσ,TT}, ::AbstractTensor) where {Nσ,TT}
178+
N = Tensors.n_components(Tensors.get_base(TT)) -
179+
return controlled_indices_from_tensor(ss.σ_ctrl, false, Val(N))
180+
end
181+
function controlled_indices_from_tensor(ctrl::AbstractTensor, return_if, ::Val{N}) where N
182+
return SVector{N}(i for (i, v) in pairs(tovoigt(SVector, ctrl)) if v == return_if)
183+
end

0 commit comments

Comments
 (0)