Skip to content

Commit 203130d

Browse files
Multi-Material support for mechanical problems (#203)
* Solid multi-material type * Add some pretty printing --------- Co-authored-by: termi-official <9196588+termi-official@users.noreply.github.com>
1 parent 10fa36d commit 203130d

12 files changed

Lines changed: 421 additions & 52 deletions

File tree

src/discretization/fem.jl

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,44 @@ function semidiscretize(split::ReactionDiffusionSplit{<:MonodomainModel}, discre
153153
return semidiscrete_ode
154154
end
155155

156-
function semidiscretize(model::QuasiStaticModel, discretization::FiniteElementDiscretization, mesh::AbstractGrid)
156+
# Solid mechanics semidiscretize interface
157+
function semidiscretize_register_subdomains!(dh, lvh, model, material_model::AbstractMaterialModel, discretization::FiniteElementDiscretization)
157158
sym = model.displacement_symbol
158159
ipc = _get_interpolation_from_discretization(discretization, sym)
159160
qrc = _get_quadrature_from_discretization(discretization, sym)
160-
dh = DofHandler(mesh)
161-
lvh = InternalVariableHandler(mesh)
162161
for name in discretization.subdomains
163162
add_subdomain!(dh, name, [ApproximationDescriptor(sym, ipc)])
164-
add_subdomain!(lvh, name, gather_internal_variable_infos(model.material_model), qrc, dh)
163+
add_subdomain!(lvh, name, gather_internal_variable_infos(material_model), qrc, dh)
164+
end
165+
end
166+
167+
function semidiscretize_register_subdomains!(dh, lvh, model, material_models::MultiMaterialModel, discretization::FiniteElementDiscretization)
168+
semidiscretize_register_subdomains_multi!(dh, lvh, model, material_models.materials, material_models.domains, material_models.domain_names, discretization)
169+
end
170+
@unroll function semidiscretize_register_subdomains_multi!(dh, lvh, model, material_models, domains, domain_names, discretization)
171+
sym = model.displacement_symbol
172+
ipc = _get_interpolation_from_discretization(discretization, sym)
173+
qrc = _get_quadrature_from_discretization(discretization, sym)
174+
idx = 1
175+
@unroll for material_model in material_models
176+
add_subdomain!(dh, domain_names[idx], [ApproximationDescriptor(sym, ipc)])
177+
add_subdomain!(lvh, domain_names[idx], gather_internal_variable_infos(material_model), qrc, dh)
178+
idx += 1
165179
end
180+
end
181+
182+
183+
function semidiscretize(model::QuasiStaticModel, discretization::FiniteElementDiscretization, mesh::AbstractGrid)
184+
sym = model.displacement_symbol
185+
ipc = _get_interpolation_from_discretization(discretization, sym)
186+
qrc = _get_quadrature_from_discretization(discretization, sym)
187+
dh = DofHandler(mesh)
188+
lvh = InternalVariableHandler(mesh)
189+
# for name in discretization.subdomains
190+
# add_subdomain!(dh, name, [ApproximationDescriptor(sym, ipc)])
191+
# add_subdomain!(lvh, name, gather_internal_variable_infos(model.material_model), qrc, dh)
192+
# end
193+
semidiscretize_register_subdomains!(dh, lvh, model, model.material_model, discretization)
166194
close!(dh)
167195
close!(lvh)
168196

src/discretization/operator.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ function AssembledNonlinearOperator(integrator::NonlinearIntegrator, dh::Abstrac
161161
)
162162
end
163163

164+
function Base.show(io::IO, cache::AssembledNonlinearOperator)
165+
println(io, "AssembledNonlinearOperator:")
166+
Base.show(io, typeof(cache.integrator))
167+
Base.show(io, MIME"text/plain"(), cache.dh)
168+
end
169+
164170
getJ(op::AssembledNonlinearOperator) = op.J
165171

166172
function update_linearization!(op::AssembledNonlinearOperator, u::AbstractVector, time)

src/mesh/simple_meshes.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ end
208208
@inline Ferrite.getcells(mesh::SimpleMesh, setname::String) = Ferrite.getcells(mesh.grid, setname)
209209
@inline Ferrite.getcelltype(mesh::SimpleMesh) = Ferrite.getcelltype(mesh.grid)
210210
@inline Ferrite.getcelltype(mesh::SimpleMesh, i::Int) = Ferrite.getcelltype(mesh.grid, i)
211+
@inline Ferrite.getcellset(mesh::SimpleMesh, name::String) = Ferrite.getcellset(mesh.grid, name)
211212
@inline Ferrite.getfacetset(mesh::SimpleMesh, name::String) = Ferrite.getfacetset(mesh.grid, name)
212213
@inline Ferrite.getnodeset(mesh::SimpleMesh, name::String) = Ferrite.getnodeset(mesh.grid, name)
213214

src/modeling/core/nonlinear.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@doc raw"""
2-
NonlinearIntegrator{CoefficientType}
2+
NonlinearIntegrator
33
44
Represents the integrand a the nonlinear form over some function space.
55
"""

src/modeling/functions.jl

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ end
9393

9494
solution_size(f::QuasiStaticFunction) = ndofs(f.dh)+ndofs(f.lvh)
9595
function local_function_size(f::QuasiStaticFunction)
96-
length(f.lvh.dh.subdofhandlers) == 0 && return 0
97-
return sum(Ferrite.n_components.(f.lvh.dh.subdofhandlers[1].field_interpolations); init=0)
96+
error("Local function size of QuasiStaticFunction can vary!")
9897
end
9998
function default_initial_condition!(u::AbstractVector, f::QuasiStaticFunction)
10099
fill!(u, 0.0)
@@ -105,10 +104,12 @@ function default_initial_condition!(u::AbstractVector, f::QuasiStaticFunction)
105104
qr = getquadraturerule(f.integrator.qrc, sdh)
106105
# ivsize_per_qp = sum(sdh.field_n_components; init=0) # FIXME broken...
107106
ivsize_per_qp = sum(Ferrite.n_components.(sdh.field_interpolations); init=0)
107+
ivsize_per_qp == 0 && continue
108+
material_model = get_material_model(f, sdh)
108109
for cell in CellIterator(sdh)
109110
for qp in QuadratureIterator(qr)
110111
q = @view uq[offset:(offset+ivsize_per_qp-1)]
111-
default_initial_state!(q, f.integrator.volume_model.material_model)
112+
default_initial_state!(q, material_model)
112113
offset += ivsize_per_qp
113114
end
114115
end
@@ -117,3 +118,17 @@ end
117118

118119
gather_internal_variable_infos(model::QuasiStaticModel) = gather_internal_variable_infos(model.material_model)
119120
gather_internal_variable_infos(model::AbstractMaterialModel) = InternalVariableInfo[]
121+
122+
@unroll function __get_material_model_multi(materials, domains, sdh)
123+
idx = 1
124+
@unroll for material materials
125+
if first(domains[idx]) sdh.cellset
126+
return material
127+
end
128+
idx += 1
129+
end
130+
error("MultiDomainIntegrator is broken: Requested to construct an internal cache for a SubDofHandler which is not associated with the integrator.")
131+
end
132+
__get_material_model(model::MultiMaterialModel, sdh) = __get_material_model_multi(model.materials, model.domains, sdh)
133+
__get_material_model(model::AbstractMaterialModel, sdh) = model
134+
get_material_model(f::QuasiStaticFunction, sdh) = __get_material_model(f.integrator.volume_model.material_model, sdh)

src/modeling/solid/elements.jl

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,60 @@ function assemble_element!(residualₑ::AbstractVector, uₑ::AbstractVector, ge
117117
end
118118
end
119119

120+
struct MultiMaterialModel{MaterialTuple} <: AbstractMaterialModel
121+
materials::MaterialTuple
122+
domains::Vector{OrderedSet{Int}} # These must match the subdofhandler sets and hence be disjoint
123+
domain_names::Vector{String} # These must match the subdofhandler sets and hence be disjoint
124+
function MultiMaterialModel(materials::MaterialTuple, subdomain_names::Vector{String}, mesh::AbstractGrid) where MaterialTuple
125+
return new{MaterialTuple}(materials, [getcellset(mesh, subdomain_name) for subdomain_name in subdomain_names], subdomain_names)
126+
end
127+
end
128+
129+
function setup_quasistatic_element_cache(material_model::MultiMaterialModel, qr::QuadratureRule, sdh::SubDofHandler, cv::CellValues)
130+
return setup_quasistatic_element_cache_multi(material_model.materials, material_model.domains, qr, sdh, cv)
131+
end
132+
@unroll function setup_quasistatic_element_cache_multi(materials::Tuple, domains::Vector, qr::QuadratureRule, sdh::SubDofHandler, cv::CellValues)
133+
idx = 1
134+
@unroll for material materials
135+
if first(domains[idx]) sdh.cellset
136+
return QuasiStaticElementCache(
137+
material,
138+
setup_coefficient_cache(material, qr, sdh),
139+
setup_internal_cache(material, qr, sdh),
140+
cv
141+
)
142+
end
143+
idx += 1
144+
end
145+
error("MultiDomainIntegrator is broken: Requested to construct an element cache for a SubDofHandler which is not associated with the integrator.")
146+
end
147+
function setup_quasistatic_element_cache(material_model::AbstractMaterialModel, qr::QuadratureRule, sdh::SubDofHandler, cv::CellValues)
148+
return QuasiStaticElementCache(
149+
material_model,
150+
setup_coefficient_cache(material_model, qr, sdh),
151+
setup_internal_cache(material_model, qr, sdh),
152+
cv
153+
)
154+
end
120155
function setup_element_cache(model::QuasiStaticModel, qr::QuadratureRule, sdh::SubDofHandler)
121156
@assert length(sdh.dh.field_names) == 1 "Support for multiple fields not yet implemented."
122157
field_name = first(sdh.dh.field_names)
123158
ip = Ferrite.getfieldinterpolation(sdh, field_name)
124159
ip_geo = geometric_subdomain_interpolation(sdh)
125160
cv = CellValues(qr, ip, ip_geo)
126-
return QuasiStaticElementCache(
127-
model.material_model,
128-
setup_coefficient_cache(model.material_model, qr, sdh),
129-
setup_internal_cache(model.material_model, qr, sdh),
130-
cv
131-
)
161+
return setup_quasistatic_element_cache(model.material_model, qr, sdh, cv)
162+
end
163+
164+
@unroll function setup_internal_cache_multi(materials::Tuple, domains::Vector, qr::QuadratureRule, sdh::SubDofHandler)
165+
idx = 1
166+
@unroll for material materials
167+
if first(domains[idx]) sdh.cellset
168+
return setup_internal_cache(material, qr, sdh)
169+
end
170+
idx += 1
171+
end
172+
error("MultiDomainIntegrator is broken: Requested to construct an internal cache for a SubDofHandler which is not associated with the integrator.")
173+
end
174+
function setup_internal_cache(model::MultiMaterialModel, qr::QuadratureRule, sdh::SubDofHandler)
175+
return setup_internal_cache_multi(model.materials, model.domains, qr, sdh)
132176
end

src/modeling/solid/materials.jl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ function _solve_local_sarcomere_dQdF(dQdλ, dλdF, λ, F, coefficients, active_t
251251
end
252252

253253
function solve_local_constraint(F::Tensor{2,dim}, coefficients, material_model::ActiveStressModel, state_cache::GenericFirstOrderRateIndependentCondensationMaterialStateCache, geometry_cache, qp, time) where dim
254+
Qflat, Qprevflat = _query_local_state(state_cache, geometry_cache, qp)
254255
# Early out if any of the previous local solves failed
255256
if state_cache.local_solver_cache.retcode (SciMLBase.ReturnCode.Default, SciMLBase.ReturnCode.Success)
256-
Qflat, _ = _query_local_state(state_cache, geometry_cache, qp)
257257
return Qflat, zero(Tensor{4,dim,Float64,4^dim})
258258
end
259259

@@ -310,11 +310,9 @@ function solve_local_constraint(F::Tensor{2,dim}, coefficients, material_model::
310310
return Q, J
311311
end
312312

313-
Qflat, Qprevflat = _query_local_state(state_cache, geometry_cache, qp)
314313
Q, J = solve_internal_timestep(material_model, state_cache, λ, dλdt, Qflat, Qprevflat)
315314
# Abort if local solve failed
316315
if state_cache.local_solver_cache.retcode (SciMLBase.ReturnCode.Default, SciMLBase.ReturnCode.Success)
317-
Qflat, _ = _query_local_state(state_cache, geometry_cache, qp)
318316
return Qflat, zero(Tensor{4,dim,Float64,4^dim})
319317
end
320318
Qflat .= Q
@@ -336,13 +334,14 @@ function solve_local_constraint(F::Tensor{2,dim}, coefficients, material_model::
336334
end
337335

338336
# Some debug materials
339-
Base.@kwdef struct LinearMaxwellMaterial{T} <: AbstractMaterialModel
337+
Base.@kwdef struct LinearMaxwellMaterial{T, sdim} <: AbstractMaterialModel
340338
E₀::T
341339
E₁::T
342340
μ::T
343341
η₁::T
344342
ν::T
345343
end
344+
LinearMaxwellMaterial(E₀::T, Eₗ::T, μ::T, η₁::T, ν::T) where T = LinearMaxwellMaterial{T,3}(E₀, Eₗ, μ, η₁, ν)
346345

347346
local_function_size(model::QuasiStaticModel) = local_function_size(model.material_model)
348347
function local_function_size(model::AbstractMaterialModel)
@@ -452,7 +451,7 @@ function stress_function(material::LinearMaxwellMaterial, ε, coefficients, ε
452451
return E₀ * ε + E₁ *- εᵛ)
453452
end
454453

455-
function stress_and_tangent(material_model::LinearMaxwellMaterial, F::Tensor{2}, coefficients, εᵛ)
454+
function stress_and_tangent(material_model::LinearMaxwellMaterial, F::Tensor{2}, coefficients, εᵛ::SymmetricTensor{2})
456455
ε = symmetric(F - one(F))
457456
∂σ∂ε, σ = Tensors.gradient->stress_function(material_model, ε, coefficients, εᵛ), ε, :all)
458457
return σ, ∂σ∂ε
@@ -463,9 +462,13 @@ function setup_coefficient_cache(m::LinearMaxwellMaterial, qr::QuadratureRule, s
463462
end
464463

465464
function setup_internal_cache(material_model::LinearMaxwellMaterial, qr::QuadratureRule, sdh::SubDofHandler)
466-
return nothing # FIXME what should we do here? :)
465+
return EmptyRateIndependentCondensationMaterialStateCache()
467466
end
468467

469-
function gather_internal_variable_infos(model::LinearMaxwellMaterial)
470-
return InternalVariableInfo(:εᵛ, 6) # TODO dimension info
468+
function gather_internal_variable_infos(model::LinearMaxwellMaterial{T, sdim}) where {T, sdim}
469+
if sdim == 3
470+
return InternalVariableInfo(:εᵛ, 6)
471+
else
472+
return InternalVariableInfo(:εᵛ, 4)
473+
end
471474
end

src/solver/nonlinear/multilevel_newton_raphson.jl

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,30 @@ Base.@kwdef mutable struct GenericLocalNonlinearSolverCache{JacobianType, Residu
1212
retcode::SciMLBase.ReturnCode.T = SciMLBase.ReturnCode.Default
1313
end
1414

15+
function Base.show(io::IO, cache::GenericLocalNonlinearSolverCache)
16+
println(io, "NewtonRaphsonSolverCache:")
17+
Base.show(io, cache.params)
18+
println(io, "J=$(typeof(cache.J)) with size $(size(cache.J))")
19+
println(io, "R=$(typeof(cache.residual)) with size $(size(cache.residual))")
20+
println(io, "C=$(typeof(cache.rhs_corrector)) with size $(size(cache.rhs_corrector))")
21+
println(io, "outer_tol=$(cache.outer_tol)")
22+
println(io, "status=$(cache.retcode)")
23+
end
24+
25+
function check_local_solve_covergence(local_solver_cache::GenericLocalNonlinearSolverCache)
26+
return local_solver_cache.retcode (SciMLBase.ReturnCode.Default, SciMLBase.ReturnCode.Success)
27+
end
28+
function check_local_solve_covergence(local_solver_cache::Tuple)
29+
return any(check_local_solve_covergence.(local_solver_cache))
30+
end
31+
32+
function set_local_solver_tol(local_solver_cache::GenericLocalNonlinearSolverCache, tol)
33+
local_solver_cache.outer_tol = tol
34+
end
35+
function set_local_solver_tol(local_solver_cache::Tuple, tol)
36+
set_local_solver_tol.(local_solver_cache, tol)
37+
end
38+
1539
"""
1640
MultilevelNewtonRaphsonSolver{T}
1741
@@ -29,6 +53,18 @@ struct MultiLevelNewtonRaphsonSolverCache{gCacheType, lCacheType} <: AbstractNon
2953
local_solver_cache::lCacheType
3054
end
3155

56+
function Base.show(io::IO, cache::MultiLevelNewtonRaphsonSolverCache)
57+
println(io, "MultiLevelNewtonRaphsonSolverCache:")
58+
Base.show(io, cache.global_solver_cache)
59+
if cache.local_solver_cache isa Tuple
60+
for local_solver_cache in cache.local_solver_cache
61+
Base.show(io, local_solver_cache)
62+
end
63+
else
64+
Base.show(io, cache.local_solver_cache)
65+
end
66+
end
67+
3268
function nlsolve!(u::AbstractVector, f::AbstractSemidiscreteFunction, mlcache::MultiLevelNewtonRaphsonSolverCache, t)
3369
cache = mlcache.global_solver_cache
3470

@@ -39,21 +75,21 @@ function nlsolve!(u::AbstractVector, f::AbstractSemidiscreteFunction, mlcache::M
3975
residualnormprev = 0.0
4076
Θ1prev = length(Θks) > 0 ? first(Θks) : 0.0
4177
resize!(Θks, 0)
42-
mlcache.local_solver_cache.outer_tol = Inf
78+
set_local_solver_tol(mlcache.local_solver_cache, Inf)
4379
while true
4480
cache.iter += 1
4581
residual .= 0.0
4682
@timeit_debug "update operator" update_linearization!(op, residual, u, t)
4783
# Check if local solve failed
48-
if mlcache.local_solver_cache.retcode (SciMLBase.ReturnCode.Default, SciMLBase.ReturnCode.Success)
84+
if check_local_solve_covergence(mlcache.local_solver_cache)
4985
@debug "Some local newton did not converge. Aborting. ||r|| = $residualnorm" _group=:nlsolve
5086
return false
5187
end
5288
@timeit_debug "elimination" eliminate_constraints_from_linearization!(cache, f)
5389
linear_solver_cache.isfresh = true # Notify linear solver that we touched the system matrix
5490

5591
residualnorm = residual_norm(cache, f)
56-
mlcache.local_solver_cache.outer_tol = residualnorm
92+
set_local_solver_tol(mlcache.local_solver_cache, residualnorm^2)
5793
if residualnorm < cache.parameters.tol && cache.iter > 1 # Do at least two iterations to get a sane convergence estimate
5894
break
5995
elseif cache.iter > cache.parameters.max_iter

src/solver/nonlinear/newton_raphson.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ mutable struct NewtonRaphsonSolverCache{OpType, ResidualType, T, NewtonType <: N
2727
iter::Int
2828
end
2929

30+
function Base.show(io::IO, cache::NewtonRaphsonSolverCache)
31+
println(io, "NewtonRaphsonSolverCache:")
32+
Base.show(io, cache.parameters)
33+
Base.show(io, cache.op)
34+
end
35+
3036
function setup_solver_cache(f::AbstractSemidiscreteFunction, solver::NewtonRaphsonSolver{T}) where {T}
3137
@unpack inner_solver = solver
3238
op = setup_operator(f, solver)

0 commit comments

Comments
 (0)