Files
JuliaFEM.jl/src/solvers.jl
T

872 lines
27 KiB
Julia
Raw Normal View History

2015-10-09 23:45:28 +03:00
# 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}
2016-08-01 01:15:41 +03:00
name :: AbstractString # some descriptive name for problem
time :: Float64 # current time
problems :: Vector{Problem}
2016-08-01 01:15:41 +03:00
norms :: Vector{Tuple} # solution norms for convergence studies
ndofs :: Int # number of degrees of freedom in problem
2016-08-02 02:51:32 +03:00
xdmf :: Nullable{Xdmf} # input/output handle
2016-08-04 13:15:19 +03:00
initialized :: Bool
u :: Vector{Float64}
la :: Vector{Float64}
properties :: S
end
2016-02-01 09:13:07 +02:00
2016-06-27 16:11:33 +03:00
function Solver{S<:AbstractSolver}(::Type{S}, name="solver", properties...)
variant = S(properties...)
2016-08-04 13:15:19 +03:00
solver = Solver{S}(name, 0.0, [], [], 0, nothing, false, [], [], variant)
return solver
2016-02-01 09:13:07 +02:00
end
function Solver{S<:AbstractSolver}(::Type{S}, problems::Problem...)
2016-08-04 13:15:19 +03:00
solver = Solver(S, "$(S)Solver")
push!(solver.problems, problems...)
return solver
end
2016-06-25 04:12:53 +03:00
function get_problems(solver::Solver)
return solver.problems
end
2016-02-03 06:49:42 +02:00
function push!(solver::Solver, problem)
2016-02-01 09:13:07 +02:00
push!(solver.problems, problem)
end
function getindex(solver::Solver, problem_name)
2016-06-25 04:12:53 +03:00
for problem in get_problems(solver)
if problem.name == problem_name
return problem
end
end
throw(KeyError(problem_name))
end
2016-02-01 09:13:07 +02:00
# one-liner helpers to identify problem types
2016-06-27 16:11:33 +03:00
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))
2016-02-01 09:13:07 +02:00
2016-06-27 16:11:33 +03:00
"""
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))
2016-02-01 09:13:07 +02:00
end
2016-06-27 16:11:33 +03:00
"""
2016-02-05 14:03:27 +02:00
function field_assembly_posthook!
end
2016-02-05 12:27:36 +02:00
2016-02-01 09:13:07 +02:00
"""Return one combined field assembly for a set of field problems.
Parameters
----------
solver :: Solver
Returns
-------
2016-06-27 16:11:33 +03:00
M, K, Kg, f, fg :: SparseMatrixCSC
2016-02-01 09:13:07 +02:00
Notes
-----
If several field problems exists, they are simply summed together, so
problems must have unique node ids.
"""
2016-06-27 16:11:33 +03:00
function get_field_assembly(solver::Solver; show_info=true)
2016-02-05 12:27:36 +02:00
problems = get_field_problems(solver)
2016-06-27 16:11:33 +03:00
M = SparseMatrixCOO()
2016-02-01 09:13:07 +02:00
K = SparseMatrixCOO()
Kg = SparseMatrixCOO()
2016-02-01 09:13:07 +02:00
f = SparseMatrixCOO()
2016-06-27 16:11:33 +03:00
fg = SparseMatrixCOO()
2016-02-01 09:13:07 +02:00
for problem in problems
2016-06-27 16:11:33 +03:00
append!(M, problem.assembly.M)
2016-06-09 01:27:56 +03:00
append!(K, problem.assembly.K)
append!(Kg, problem.assembly.Kg)
2016-06-09 01:27:56 +03:00
append!(f, problem.assembly.f)
2016-06-27 16:11:33 +03:00
append!(fg, problem.assembly.fg)
end
2016-06-27 16:11:33 +03:00
if solver.ndofs == 0
solver.ndofs = size(K, 1)
2016-06-27 16:11:33 +03:00
show_info && info("automatically determined problem dimension, ndofs = $(solver.ndofs)")
end
2016-06-27 16:11:33 +03:00
M = sparse(M, solver.ndofs, solver.ndofs)
K = sparse(K, solver.ndofs, solver.ndofs)
2016-07-03 21:16:03 +03:00
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)
2016-02-05 12:27:36 +02:00
f = sparse(f, solver.ndofs, 1)
2016-06-27 16:11:33 +03:00
fg = sparse(fg, solver.ndofs, 1)
2016-02-05 14:03:27 +02:00
# run any posthook for assembly if defined
2016-06-27 16:11:33 +03:00
args = Tuple{Solver, SparseMatrixCSC, SparseMatrixCSC, SparseMatrixCSC, SparseMatrixCSC}
2016-02-05 14:03:27 +02:00
if method_exists(field_assembly_posthook!, args)
2016-06-27 16:11:33 +03:00
field_assembly_posthook!(solver, K, Kg, fg, fg)
2016-02-05 14:03:27 +02:00
end
2016-06-27 16:11:33 +03:00
return M, K, Kg, f, fg
2016-02-01 09:13:07 +02:00
end
""" Loop through boundary assemblies and check for possible overconstrain situations. """
function check_for_overconstrained_dofs(solver::Solver)
overdetermined = false
constrained_dofs = Set{Int}()
boundary_problems = get_boundary_problems(solver)
for problem in boundary_problems
new_constraints = Set(problem.assembly.C2.I)
new_constraints = setdiff(new_constraints, problem.assembly.removed_dofs)
overconstrained_dofs = intersect(constrained_dofs, new_constraints)
if length(overconstrained_dofs) != 0
warn("problem is overconstrained, finding overconstrained dofs... ")
overdetermined = true
for dof in overconstrained_dofs
for problem_ in boundary_problems
new_constraints_ = Set(problem_.assembly.C2.I)
new_constraints_ = setdiff(new_constraints_, problem_.assembly.removed_dofs)
if dof in new_constraints_
warn("overconstrained dof $dof defined in problem $(problem_.name)")
end
end
warn("To solve overconstrained situation, remove dofs from problems so that it exists only in one.")
warn("To do this, use push! to add dofs to remove to problem.assembly.removed_dofs, e.g.")
warn("`push!(bc.assembly.removed_dofs, $dof`)")
end
end
constrained_dofs = union(constrained_dofs, new_constraints)
end
if overdetermined
error("problem is overconstrained, not continuing to solution.")
end
return true
2016-02-05 14:03:27 +02:00
end
2016-02-05 12:27:36 +02:00
2016-02-01 09:13:07 +02:00
""" Return one combined boundary assembly for a set of boundary problems.
Returns
-------
K, C1, C2, D, f, g :: SparseMatrixCSC
2016-02-01 09:13:07 +02:00
"""
function get_boundary_assembly(solver::Solver)
check_for_overconstrained_dofs(solver)
2016-02-05 12:27:36 +02:00
ndofs = solver.ndofs
@assert ndofs != 0
2016-02-24 01:20:39 +02:00
K = spzeros(ndofs, ndofs)
2016-02-05 12:27:36 +02:00
C1 = spzeros(ndofs, ndofs)
C2 = spzeros(ndofs, ndofs)
D = spzeros(ndofs, ndofs)
2016-02-24 01:20:39 +02:00
f = spzeros(ndofs, 1)
2016-02-05 12:27:36 +02:00
g = spzeros(ndofs, 1)
for problem in get_boundary_problems(solver)
assembly = problem.assembly
2016-02-24 01:20:39 +02:00
K_ = sparse(assembly.K, ndofs, ndofs)
2016-02-05 12:27:36 +02:00
C1_ = sparse(assembly.C1, ndofs, ndofs)
C2_ = sparse(assembly.C2, ndofs, ndofs)
D_ = sparse(assembly.D, ndofs, ndofs)
2016-02-24 01:20:39 +02:00
f_ = sparse(assembly.f, ndofs, 1)
2016-02-05 12:27:36 +02:00
g_ = sparse(assembly.g, ndofs, 1)
for dof in assembly.removed_dofs
info("$(problem.name): removing dof $dof from assembly")
C1_[:,dof] = 0.0
C2_[dof,:] = 0.0
end
2016-02-05 12:27:36 +02:00
already_constrained = get_nonzero_rows(C2)
new_constraints = get_nonzero_rows(C2_)
overconstrained_dofs = intersect(already_constrained, new_constraints)
if length(overconstrained_dofs) != 0
warn("overconstrained dofs $overconstrained_dofs")
warn("already constrained = $already_constrained")
warn("new constraints = $new_constraints")
2016-02-05 12:27:36 +02:00
overconstrained_dofs = sort(overconstrained_dofs)
overconstrained_nodes = find_nodes_by_dofs(problem, overconstrained_dofs)
warn("in overconstrained nodes $overconstrained_nodes")
error("overconstrained dofs, not solving problem.")
2016-02-05 12:27:36 +02:00
end
2016-02-24 01:20:39 +02:00
K += K_
2016-02-05 12:27:36 +02:00
C1 += C1_
C2 += C2_
D += D_
2016-02-24 01:20:39 +02:00
f += f_
2016-02-05 12:27:36 +02:00
g += g_
2016-02-01 09:13:07 +02:00
end
2016-02-24 01:20:39 +02:00
return K, C1, C2, D, f, g
2016-02-01 09:13:07 +02:00
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.
"""
2016-08-04 13:15:19 +03:00
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{1}}; debug=false)
2016-08-04 13:15:19 +03:00
nnz(D) == 0 || return false
2016-11-28 12:20:54 +02:00
C1 == C2 || return false
A = get_nonzero_rows(K)
2016-11-28 12:20:54 +02:00
B = get_nonzero_rows(C2)
B2 = get_nonzero_columns(C2)
B == B2 || return false
I = setdiff(A, B)
2016-11-28 12:20:54 +02:00
if debug
info("# A = $(length(A))")
info("# B = $(length(B))")
info("# I = $(length(I))")
end
2016-02-05 12:27:36 +02:00
2016-11-28 12:20:54 +02:00
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
2016-08-04 13:15:19 +03:00
F = ldltfact(K[I,I])
2016-06-27 16:11:33 +03:00
u[I] = F \ (f[I] - K[I,B]*u[B])
2016-11-28 12:20:54 +02:00
# solve lagrange multipliers
la[B] = lufact(C1[B2,B]) \ full(f[B] - K[B,I]*u[I] - K[B,B]*u[B])
2016-08-04 13:15:19 +03:00
return true
end
"""
Solve linear system using LU factorization (UMFPACK). This version solves
directly the saddle point problem without elimination of boundary conditions.
"""
2016-08-04 13:15:19 +03:00
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{2}})
nz = ones(solver.ndofs)
nz[get_nonzero_rows(C2)] = 0.0
nz[get_nonzero_rows(D)] = 0.0
D += spdiagm(nz)
A = [K C1'; C2 D]
b = [f; g]
x = lufact(A) \ full(b)
2016-08-04 13:15:19 +03:00
u[:] = x[1:solver.ndofs]
la[:] = x[solver.ndofs+1:end]
return true
2016-02-01 09:13:07 +02:00
end
2016-06-27 16:11:33 +03:00
""" Default linear system solver for solver. """
2016-11-28 12:20:54 +02:00
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
2016-06-27 16:11:33 +03:00
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)
2016-06-09 01:27:56 +03:00
2016-06-27 16:11:33 +03:00
M, K, Kg, f, fg = get_field_assembly(solver)
2016-06-09 01:27:56 +03:00
Kb, C1, C2, D, fb, g = get_boundary_assembly(solver)
K = K + Kg + Kb
2016-06-27 16:11:33 +03:00
f = f + fg + fb
2016-11-28 12:20:54 +02:00
if symmetric
K = 1/2*(K + K')
M = 1/2*(M + M')
end
2016-08-04 13:15:19 +03:00
2016-11-28 12:20:54 +02:00
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)
2016-06-27 16:11:33 +03:00
if empty_assemblies_before_solution
empty!(problem.assembly)
2016-11-28 12:20:54 +02:00
elseif optimize
2016-06-27 16:11:33 +03:00
optimize!(problem.assembly)
end
gc()
end
2016-08-04 13:15:19 +03:00
ndofs = solver.ndofs
u = zeros(ndofs)
la = zeros(ndofs)
status = false
2016-06-27 16:11:33 +03:00
i = 0
for i in [1, 2]
2016-08-04 13:15:19 +03:00
status = solve!(solver, K, C1, C2, D, f, g, u, la, Val{i})
2016-07-03 05:01:18 +03:00
status && break
2016-06-09 01:27:56 +03:00
end
status || error("Failed to solve linear system!")
2016-06-27 16:11:33 +03:00
t1 = round(Base.time()-t0, 2)
norms = (norm(u), norm(la))
push!(solver.norms, norms)
2016-08-04 13:15:19 +03:00
solver.u = u
solver.la = la
2016-11-28 12:20:54 +02:00
if show_info
info("Solved problems in $t1 seconds using solver $i.")
info("Solution norms = $norms.")
end
2016-08-04 13:15:19 +03:00
return
2016-06-27 16:11:33 +03:00
end
""" Default assembler for solver. """
2016-10-13 00:59:38 +03:00
function assemble!(solver::Solver; show_info=true, timing=true, with_mass_matrix=false)
2016-06-27 16:11:33 +03:00
show_info && info("Assembling problems ...")
2016-10-13 00:59:38 +03:00
function do_assemble(problem)
t00 = Base.time()
2016-06-27 16:11:33 +03:00
empty!(problem.assembly)
assemble!(problem, solver.time)
2016-10-13 00:59:38 +03:00
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)
2016-06-27 16:11:33 +03:00
end
2016-10-13 00:59:38 +03:00
2016-06-27 16:11:33 +03:00
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
2016-10-13 00:59:38 +03:00
pt = round(assembly_times[i], 2)
info("$i $pn $pt")
end
end
2016-06-27 16:11:33 +03:00
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
2016-06-27 16:11:33 +03:00
""" Default initializer for solver. """
function initialize!(solver::Solver; show_info=true)
2016-08-04 13:15:19 +03:00
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!")
2016-06-27 16:11:33 +03:00
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
2016-06-27 16:11:33 +03:00
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")
2016-08-04 13:15:19 +03:00
solver.u = zeros(maxdof)
solver.la = zeros(maxdof)
# TODO: this could be used to initialize elements too...
2016-08-04 13:15:19 +03:00
# TODO: cannot initialize to zero always, construct vector from elements.
for problem in problems
2016-08-04 13:15:19 +03:00
problem.assembly.u = zeros(maxdof)
problem.assembly.la = zeros(maxdof)
# initialize(problem, ....)
2016-06-27 16:11:33 +03:00
end
t1 = round(Base.time()-t0, 2)
2016-08-04 13:15:19 +03:00
show_info && info("Initialized solver in $t1 seconds.")
solver.initialized = true
2016-06-27 16:11:33 +03:00
end
2016-08-01 01:15:41 +03:00
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
2016-11-13 13:24:08 +02:00
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
2016-08-01 01:15:41 +03:00
return merge(fields...)
end
2016-08-02 02:51:32 +03:00
function get_temporal_collection(xdmf::Xdmf)
domain = find_element(xdmf.xml, "Domain")
grid = nothing
if domain == nothing
2016-08-01 01:15:41 +03:00
info("Xdmf: creating new temporal collection")
2016-08-02 02:51:32 +03:00
domain = new_child(xdmf.xml, "Domain")
2016-08-01 01:15:41 +03:00
grid = new_child(domain, "Grid")
set_attribute(grid, "CollectionType", "Temporal")
set_attribute(grid, "GridType", "Collection")
end
2016-08-02 02:51:32 +03:00
grid = find_element(domain, "Grid")
2016-08-01 01:15:41 +03:00
return grid
end
2016-06-27 16:11:33 +03:00
""" Default update for solver. """
2016-08-04 13:15:19 +03:00
function update!{S}(solver::Solver{S}; show_info=true)
u = solver.u
la = solver.la
2016-06-27 16:11:33 +03:00
show_info && info("Updating problems ...")
t0 = Base.time()
2016-08-02 02:51:32 +03:00
2016-06-27 16:11:33 +03:00
for problem in solver.problems
2016-07-14 12:43:41 +03:00
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)
2016-06-27 16:11:33 +03:00
end
2016-08-01 01:15:41 +03:00
# if io is attached to solver, update hdf / xml also
2016-08-02 02:51:32 +03:00
if !isnull(solver.xdmf)
2016-08-04 13:15:19 +03:00
update_xdmf!(solver)
2016-08-01 01:15:41 +03:00
end
2016-06-27 16:11:33 +03:00
t1 = round(Base.time()-t0, 2)
show_info && info("Updated problems in $t1 seconds.")
end
2016-08-04 13:15:19 +03:00
function update_xdmf!{S}(solver::Solver{S}; show_info=true)
xdmf = get(solver.xdmf)
temporal_collection = get_temporal_collection(xdmf)
2016-11-28 12:20:54 +02:00
# 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))
2016-08-04 13:15:19 +03:00
all_elements = get_all_elements(solver)
nelements = length(all_elements)
2016-11-28 12:20:54 +02:00
debug("Saving topology: $nelements elements total.")
2016-08-04 13:15:19 +03:00
element_types = unique(map(get_element_type, all_elements))
xdmf_element_mapping = Dict(
2016-11-28 12:20:54 +02:00
"Poi1" => "Polyvertex",
2016-08-04 13:15:19 +03:00
"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")
2016-11-28 12:20:54 +02:00
topology = []
2016-08-04 13:15:19 +03:00
for element_type in element_types
elements = filter_by_element_type(element_type, all_elements)
2016-11-28 12:20:54 +02:00
nelements = length(elements)
info("Xdmf save: $nelements elements of type $element_type")
2016-08-04 13:15:19 +03:00
sort!(elements, by=get_element_id)
element_ids = map(get_element_id, elements)
2016-11-28 12:20:54 +02:00
element_conn = map(element -> [nid_mapping[j]-1 for j in get_connectivity(element)], elements)
element_conn = hcat(element_conn...)
2016-08-04 13:15:19 +03:00
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)
2016-11-28 12:20:54 +02:00
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_)
2016-08-04 13:15:19 +03:00
end
2016-11-28 12:20:54 +02:00
# 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
2016-08-04 13:15:19 +03:00
2016-11-28 12:20:54 +02:00
unknown_field_name = get_unknown_field_name(solver)
U_ = solver(unknown_field_name, solver.time)
node_ids2 = sort(collect(keys(U_)))
2016-08-04 13:15:19 +03:00
@assert node_ids == node_ids2
2016-11-28 12:20:54 +02:00
ndim = length(U_[first(node_ids)])
2016-08-04 13:15:19 +03:00
field_type = ndim == 1 ? "Scalar" : "Vector"
field_center = "Node"
if ndim == 2
for nid in node_ids
2016-11-28 12:20:54 +02:00
U_[nid] = [U_[nid]; 0.0]
2016-08-04 13:15:19 +03:00
end
ndim = 3
end
2016-11-28 12:20:54 +02:00
U = zeros(X)
for nid in node_ids
loc = nid_mapping[nid]
U[:,loc] = U_[nid]
end
2016-08-04 13:15:19 +03:00
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)
2016-11-28 12:20:54 +02:00
add_child(attribute, new_dataitem(xdmf, path, U))
add_child(frame, attribute)
2016-08-04 13:15:19 +03:00
if (S == Linear) || ((S == Nonlinear) && has_converged(solver))
add_child(temporal_collection, frame)
end
save!(xdmf)
end
2016-06-27 16:11:33 +03:00
### 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()
2016-08-04 13:15:19 +03:00
solver = Nonlinear(0, 1, 10, 5.0e-5, true)
2016-06-27 16:11:33 +03:00
return solver
2016-06-09 01:27:56 +03:00
end
2016-02-05 12:27:36 +02:00
2016-02-01 09:13:07 +02:00
""" Check convergence of problems.
Notes
-----
Default convergence criteria is obtained by checking each sub-problem convergence.
"""
2016-06-27 16:11:33 +03:00
function has_converged(solver::Solver{Nonlinear}; show_info=false,
check_convergence_for_boundary_problems=false)
properties = solver.properties
2016-02-01 09:13:07 +02:00
converged = true
eps = properties.convergence_tolerance
2016-02-01 09:13:07 +02:00
for problem in solver.problems
2016-02-03 20:39:03 +02:00
has_converged = true
if is_field_problem(problem)
has_converged = problem.assembly.u_norm_change < eps
if isapprox(norm(problem.assembly.u), 0.0)
2016-06-27 16:11:33 +03:00
# trivial solution
has_converged = true
end
2016-06-27 16:11:33 +03:00
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)")
2016-02-03 20:39:03 +02:00
end
2016-02-11 02:51:27 +02:00
if is_boundary_problem(problem) && check_convergence_for_boundary_problems
2016-02-03 20:39:03 +02:00
has_converged = problem.assembly.la_norm_change/norm(problem.assembly.la) < eps
2016-06-27 16:11:33 +03:00
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)")
2016-02-01 09:13:07 +02:00
end
converged &= has_converged
end
2016-06-27 16:11:33 +03:00
return converged
2016-02-01 09:13:07 +02:00
end
type NonlinearConvergenceError <: Exception
solver :: Solver
end
function Base.showerror(io::IO, exception::NonlinearConvergenceError)
max_iters = exception.solver.properties.max_iterations
2016-02-01 09:13:07 +02:00
print(io, "nonlinear iteration did not converge in $max_iters iterations!")
end
""" Default solver for quasistatic nonlinear problems. """
2016-11-13 13:24:08 +02:00
function (solver::Solver{Nonlinear})(; show_info=true)
properties = solver.properties
2016-02-01 09:13:07 +02:00
# 1. initialize each problem so that we can start nonlinear iterations
initialize!(solver)
2016-02-01 09:13:07 +02:00
# 2. start non-linear iterations
for properties.iteration=1:properties.max_iterations
2016-06-27 16:11:33 +03:00
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))
2016-02-24 01:20:39 +02:00
2016-06-27 16:11:33 +03:00
# 2.1 update linearized assemblies
2016-06-19 20:01:37 +03:00
assemble!(solver)
2016-06-27 16:11:33 +03:00
# 2.2 call solver for linearized system
2016-08-04 13:15:19 +03:00
solve!(solver)
2016-02-01 09:13:07 +02:00
# 2.3 update solution back to elements
2016-08-04 13:15:19 +03:00
update!(solver)
2016-02-01 09:13:07 +02:00
# 2.4 check convergence
if has_converged(solver)
info("Converged in $(properties.iteration) iterations.")
2016-06-27 16:11:33 +03:00
properties.iteration >= properties.min_iterations && return true
info("Convergence criteria met, but iteration < min_iterations, continuing...")
2016-02-01 09:13:07 +02:00
end
end
# 3. did not converge
2016-06-27 16:11:33 +03:00
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...)
2016-07-03 21:16:03 +03:00
solver = NonlinearSolver(problems...)
solver.name = name
return solver
end
2016-06-27 16:11:33 +03:00
### 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
2016-11-13 13:24:08 +02:00
function (solver::Solver{Linear})(; show_info=true)
2016-06-27 16:11:33 +03:00
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)
2016-08-04 13:15:19 +03:00
solve!(solver)
update!(solver)
2016-06-27 16:11:33 +03:00
t1 = round(Base.time()-t0, 2)
show_info && info("Linear solver ready in $t1 seconds.")
2016-02-01 09:13:07 +02:00
end
2016-02-24 01:20:39 +02:00
2016-06-27 16:11:33 +03:00
""" Convenience function to call linear solver. """
2016-07-01 02:55:56 +03:00
function LinearSolver(problems::Problem...)
2016-06-27 16:11:33 +03:00
solver = Solver(Linear, "default linear solver")
if length(problems) != 0
push!(solver, problems...)
end
return solver
end
function LinearSolver(name::AbstractString, problems::Problem...)
2016-07-01 02:55:56 +03:00
solver = LinearSolver(problems...)
solver.name = name
return solver
end
2016-06-27 16:11:33 +03:00
### End of linear quasistatic solver
2016-07-03 05:01:18 +03:00
### 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
2016-11-13 13:24:08 +02:00
function (solver::Solver{Postprocessor})(; show_info=true)
2016-07-03 05:01:18 +03:00
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...)
2016-07-03 05:01:18 +03:00
solver = Postprocessor(problems...)
solver.name = name
return solver
end