mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-09 04:36:34 +00:00
888 lines
27 KiB
Julia
888 lines
27 KiB
Julia
# This file is a part of JuliaFEM.
|
|
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
|
|
|
abstract AbstractSolver
|
|
|
|
type Solver{S<:AbstractSolver}
|
|
name :: AbstractString # some descriptive name for problem
|
|
time :: Float64 # current time
|
|
problems :: Vector{Problem}
|
|
norms :: Vector{Tuple} # solution norms for convergence studies
|
|
ndofs :: Int # number of degrees of freedom in problem
|
|
xdmf :: Nullable{Xdmf} # input/output handle
|
|
initialized :: Bool
|
|
u :: Vector{Float64}
|
|
la :: Vector{Float64}
|
|
properties :: S
|
|
end
|
|
|
|
|
|
function Solver{S<:AbstractSolver}(::Type{S}, name="solver", properties...)
|
|
variant = S(properties...)
|
|
solver = Solver{S}(name, 0.0, [], [], 0, nothing, false, [], [], variant)
|
|
return solver
|
|
end
|
|
|
|
function Solver{S<:AbstractSolver}(::Type{S}, problems::Problem...)
|
|
solver = Solver(S, "$(S)Solver")
|
|
push!(solver.problems, problems...)
|
|
return solver
|
|
end
|
|
|
|
function get_problems(solver::Solver)
|
|
return solver.problems
|
|
end
|
|
|
|
function push!(solver::Solver, problem)
|
|
push!(solver.problems, problem)
|
|
end
|
|
|
|
function getindex(solver::Solver, problem_name)
|
|
for problem in get_problems(solver)
|
|
if problem.name == problem_name
|
|
return problem
|
|
end
|
|
end
|
|
throw(KeyError(problem_name))
|
|
end
|
|
|
|
# one-liner helpers to identify problem types
|
|
|
|
is_field_problem(problem) = false
|
|
is_field_problem{P<:FieldProblem}(problem::Problem{P}) = true
|
|
is_boundary_problem(problem) = false
|
|
is_boundary_problem{P<:BoundaryProblem}(problem::Problem{P}) = true
|
|
get_field_problems(solver::Solver) = filter(is_field_problem, get_problems(solver))
|
|
get_boundary_problems(solver::Solver) = filter(is_boundary_problem, get_problems(solver))
|
|
|
|
"""
|
|
Posthook for field assembly. By default, do nothing.
|
|
This can be used to make some modifications for assembly
|
|
after all elements are assembled.
|
|
|
|
Examples
|
|
--------
|
|
function field_assembly_posthook!(solver::Solver,
|
|
K::SparseMatrixCSC,
|
|
Kg::SparseMatrixCSC,
|
|
f::SparseMatrixCSC,
|
|
fg::SpareMatrixCSC)
|
|
info("doing stuff, size(K) = ", size(K))
|
|
end
|
|
"""
|
|
function field_assembly_posthook!
|
|
end
|
|
|
|
"""Return one combined field assembly for a set of field problems.
|
|
|
|
Parameters
|
|
----------
|
|
solver :: Solver
|
|
|
|
Returns
|
|
-------
|
|
M, K, Kg, f, fg :: SparseMatrixCSC
|
|
|
|
Notes
|
|
-----
|
|
If several field problems exists, they are simply summed together, so
|
|
problems must have unique node ids.
|
|
|
|
"""
|
|
function get_field_assembly(solver::Solver; show_info=true)
|
|
problems = get_field_problems(solver)
|
|
|
|
M = SparseMatrixCOO()
|
|
K = SparseMatrixCOO()
|
|
Kg = SparseMatrixCOO()
|
|
f = SparseMatrixCOO()
|
|
fg = SparseMatrixCOO()
|
|
|
|
for problem in problems
|
|
append!(M, problem.assembly.M)
|
|
append!(K, problem.assembly.K)
|
|
append!(Kg, problem.assembly.Kg)
|
|
append!(f, problem.assembly.f)
|
|
append!(fg, problem.assembly.fg)
|
|
end
|
|
|
|
if solver.ndofs == 0
|
|
solver.ndofs = size(K, 1)
|
|
show_info && info("automatically determined problem dimension, ndofs = $(solver.ndofs)")
|
|
end
|
|
|
|
M = sparse(M, solver.ndofs, solver.ndofs)
|
|
K = sparse(K, solver.ndofs, solver.ndofs)
|
|
if nnz(K) == 0
|
|
warn("Field assembly seems to be empty. Check that elements are pushed to problem and formulation is correct.")
|
|
end
|
|
Kg = sparse(Kg, solver.ndofs, solver.ndofs)
|
|
f = sparse(f, solver.ndofs, 1)
|
|
fg = sparse(fg, solver.ndofs, 1)
|
|
|
|
# run any posthook for assembly if defined
|
|
args = Tuple{Solver, SparseMatrixCSC, SparseMatrixCSC, SparseMatrixCSC, SparseMatrixCSC}
|
|
if method_exists(field_assembly_posthook!, args)
|
|
field_assembly_posthook!(solver, K, Kg, fg, fg)
|
|
end
|
|
|
|
return M, K, Kg, f, fg
|
|
end
|
|
|
|
""" Posthook for boundary assembly. By default, do nothing. """
|
|
function boundary_assembly_posthook!
|
|
end
|
|
|
|
""" Return one combined boundary assembly for a set of boundary problems.
|
|
|
|
Returns
|
|
-------
|
|
C1, C2, D, g :: SparseMatrixCSC
|
|
|
|
Notes
|
|
-----
|
|
When some dof is constrained by multiple boundary problems an algorithm is
|
|
launched what tries to do it's best to solve issue. It's far from perfect
|
|
but is able to handle some basic situations occurring in corner nodes and
|
|
crosspoints.
|
|
|
|
"""
|
|
function get_boundary_assembly(solver::Solver)
|
|
ndofs = solver.ndofs
|
|
@assert ndofs != 0
|
|
K = spzeros(ndofs, ndofs)
|
|
C1 = spzeros(ndofs, ndofs)
|
|
C2 = spzeros(ndofs, ndofs)
|
|
D = spzeros(ndofs, ndofs)
|
|
f = spzeros(ndofs, 1)
|
|
g = spzeros(ndofs, 1)
|
|
for problem in get_boundary_problems(solver)
|
|
assembly = problem.assembly
|
|
K_ = sparse(assembly.K, ndofs, ndofs)
|
|
C1_ = sparse(assembly.C1, ndofs, ndofs)
|
|
C2_ = sparse(assembly.C2, ndofs, ndofs)
|
|
D_ = sparse(assembly.D, ndofs, ndofs)
|
|
f_ = sparse(assembly.f, ndofs, 1)
|
|
g_ = sparse(assembly.g, ndofs, 1)
|
|
# check for overconstraint situation and handle it if possible
|
|
already_constrained = get_nonzero_rows(C2)
|
|
new_constraints = get_nonzero_rows(C2_)
|
|
overconstrained_dofs = intersect(already_constrained, new_constraints)
|
|
if length(overconstrained_dofs) != 0
|
|
overconstrained_dofs = sort(overconstrained_dofs)
|
|
overconstrained_nodes = find_nodes_by_dofs(problem, overconstrained_dofs)
|
|
handle_overconstraint_error!(problem, overconstrained_nodes,
|
|
overconstrained_dofs, C1, C1_, C2, C2_, D, D_, g, g_)
|
|
end
|
|
K += K_
|
|
C1 += C1_
|
|
C2 += C2_
|
|
D += D_
|
|
f += f_
|
|
g += g_
|
|
end
|
|
return K, C1, C2, D, f, g
|
|
end
|
|
|
|
function resize!(A::SparseMatrixCSC, m::Int64, n::Int64)
|
|
(n == A.n) && (m == A.m) && return
|
|
@assert n >= A.n
|
|
@assert m >= A.m
|
|
append!(A.colptr, A.colptr[end]*ones(Int, m-A.m))
|
|
A.n = n
|
|
A.m = m
|
|
end
|
|
|
|
"""
|
|
Given C and g, construct new basis such that v = P*u + g
|
|
|
|
Parameters
|
|
----------
|
|
S set of linearly independent dofs.
|
|
"""
|
|
function create_projection(C::SparseMatrixCSC, g; S=nothing, tol=1.0e-12)
|
|
n, m = size(C)
|
|
@assert n == m
|
|
if S == nothing
|
|
S = get_nonzero_rows(C)
|
|
end
|
|
# FIXME: this creates dense matrices
|
|
# efficiency / memory usage is a question
|
|
M = get_nonzero_columns(C)
|
|
F = qrfact(C[S,:])
|
|
P = spzeros(n,m)
|
|
P[:,M] = sparse(F \ full(C[S,M]))
|
|
h = sparse(F \ full(g[S]))
|
|
resize!(P, n, m)
|
|
resize!(h, n, 1)
|
|
P = speye(n) - P
|
|
droptol!(P, tol)
|
|
return P, h
|
|
end
|
|
|
|
""" Assume C is invertible. """
|
|
function create_projection(C, g, ::Type{Val{:invertible}})
|
|
nz1, nz2 = get_nonzeros(C)
|
|
P = spzeros(size(C)...)
|
|
for j=1:size(C,1)
|
|
j in nz1 && continue
|
|
P[j,j] = 1.0
|
|
end
|
|
v = lufact(C[nz1,nz2]) \ full(g[nz1])
|
|
return P, v
|
|
end
|
|
|
|
|
|
|
|
"""
|
|
Solve linear system using LDLt factorization (SuiteSparse). This version
|
|
requires that final system is symmetric and positive definite, so boundary
|
|
conditions are first eliminated before solution.
|
|
"""
|
|
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{1}}; debug=false)
|
|
|
|
nnz(D) == 0 || return false
|
|
C1 == C2 || return false
|
|
|
|
A = get_nonzero_rows(K)
|
|
B = get_nonzero_rows(C2)
|
|
B2 = get_nonzero_columns(C2)
|
|
B == B2 || return false
|
|
I = setdiff(A, B)
|
|
|
|
if debug
|
|
info("# A = $(length(A))")
|
|
info("# B = $(length(B))")
|
|
info("# I = $(length(I))")
|
|
end
|
|
|
|
if length(B) == 0
|
|
warn("No rows in C2, forget to set Dirichlet boundary conditions to model?")
|
|
else
|
|
# solver boundary dofs (usually a trivial solution Iu = g
|
|
try
|
|
u[B] = lufact(C2[B,B2]) \ full(g[B])
|
|
catch
|
|
info("solver #1 failed to solve boundary dofs (you should not see this message).")
|
|
info("# A = $(length(A))")
|
|
info("# B = $(length(B))")
|
|
info("# B2 = $(length(B2))")
|
|
info("# I = $(length(I))")
|
|
info("B = $B")
|
|
info("B2 = $B2")
|
|
rethrow()
|
|
end
|
|
end
|
|
|
|
# solve interior domain using LDLt factorization
|
|
F = ldltfact(K[I,I])
|
|
u[I] = F \ (f[I] - K[I,B]*u[B])
|
|
|
|
# solve lagrange multipliers
|
|
la[B] = lufact(C1[B2,B]) \ full(f[B] - K[B,I]*u[I] - K[B,B]*u[B])
|
|
|
|
return true
|
|
end
|
|
|
|
"""
|
|
Solve linear system using LU factorization (UMFPACK). This version solves
|
|
directly the saddle point problem without elimination of boundary conditions.
|
|
"""
|
|
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{2}})
|
|
# construct global system Ax = b and solve using lufact (UMFPACK)
|
|
A = [K C1'; C2 D]
|
|
b = [f; g]
|
|
nz1 = get_nonzero_rows(A)
|
|
nz2 = get_nonzero_columns(A)
|
|
dim = size(A, 1)
|
|
x = zeros(dim)
|
|
x[nz1] = lufact(A[nz1,nz2]) \ full(b[nz1])
|
|
u[:] = x[1:solver.ndofs]
|
|
la[:] = x[solver.ndofs+1:end]
|
|
return true
|
|
end
|
|
|
|
""" Default linear system solver for solver. """
|
|
function solve!(solver::Solver; empty_assemblies_before_solution=true,
|
|
show_info=true, symmetric=true, optimize=false, fill_D_diagonal=false)
|
|
|
|
if show_info
|
|
info("Solving problems ...")
|
|
end
|
|
t0 = Base.time()
|
|
|
|
# assemble field & boundary problems
|
|
# TODO: return same kind of set for both assembly types
|
|
# M1, K1, Kg1, f1, fg1, C11, C21, D1, g1 = get_field_assembly(solver)
|
|
# M2, K2, Kg2, f2, fg2, C12, C22, D2, g2 = get_boundary_assembly(solver)
|
|
|
|
M, K, Kg, f, fg = get_field_assembly(solver)
|
|
Kb, C1, C2, D, fb, g = get_boundary_assembly(solver)
|
|
K = K + Kg + Kb
|
|
f = f + fg + fb
|
|
|
|
if symmetric
|
|
K = 1/2*(K + K')
|
|
M = 1/2*(M + M')
|
|
end
|
|
|
|
if fill_D_diagonal
|
|
nz = ones(solver.ndofs)
|
|
nz[get_nonzero_rows(C2)] = 0.0
|
|
nz[get_nonzero_rows(D)] = 0.0
|
|
D += spdiagm(nz)
|
|
end
|
|
|
|
# free up some memory before solution by either emptying field assemblies
|
|
# or combining values with same indices in sparse COO matrices. Small
|
|
# boundary problems are untouched.
|
|
for problem in get_field_problems(solver)
|
|
if empty_assemblies_before_solution
|
|
empty!(problem.assembly)
|
|
elseif optimize
|
|
optimize!(problem.assembly)
|
|
end
|
|
gc()
|
|
end
|
|
|
|
ndofs = solver.ndofs
|
|
u = zeros(ndofs)
|
|
la = zeros(ndofs)
|
|
status = false
|
|
i = 0
|
|
for i in [1, 2]
|
|
status = solve!(solver, K, C1, C2, D, f, g, u, la, Val{i})
|
|
status && break
|
|
end
|
|
status || error("Failed to solve linear system!")
|
|
t1 = round(Base.time()-t0, 2)
|
|
norms = (norm(u), norm(la))
|
|
push!(solver.norms, norms)
|
|
|
|
solver.u = u
|
|
solver.la = la
|
|
|
|
if show_info
|
|
info("Solved problems in $t1 seconds using solver $i.")
|
|
info("Solution norms = $norms.")
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
""" Default assembler for solver. """
|
|
function assemble!(solver::Solver; show_info=true, timing=true, with_mass_matrix=false)
|
|
show_info && info("Assembling problems ...")
|
|
|
|
function do_assemble(problem)
|
|
t00 = Base.time()
|
|
empty!(problem.assembly)
|
|
assemble!(problem, solver.time)
|
|
if with_mass_matrix && is_field_problem(problem)
|
|
assemble!(problem, solver.time, Val{:mass_matrix})
|
|
end
|
|
t11 = Base.time()
|
|
return t11-t00
|
|
end
|
|
|
|
t0 = Base.time()
|
|
assembly_times = map(do_assemble, solver.problems)
|
|
nproblems = length(assembly_times)
|
|
|
|
ndofs = 0
|
|
for problem in solver.problems
|
|
Ks = size(problem.assembly.K, 2)
|
|
Cs = size(problem.assembly.C1, 2)
|
|
ndofs = max(ndofs, Ks, Cs)
|
|
end
|
|
|
|
solver.ndofs = ndofs
|
|
t1 = round(Base.time()-t0, 2)
|
|
show_info && info("Assembled $nproblems problems in $t1 seconds. ndofs = $ndofs.")
|
|
if timing
|
|
info("Assembly times:")
|
|
for (i, problem) in enumerate(solver.problems)
|
|
pn = problem.name
|
|
pt = round(assembly_times[i], 2)
|
|
info("$i $pn $pt")
|
|
end
|
|
end
|
|
end
|
|
|
|
function get_unknown_fields(solver::Solver)
|
|
fields = Dict()
|
|
for problem in get_field_problems(solver)
|
|
field_name = get_unknown_field_name(problem)
|
|
field_dim = get_unknown_field_dimension(problem)
|
|
fields[field_name] = field_dim
|
|
end
|
|
return fields
|
|
end
|
|
|
|
function get_unknown_field_name(solver::Solver)
|
|
fields = get_unknown_fields(solver)
|
|
return join(sort(collect(keys(fields))), ", ")
|
|
end
|
|
|
|
function get_unknown_field_dimension(solver::Solver)
|
|
fields = get_unknown_fields(solver)
|
|
return sum(values(fields))
|
|
end
|
|
|
|
""" Default initializer for solver. """
|
|
function initialize!(solver::Solver; show_info=true)
|
|
if solver.initialized
|
|
show_info && info("initialize!(): solver already initialized")
|
|
return
|
|
end
|
|
show_info && info("Initializing solver ...")
|
|
problems = get_problems(solver)
|
|
length(problems) != 0 || error("Empty solver, add problems to solver using push!")
|
|
t0 = Base.time()
|
|
field_problems = get_field_problems(solver)
|
|
length(field_problems) != 0 || warn("No field problem found from solver, add some..?")
|
|
field_name = get_unknown_field_name(solver)
|
|
field_dim = get_unknown_field_dimension(solver)
|
|
info("initialize!(): looks we are solving $field_name, $field_dim dofs/node")
|
|
nodes = Set{Int64}()
|
|
for problem in problems
|
|
initialize!(problem, solver.time)
|
|
for element in get_elements(problem)
|
|
conn = get_connectivity(element)
|
|
push!(nodes, conn...)
|
|
end
|
|
end
|
|
nnodes = length(nodes)
|
|
info("Total number of nodes in problems: $nnodes")
|
|
maxdof = maximum(nnodes)*field_dim
|
|
info("# of max dof (=size of solution vector) is $maxdof")
|
|
solver.u = zeros(maxdof)
|
|
solver.la = zeros(maxdof)
|
|
# TODO: this could be used to initialize elements too...
|
|
# TODO: cannot initialize to zero always, construct vector from elements.
|
|
for problem in problems
|
|
problem.assembly.u = zeros(maxdof)
|
|
problem.assembly.la = zeros(maxdof)
|
|
# initialize(problem, ....)
|
|
end
|
|
t1 = round(Base.time()-t0, 2)
|
|
show_info && info("Initialized solver in $t1 seconds.")
|
|
solver.initialized = true
|
|
end
|
|
|
|
function get_all_elements(solver::Solver)
|
|
elements = [get_elements(problem) for problem in get_problems(solver)]
|
|
return [elements...;]
|
|
end
|
|
|
|
function get_element_type{E}(element::Element{E})
|
|
return E
|
|
end
|
|
|
|
function get_element_id{E}(element::Element{E})
|
|
return element.id
|
|
end
|
|
|
|
function is_element_type{E}(element::Element{E}, element_type)
|
|
return is(E, element_type)
|
|
end
|
|
|
|
function filter_by_element_type(element_type, elements)
|
|
return filter(element -> is_element_type(element, element_type), elements)
|
|
end
|
|
|
|
function (solver::Solver)(field_name::AbstractString, time::Float64)
|
|
fields = []
|
|
for problem in get_problems(solver)
|
|
field = problem(field_name, time)
|
|
if length(field) == 0
|
|
warn("no field $field_name found for problem $(problem.name)")
|
|
else
|
|
push!(fields, field)
|
|
end
|
|
end
|
|
return merge(fields...)
|
|
end
|
|
|
|
function get_temporal_collection(xdmf::Xdmf)
|
|
domain = find_element(xdmf.xml, "Domain")
|
|
grid = nothing
|
|
if domain == nothing
|
|
info("Xdmf: creating new temporal collection")
|
|
domain = new_child(xdmf.xml, "Domain")
|
|
grid = new_child(domain, "Grid")
|
|
set_attribute(grid, "CollectionType", "Temporal")
|
|
set_attribute(grid, "GridType", "Collection")
|
|
end
|
|
grid = find_element(domain, "Grid")
|
|
return grid
|
|
end
|
|
|
|
""" Default update for solver. """
|
|
function update!{S}(solver::Solver{S}; show_info=true)
|
|
u = solver.u
|
|
la = solver.la
|
|
|
|
show_info && info("Updating problems ...")
|
|
t0 = Base.time()
|
|
|
|
for problem in solver.problems
|
|
assembly = get_assembly(problem)
|
|
elements = get_elements(problem)
|
|
# update solution, first for assembly (u,la) ...
|
|
update!(problem, assembly, u, la)
|
|
# .. and then from assembly (u,la) to elements
|
|
update!(problem, assembly, elements, solver.time)
|
|
end
|
|
|
|
# if io is attached to solver, update hdf / xml also
|
|
if !isnull(solver.xdmf)
|
|
update_xdmf!(solver)
|
|
end
|
|
|
|
t1 = round(Base.time()-t0, 2)
|
|
show_info && info("Updated problems in $t1 seconds.")
|
|
end
|
|
|
|
function update_xdmf!{S}(solver::Solver{S}; show_info=true)
|
|
xdmf = get(solver.xdmf)
|
|
temporal_collection = get_temporal_collection(xdmf)
|
|
|
|
# 1. save geometry
|
|
X_ = solver("geometry", solver.time)
|
|
node_ids = sort(collect(keys(X_)))
|
|
X = hcat([X_[nid] for nid in node_ids]...)
|
|
ndim, nnodes = size(X)
|
|
geom_type = (ndim == 2 ? "XY" : "XYZ")
|
|
data_node_ids = new_dataitem(xdmf, "/Node IDs", node_ids)
|
|
data_geometry = new_dataitem(xdmf, "/Geometry", X)
|
|
geometry = new_element("Geometry", Dict("Type" => geom_type))
|
|
add_child(geometry, data_geometry)
|
|
|
|
# 2. save topology
|
|
nid_mapping = Dict(j=>i for (i, j) in enumerate(node_ids))
|
|
all_elements = get_all_elements(solver)
|
|
nelements = length(all_elements)
|
|
debug("Saving topology: $nelements elements total.")
|
|
element_types = unique(map(get_element_type, all_elements))
|
|
|
|
xdmf_element_mapping = Dict(
|
|
"Poi1" => "Polyvertex",
|
|
"Seg2" => "Polyline",
|
|
"Tri3" => "Triangle",
|
|
"Quad4" => "Quadrilateral",
|
|
"Tet4" => "Tetrahedron",
|
|
"Pyramid5" => "Pyramid",
|
|
"Wedge6" => "Wedge",
|
|
"Hex8" => "Hexahedron",
|
|
"Seg3" => "Edge_3",
|
|
"Tri6" => "Tri_6",
|
|
"Quad8" => "Quad_8",
|
|
"Tet10" => "Tet_10",
|
|
"Pyramid13" => "Pyramid_13",
|
|
"Wedge15" => "Wedge_15",
|
|
"Hex20" => "Hex_20")
|
|
|
|
topology = []
|
|
for element_type in element_types
|
|
elements = filter_by_element_type(element_type, all_elements)
|
|
nelements = length(elements)
|
|
info("Xdmf save: $nelements elements of type $element_type")
|
|
sort!(elements, by=get_element_id)
|
|
element_ids = map(get_element_id, elements)
|
|
element_conn = map(element -> [nid_mapping[j]-1 for j in get_connectivity(element)], elements)
|
|
element_conn = hcat(element_conn...)
|
|
element_code = split(string(element_type), ".")[end]
|
|
dataitem = new_dataitem(xdmf, "/Topology/$element_code/Element IDs", element_ids)
|
|
dataitem = new_dataitem(xdmf, "/Topology/$element_code/Connectivity", element_conn)
|
|
topology_ = new_element("Topology")
|
|
set_attribute(topology_, "TopologyType", xdmf_element_mapping[element_code])
|
|
set_attribute(topology_, "NumberOfElements", length(elements))
|
|
add_child(topology_, dataitem)
|
|
push!(topology, topology_)
|
|
end
|
|
|
|
# 3. save solved field
|
|
frame = new_element("Grid")
|
|
new_child(frame, "Time", Dict("Value" => solver.time))
|
|
add_child(frame, geometry)
|
|
for topo in topology
|
|
add_child(frame, topo)
|
|
end
|
|
|
|
unknown_field_name = get_unknown_field_name(solver)
|
|
U_ = solver(unknown_field_name, solver.time)
|
|
node_ids2 = sort(collect(keys(U_)))
|
|
@assert node_ids == node_ids2
|
|
|
|
ndim = length(U_[first(node_ids)])
|
|
field_type = ndim == 1 ? "Scalar" : "Vector"
|
|
field_center = "Node"
|
|
if ndim == 2
|
|
for nid in node_ids
|
|
U_[nid] = [U_[nid]; 0.0]
|
|
end
|
|
ndim = 3
|
|
end
|
|
U = zeros(X)
|
|
for nid in node_ids
|
|
loc = nid_mapping[nid]
|
|
U[:,loc] = U_[nid]
|
|
end
|
|
unknown_field_name = ucfirst(unknown_field_name)
|
|
time = solver.time
|
|
path = ""
|
|
if S == Nonlinear
|
|
iteration = solver.properties.iteration
|
|
path = "/Results/Time $time/Iteration $iteration/Nodal Fields/$unknown_field_name"
|
|
elseif S == Linear
|
|
path = "/Results/Time $time/Nodal Fields/$unknown_field_name"
|
|
end
|
|
attribute = new_child(frame, "Attribute")
|
|
set_attribute(attribute, "Name", unknown_field_name)
|
|
set_attribute(attribute, "Center", field_center)
|
|
set_attribute(attribute, "AttributeType", field_type)
|
|
add_child(attribute, new_dataitem(xdmf, path, U))
|
|
add_child(frame, attribute)
|
|
if (S == Linear) || ((S == Nonlinear) && has_converged(solver))
|
|
add_child(temporal_collection, frame)
|
|
end
|
|
save!(xdmf)
|
|
end
|
|
|
|
|
|
### Nonlinear quasistatic solver
|
|
|
|
type Nonlinear <: AbstractSolver
|
|
iteration :: Int # iteration counter
|
|
min_iterations :: Int64 # minimum number of iterations
|
|
max_iterations :: Int64 # maximum number of iterations
|
|
convergence_tolerance :: Float64
|
|
error_if_no_convergence :: Bool # throw error if no convergence
|
|
end
|
|
|
|
function Nonlinear()
|
|
solver = Nonlinear(0, 1, 10, 5.0e-5, true)
|
|
return solver
|
|
end
|
|
|
|
""" Check convergence of problems.
|
|
|
|
Notes
|
|
-----
|
|
Default convergence criteria is obtained by checking each sub-problem convergence.
|
|
"""
|
|
function has_converged(solver::Solver{Nonlinear}; show_info=false,
|
|
check_convergence_for_boundary_problems=false)
|
|
properties = solver.properties
|
|
converged = true
|
|
eps = properties.convergence_tolerance
|
|
for problem in solver.problems
|
|
has_converged = true
|
|
if is_field_problem(problem)
|
|
has_converged = problem.assembly.u_norm_change < eps
|
|
if isapprox(norm(problem.assembly.u), 0.0)
|
|
# trivial solution
|
|
has_converged = true
|
|
end
|
|
show_info && info("Details for problem $(problem.name)")
|
|
show_info && info("Norm: $(norm(problem.assembly.u))")
|
|
show_info && info("Norm change: $(problem.assembly.u_norm_change)")
|
|
show_info && info("Has converged? $(has_converged)")
|
|
end
|
|
if is_boundary_problem(problem) && check_convergence_for_boundary_problems
|
|
has_converged = problem.assembly.la_norm_change/norm(problem.assembly.la) < eps
|
|
show_info && info("Details for problem $(problem.name)")
|
|
show_info && info("Norm: $(norm(problem.assembly.la))")
|
|
show_info && info("Norm change: $(problem.assembly.la_norm_change)")
|
|
show_info && info("Has converged? $(has_converged)")
|
|
end
|
|
converged &= has_converged
|
|
end
|
|
return converged
|
|
end
|
|
|
|
type NonlinearConvergenceError <: Exception
|
|
solver :: Solver
|
|
end
|
|
|
|
function Base.showerror(io::IO, exception::NonlinearConvergenceError)
|
|
max_iters = exception.solver.properties.max_iterations
|
|
print(io, "nonlinear iteration did not converge in $max_iters iterations!")
|
|
end
|
|
|
|
""" Default solver for quasistatic nonlinear problems. """
|
|
function (solver::Solver{Nonlinear})(; show_info=true)
|
|
|
|
properties = solver.properties
|
|
|
|
# 1. initialize each problem so that we can start nonlinear iterations
|
|
initialize!(solver)
|
|
|
|
# 2. start non-linear iterations
|
|
for properties.iteration=1:properties.max_iterations
|
|
show_info && info(repeat("-", 80))
|
|
show_info && info("Starting nonlinear iteration #$(properties.iteration)")
|
|
show_info && info("Increment time t=$(round(solver.time, 3))")
|
|
show_info && info(repeat("-", 80))
|
|
|
|
# 2.1 update linearized assemblies
|
|
assemble!(solver)
|
|
# 2.2 call solver for linearized system
|
|
solve!(solver)
|
|
# 2.3 update solution back to elements
|
|
update!(solver)
|
|
|
|
# 2.4 check convergence
|
|
if has_converged(solver)
|
|
info("Converged in $(properties.iteration) iterations.")
|
|
properties.iteration >= properties.min_iterations && return true
|
|
info("Convergence criteria met, but iteration < min_iterations, continuing...")
|
|
end
|
|
end
|
|
|
|
# 3. did not converge
|
|
properties.error_if_no_convergence && throw(NonlinearConvergenceError(solver))
|
|
end
|
|
|
|
""" Convenience function to call nonlinear solver. """
|
|
function NonlinearSolver(problems...)
|
|
solver = Solver(Nonlinear, "default nonlinear solver")
|
|
if length(problems) != 0
|
|
push!(solver, problems...)
|
|
end
|
|
return solver
|
|
end
|
|
function NonlinearSolver(name::AbstractString, problems::Problem...)
|
|
solver = NonlinearSolver(problems...)
|
|
solver.name = name
|
|
return solver
|
|
end
|
|
|
|
|
|
### Linear quasistatic solver
|
|
|
|
""" Quasistatic solver for linear problems.
|
|
|
|
Notes
|
|
-----
|
|
Main differences in this solver, compared to nonlinear solver are:
|
|
1. system of problems is assumed to converge in one step
|
|
2. reassembly of problem is done only if it's manually requested using empty!(problem.assembly)
|
|
|
|
"""
|
|
type Linear <: AbstractSolver
|
|
end
|
|
|
|
function assemble!(solver::Solver{Linear}; show_info=true)
|
|
show_info && info("Assembling problems ...")
|
|
tic()
|
|
nproblems = 0
|
|
ndofs = 0
|
|
for problem in get_problems(solver)
|
|
if isempty(problem.assembly)
|
|
assemble!(problem, solver.time)
|
|
nproblems += 1
|
|
else
|
|
show_info && info("$(problem.name) already assembled, skipping.")
|
|
end
|
|
ndofs = max(ndofs, size(problem.assembly.K, 2))
|
|
end
|
|
solver.ndofs = ndofs
|
|
t1 = round(toq(), 2)
|
|
show_info && info("Assembled $nproblems problems in $t1 seconds. ndofs = $ndofs.")
|
|
end
|
|
|
|
function (solver::Solver{Linear})(; show_info=true)
|
|
t0 = Base.time()
|
|
show_info && info(repeat("-", 80))
|
|
show_info && info("Starting linear solver")
|
|
show_info && info("Increment time t=$(round(solver.time, 3))")
|
|
show_info && info(repeat("-", 80))
|
|
initialize!(solver)
|
|
assemble!(solver)
|
|
solve!(solver)
|
|
update!(solver)
|
|
t1 = round(Base.time()-t0, 2)
|
|
show_info && info("Linear solver ready in $t1 seconds.")
|
|
end
|
|
|
|
""" Convenience function to call linear solver. """
|
|
function LinearSolver(problems::Problem...)
|
|
solver = Solver(Linear, "default linear solver")
|
|
if length(problems) != 0
|
|
push!(solver, problems...)
|
|
end
|
|
return solver
|
|
end
|
|
function LinearSolver(name::AbstractString, problems::Problem...)
|
|
solver = LinearSolver(problems...)
|
|
solver.name = name
|
|
return solver
|
|
end
|
|
|
|
### End of linear quasistatic solver
|
|
|
|
### Postprocessor
|
|
|
|
type Postprocessor <: AbstractSolver
|
|
assembly :: Assembly
|
|
F :: Union{Factorization, Void}
|
|
end
|
|
|
|
function Postprocessor()
|
|
Postprocessor(Assembly(), nothing)
|
|
end
|
|
|
|
function assemble!(solver::Solver{Postprocessor}; show_info=true)
|
|
show_info && info("Assembling problems ...")
|
|
tic()
|
|
nproblems = 0
|
|
ndofs = 0
|
|
assembly = solver.properties.assembly
|
|
empty!(assembly)
|
|
for problem in get_problems(solver)
|
|
for element in get_elements(problem)
|
|
postprocess!(assembly, problem, element, solver.time)
|
|
end
|
|
nproblems += 1
|
|
ndofs = max(ndofs, size(problem.assembly.K, 2))
|
|
end
|
|
solver.ndofs = ndofs
|
|
t1 = round(toq(), 2)
|
|
show_info && info("Assembled $nproblems problems in $t1 seconds. ndofs = $ndofs.")
|
|
end
|
|
|
|
function (solver::Solver{Postprocessor})(; show_info=true)
|
|
t0 = Base.time()
|
|
show_info && info(repeat("-", 80))
|
|
show_info && info("Starting postprocessor")
|
|
show_info && info("Increment time t=$(round(solver.time, 3))")
|
|
show_info && info(repeat("-", 80))
|
|
initialize!(solver)
|
|
assemble!(solver)
|
|
assembly = solver.properties.assembly
|
|
M = sparse(assembly.M)
|
|
f = sparse(assembly.f)
|
|
F = cholfact(M)
|
|
q = F \ f
|
|
t1 = round(Base.time()-t0, 2)
|
|
show_info && info("Postprocess of results ready in $t1 seconds.")
|
|
return q
|
|
end
|
|
|
|
""" Convenience function to call postprocessor. """
|
|
function Postprocessor(problems::Problem...)
|
|
solver = Solver(Postprocessor, "default postprocessor")
|
|
if length(problems) != 0
|
|
push!(solver, problems...)
|
|
end
|
|
return solver
|
|
end
|
|
|
|
function Postprocessor(name::AbstractString, problems::Problem...)
|
|
solver = Postprocessor(problems...)
|
|
solver.name = name
|
|
return solver
|
|
end
|