mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-20 10:08:31 +00:00
1c67f1c1f8
* refactored code for solvers. * Added elementary tests for least-squares fitting of strain and stress fields * A more realistic postprocess + Xdmf writing test * removed debug keyword argument from test * Rewrite update_xdmf! New function to update Xdmf file no longer takes Solver object but xdmf, problem, time and fields to write, for example julia> update_xdmf!(xdmf, problem, 0.0, ["displacement", "temperature"]) All problems are written separately and put together into one SpatialCollection, allowing to have more structured Xdmf and making it easier to write complicated field configurations. Support for Xdmf API 3.0 added. * Support for Tensor6 field writing * moved update_xdmf! to io.jl * Removed some empty files * Not use old Postprocessor, obsolete code. * Not use old XDMF (obsolete code). Fixed test. * removed some postprocessing to pass test, maybe we should drop abaqus.jl from code as obsolete * add function get_temporal_collection back, it's used by update_xdmf of modal solver * postprocess of boundary problems also * added test for contact pressure. dl+quad test output was written in wrong file, fixed. * postprocess for contact pressure * contact pressure postprocess * with boundary problems always store also the primary unknown field * Change "reaction force" -> "lambda" * testing postprocess of reaction force also * sign convention
383 lines
12 KiB
Julia
383 lines
12 KiB
Julia
# This file is a part of JuliaFEM.
|
|
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
|
|
|
abstract AbstractProblem
|
|
abstract FieldProblem <: AbstractProblem
|
|
abstract BoundaryProblem <: AbstractProblem
|
|
abstract MixedProblem <: AbstractProblem
|
|
|
|
"""
|
|
General linearized problem to solve
|
|
(K₁+K₂)Δu + C1'*Δλ = f₁+f₂
|
|
C2Δu + D*Δλ = g
|
|
"""
|
|
type Assembly
|
|
|
|
M :: SparseMatrixCOO # mass matrix
|
|
|
|
# for field assembly
|
|
K :: SparseMatrixCOO # stiffness matrix
|
|
Kg :: SparseMatrixCOO # geometric stiffness matrix
|
|
f :: SparseMatrixCOO # force vector
|
|
fg :: SparseMatrixCOO #
|
|
|
|
# for boundary assembly
|
|
C1 :: SparseMatrixCOO
|
|
C2 :: SparseMatrixCOO
|
|
D :: SparseMatrixCOO
|
|
g :: SparseMatrixCOO
|
|
c :: SparseMatrixCOO
|
|
|
|
u :: Vector{Float64} # solution vector u
|
|
u_prev :: Vector{Float64} # previous solution vector u
|
|
u_norm_change :: Real # change of norm in u
|
|
|
|
la :: Vector{Float64} # solution vector la
|
|
la_prev :: Vector{Float64} # previous solution vector u
|
|
la_norm_change :: Real # change of norm in la
|
|
|
|
removed_dofs :: Vector{Int64} # manually remove dofs from assembly
|
|
end
|
|
|
|
function Assembly()
|
|
return Assembly(
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
SparseMatrixCOO(),
|
|
[], [], Inf,
|
|
[], [], Inf,
|
|
[])
|
|
end
|
|
|
|
function empty!(assembly::Assembly)
|
|
empty!(assembly.K)
|
|
empty!(assembly.Kg)
|
|
empty!(assembly.f)
|
|
empty!(assembly.fg)
|
|
empty!(assembly.C1)
|
|
empty!(assembly.C2)
|
|
empty!(assembly.D)
|
|
empty!(assembly.g)
|
|
empty!(assembly.c)
|
|
end
|
|
|
|
function isempty(assembly::Assembly)
|
|
T = isempty(assembly.K)
|
|
T &= isempty(assembly.Kg)
|
|
T &= isempty(assembly.f)
|
|
T &= isempty(assembly.fg)
|
|
T &= isempty(assembly.C1)
|
|
T &= isempty(assembly.C2)
|
|
T &= isempty(assembly.D)
|
|
T &= isempty(assembly.g)
|
|
T &= isempty(assembly.c)
|
|
return T
|
|
end
|
|
|
|
type Problem{P<:AbstractProblem}
|
|
name :: AbstractString # descriptive name for problem
|
|
dimension :: Int # degrees of freedom per node
|
|
parent_field_name :: AbstractString # (optional) name of parent field e.g. "displacement"
|
|
elements :: Vector{Element}
|
|
dofmap :: Dict{Element, Vector{Int64}} # connects element local dofs to global dofs
|
|
assembly :: Assembly
|
|
fields :: Dict{AbstractString, Field}
|
|
postprocess_fields :: Vector{String}
|
|
properties :: P
|
|
end
|
|
|
|
""" Construct a new field problem.
|
|
|
|
Examples
|
|
--------
|
|
Create vector-valued (dim=3) elasticity problem:
|
|
|
|
julia> prob1 = Problem(Elasticity, "this is my problem", 3)
|
|
julia> prob2 = Problem(Elasticity, 3)
|
|
|
|
"""
|
|
function Problem{P<:FieldProblem}(::Type{P}, name::AbstractString, dimension::Int64)
|
|
return Problem{P}(name, dimension, "none", [], Dict(), Assembly(), Dict(), Vector(), P())
|
|
end
|
|
function Problem{P<:FieldProblem}(::Type{P}, dimension::Int64)
|
|
return Problem(P, "$P problem", dimension)
|
|
end
|
|
|
|
""" Construct a new boundary problem.
|
|
|
|
Examples
|
|
--------
|
|
Create Dirichlet boundary problem for vector-valued (dim=3) elasticity problem.
|
|
|
|
julia> bc1 = Problem(Dirichlet, "support", 3, "displacement")
|
|
solver.
|
|
"""
|
|
function Problem{P<:BoundaryProblem}(::Type{P}, name, dimension, parent_field_name)
|
|
return Problem{P}(name, dimension, parent_field_name, [], Dict(), Assembly(), Dict(), Vector(), P())
|
|
end
|
|
function Problem{P<:BoundaryProblem}(::Type{P}, main_problem::Problem)
|
|
name = "$P problem"
|
|
dimension = get_unknown_field_dimension(main_problem)
|
|
parent_field_name = get_unknown_field_name(main_problem)
|
|
return Problem{P}(name, dimension, parent_field_name, [], Dict(), Assembly(), Dict(), Vector(), P())
|
|
end
|
|
|
|
function get_formulation_type(problem::Problem)
|
|
return :incremental
|
|
end
|
|
|
|
function get_unknown_field_name{P<:BoundaryProblem}(::Type{P})
|
|
return "lambda"
|
|
end
|
|
|
|
function get_assembly(problem)
|
|
return problem.assembly
|
|
end
|
|
|
|
""" Initialize element ready for calculation. """
|
|
function initialize!(problem::Problem, element::Element, time::Float64)
|
|
field_name = get_unknown_field_name(problem)
|
|
field_dim = get_unknown_field_dimension(problem)
|
|
nnodes = length(element)
|
|
|
|
# initialize primary field
|
|
if !haskey(element, field_name)
|
|
if field_dim == 1
|
|
update!(element, field_name, time => zeros(nnodes))
|
|
else
|
|
update!(element, field_name, time => [zeros(field_dim) for i=1:nnodes])
|
|
end
|
|
end
|
|
|
|
# if boundary problem, initialize field for main problem too
|
|
is_boundary_problem(problem) || return
|
|
field_name = get_parent_field_name(problem)
|
|
if !haskey(element, field_name)
|
|
if field_dim == 1
|
|
update!(element, field_name, time => zeros(nnodes))
|
|
else
|
|
update!(element, field_name, time => [zeros(field_dim) for i=1:nnodes])
|
|
end
|
|
end
|
|
end
|
|
|
|
function initialize!(problem::Problem, time::Float64=0.0)
|
|
for element in get_elements(problem)
|
|
initialize!(problem, element, time)
|
|
end
|
|
end
|
|
|
|
""" Update problem solution vector for assembly. """
|
|
function update!(problem::Problem, assembly::Assembly, u::Vector, la::Vector)
|
|
|
|
# resize & fill with zeros vectors if length mismatch with current solution
|
|
|
|
if length(u) != length(assembly.u)
|
|
info("resizing solution vector u")
|
|
resize!(assembly.u, length(u))
|
|
fill!(assembly.u, 0.0)
|
|
end
|
|
|
|
if length(la) != length(assembly.la)
|
|
info("resizing lagrange multiplier vector la")
|
|
resize!(assembly.la, length(la))
|
|
fill!(assembly.la, 0.0)
|
|
end
|
|
|
|
# copy current solutions to previous ones and add/replace new solution
|
|
# TODO: here we have couple of options and they need to be clarified
|
|
# for total formulation we are solving total quantity Ku = f while in
|
|
# incremental formulation we solve KΔu = f and u = u + Δu
|
|
assembly.u_prev = copy(assembly.u)
|
|
assembly.la_prev = copy(assembly.la)
|
|
|
|
if get_formulation_type(problem) == :total
|
|
assembly.u = u
|
|
assembly.la = la
|
|
elseif get_formulation_type(problem) == :incremental
|
|
assembly.u += u
|
|
assembly.la = la
|
|
elseif get_formulation_type(problem) == :forwarddiff
|
|
assembly.u += u
|
|
assembly.la += la
|
|
else
|
|
info("$(problem.name): unknown formulation type, don't know what to do with results")
|
|
error("serious failure with problem formulation: $(get_formulation_type(problem))")
|
|
end
|
|
|
|
# calculate change of norm
|
|
assembly.u_norm_change = norm(assembly.u - assembly.u_prev)
|
|
assembly.la_norm_change = norm(assembly.la - assembly.la_prev)
|
|
return assembly.u, assembly.la
|
|
end
|
|
|
|
""" Return global solution (u, la) for problem.
|
|
|
|
Notes
|
|
-----
|
|
If length of solution vector != number of nodes, i.e. field dimension is
|
|
something other than 1, reshape vectors so it's length matches to the
|
|
number of nodes so that one can easily get nodal results.
|
|
"""
|
|
function get_global_solution(problem::Problem, assembly::Assembly)
|
|
u = assembly.u
|
|
la = assembly.la
|
|
field_dim = get_unknown_field_dimension(problem)
|
|
if field_dim == 1
|
|
return u, la
|
|
else
|
|
nnodes = round(Int, length(u)/field_dim)
|
|
u = reshape(u, field_dim, nnodes)
|
|
u = Vector{Float64}[u[:,i] for i in 1:nnodes]
|
|
la = reshape(la, field_dim, nnodes)
|
|
la = Vector{Float64}[la[:,i] for i in 1:nnodes]
|
|
return u, la
|
|
end
|
|
end
|
|
|
|
""" Update solution from assebly to elements. """
|
|
function update!{P<:FieldProblem}(problem::Problem{P}, assembly::Assembly, elements::Vector{Element}, time::Float64)
|
|
u, la = get_global_solution(problem, assembly)
|
|
field_name = get_unknown_field_name(problem)
|
|
# update solution u for elements
|
|
for element in elements
|
|
connectivity = get_connectivity(element)
|
|
update!(element, field_name, time => u[connectivity])
|
|
end
|
|
end
|
|
|
|
function update!{P<:BoundaryProblem}(problem::Problem{P}, assembly::Assembly, elements::Vector{Element}, time::Float64)
|
|
u, la = get_global_solution(problem, assembly)
|
|
parent_field_name = get_parent_field_name(problem) # displacement
|
|
field_name = get_unknown_field_name(problem) # lambda
|
|
# update solution and lagrange multipliers for boundary elements
|
|
for element in elements
|
|
connectivity = get_connectivity(element)
|
|
update!(element, parent_field_name, time => u[connectivity])
|
|
update!(element, field_name, time => la[connectivity])
|
|
end
|
|
end
|
|
|
|
function get_elements(problem::Problem)
|
|
return problem.elements
|
|
end
|
|
|
|
function get_assembly(problem::Problem)
|
|
return problem.assembly
|
|
end
|
|
|
|
function length(problem::Problem)
|
|
return length(problem.elements)
|
|
end
|
|
|
|
function update!(problem::Problem, field_name::AbstractString, data)
|
|
#if haskey(problem.fields, field_name)
|
|
# update!(problem.fields[field_name], field_name::AbstractString, data)
|
|
#else
|
|
# problem.fields[field_name] = Field(data)
|
|
#end
|
|
update!(problem.elements, field_name::AbstractString, data)
|
|
end
|
|
|
|
function haskey(problem::Problem, field_name::AbstractString)
|
|
return haskey(problem.fields, field_name)
|
|
end
|
|
|
|
function getindex(problem::Problem, field_name::AbstractString)
|
|
return problem.fields[field_name]
|
|
end
|
|
|
|
""" Return field calculated to nodal points for elements in problem p. """
|
|
function (problem::Problem)(field_name::AbstractString, time::Float64=0.0)
|
|
#if haskey(problem, field_name)
|
|
# return problem[field_name](time)
|
|
#end
|
|
f = nothing
|
|
for element in get_elements(problem)
|
|
haskey(element, field_name) || continue
|
|
for (c, v) in zip(get_connectivity(element), element(field_name, time))
|
|
if f == nothing
|
|
f = Dict(c => v)
|
|
continue
|
|
end
|
|
if haskey(f, c)
|
|
if !isapprox(f[c], v)
|
|
info("several values for single node when returning field $field_name")
|
|
info("already have: $(f[c]), and trying to set $v")
|
|
end
|
|
else
|
|
f[c] = v
|
|
end
|
|
end
|
|
end
|
|
#f == nothing && return f
|
|
#update!(problem, field_name, time => f)
|
|
return f
|
|
end
|
|
|
|
""" Return the dimension of the unknown field of this problem. """
|
|
function get_unknown_field_dimension(problem::Problem)
|
|
return problem.dimension
|
|
end
|
|
|
|
""" Return the name of the unknown field of this problem. """
|
|
function get_unknown_field_name{P}(problem::Problem{P})
|
|
return get_unknown_field_name(P)
|
|
end
|
|
|
|
""" Return the name of the parent field of this (boundary) problem. """
|
|
function get_parent_field_name{P<:BoundaryProblem}(problem::Problem{P})
|
|
return problem.parent_field_name
|
|
end
|
|
|
|
function push!(problem::Problem, elements...)
|
|
push!(problem.elements, elements...)
|
|
end
|
|
|
|
function push!(problem::Problem, elements::Vector)
|
|
push!(problem.elements, elements...)
|
|
end
|
|
|
|
function push!(problem::Problem, elements_::Vector...)
|
|
for elements in elements_
|
|
push!(problem.elements, elements...)
|
|
end
|
|
end
|
|
|
|
function get_gdofs(element::Element, dim::Int)
|
|
conn = get_connectivity(element)
|
|
if length(conn) == 0
|
|
error("element connectivity not defined, cannot determine global dofs for element: $element")
|
|
end
|
|
gdofs = vec([dim*(i-1)+j for j=1:dim, i in conn])
|
|
return gdofs
|
|
end
|
|
|
|
function empty!(problem::Problem)
|
|
empty!(problem.assembly)
|
|
end
|
|
|
|
""" Return global degrees of freedom for element.
|
|
|
|
Notes
|
|
-----
|
|
First look dofs from problem.dofmap, it not found, update dofmap from
|
|
element.element connectivity using formula gdofs = [dim*(nid-1)+j for j=1:dim]
|
|
1. look element dofs from problem.dofmap
|
|
2. if not found, use element.connectivity to update dofmap and 1.
|
|
"""
|
|
function get_gdofs(problem::Problem, element::Element)
|
|
if !haskey(problem.dofmap, element)
|
|
dim = get_unknown_field_dimension(problem)
|
|
problem.dofmap[element] = get_gdofs(element, dim)
|
|
end
|
|
return problem.dofmap[element]
|
|
end
|