Files
JuliaFEM.jl/src/solvers.jl
T

429 lines
13 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
2015-10-28 04:29:14 +02:00
"""
Solve field equations for a single problem with some dofs fixed. This can be used
to test nonlinear element formulations. Dirichlet boundary is assumed to be homogeneous
and degrees of freedom are eliminated. So if boundary condition is known in nodal
points and everything is zero this should be quite good.
"""
function solve!(problem::Problem, free_dofs::Vector{Int}, time::Float64; max_iterations::Int=10, tolerance::Float64=1.0e-12, dump_matrices::Bool=false, callback=nothing)
2015-11-11 00:52:16 +02:00
info("start solver")
assembly = Assembly()
# x = zeros(ga.ndofs)
# dx = fill!(similar(x), 0.0)
# FIXME: better.
x = nothing
dx = nothing
2015-10-28 04:29:14 +02:00
field_name = get_unknown_field_name(problem)
dim = get_unknown_field_dimension(problem)
for i=1:max_iterations
assemble!(assembly, problem, time)
A = sparse(assembly.stiffness_matrix)
b = sparse(assembly.force_vector)
2015-10-28 04:29:14 +02:00
if dump_matrices
dump(full(A))
dump(full(b)')
end
if isa(dx, Void)
x = zeros(length(b))
dx = zeros(length(b))
end
dx[free_dofs] = lufact(A[free_dofs,free_dofs]) \ full(b)[free_dofs]
2015-11-11 00:52:16 +02:00
info("Difference in solution norm: $(norm(dx))")
2015-10-28 04:29:14 +02:00
x += dx
if !(isa(callback, Void))
callback(x)
end
2015-11-27 10:10:00 +02:00
for element in get_elements(problem)
gdofs = get_gdofs(element, problem.dim)
2015-11-21 18:23:41 +02:00
data = full(x[gdofs])
2015-11-27 10:10:00 +02:00
if length(data) != length(element)
data = reshape(data, problem.dim, length(element))
data = [data[:,i] for i=1:size(data,2)]
2015-11-21 18:23:41 +02:00
end
push!(element[field_name], time => data)
2015-10-28 04:29:14 +02:00
end
2015-11-11 00:52:16 +02:00
norm(dx) < tolerance && return
2015-10-28 04:29:14 +02:00
end
2015-11-11 00:52:16 +02:00
error("Did not converge in $max_iterations iterations")
2015-10-28 04:29:14 +02:00
end
2015-10-09 23:45:28 +03:00
2015-11-27 10:10:00 +02:00
""" Simple linear solver for educational purposes. """
2016-02-01 09:13:07 +02:00
type LinearSolver
name :: ASCIIString
field_problems :: Vector{Problem}
2016-02-03 06:49:42 +02:00
boundary_problems :: Vector{Problem}
end
function LinearSolver(name="LinearSolver")
LinearSolver(name, [], [])
end
2016-02-03 06:49:42 +02:00
function push!{P<:FieldProblem}(solver::LinearSolver, problem::Problem{P})
length(solver.field_problems) == 0 || error("Only one field problem allowed for LinearSolver")
push!(solver.field_problems, problem)
end
2016-02-03 06:49:42 +02:00
function push!{P<:BoundaryProblem}(solver::LinearSolver, problem::Problem{P})
length(solver.boundary_problems) == 0 || error("Only one boundary problem allowed for LinearSolver")
push!(solver.boundary_problems, problem)
2015-10-09 23:45:28 +03:00
end
"""
Call solver to solve a set of problems.
2015-10-28 04:29:14 +02:00
This is a simple direct solver for demonstration purposes. It handles the
2015-10-09 23:45:28 +03:00
common situation, i.e., some main field problem and it's Dirichlet boundary.
2015-10-28 04:29:14 +02:00
2015-11-27 10:10:00 +02:00
Ku + C'λ = f
2015-10-28 04:29:14 +02:00
Cu = g
2015-10-09 23:45:28 +03:00
"""
2015-11-27 10:10:00 +02:00
function call(solver::LinearSolver, time::Float64)
t0 = Base.time()
field_name = get_unknown_field_name(solver.field_problems[1])
field_dim = get_unknown_field_dimension(solver.field_problems[1])
2015-11-27 10:10:00 +02:00
info("solving $field_name problem, $field_dim dofs / nodes")
field_assembly = assemble(solver.field_problems[1], time)
boundary_assembly = assemble(solver.boundary_problems[1], time)
2015-11-27 10:10:00 +02:00
#info("Creating sparse matrices")
2015-11-27 10:10:00 +02:00
K = sparse(field_assembly.stiffness_matrix)
dim = size(K, 1)
f = sparse(field_assembly.force_vector, dim, 1)
C = sparse(boundary_assembly.stiffness_matrix, dim, dim)
g = sparse(boundary_assembly.force_vector, dim, 1)
2015-10-28 04:29:14 +02:00
# create a saddle point problem
2015-11-27 10:10:00 +02:00
A = [K C'; C' zeros(C)]
b = [f; g]
2015-10-09 23:45:28 +03:00
# solve problem
2015-11-18 01:19:04 +02:00
nz = unique(rowvals(A)) # take only non-zero rows
2015-10-09 23:45:28 +03:00
x = zeros(b)
x[nz] = lufact(A[nz,nz]) \ full(b[nz])
# get "problem-wise" solution vectors
2015-11-27 10:10:00 +02:00
u = x[1:dim]
la = x[dim+1:end]
2015-10-09 23:45:28 +03:00
# update field for elements in problem 1
for element in get_elements(solver.field_problems[1])
2015-11-27 10:10:00 +02:00
gdofs = get_gdofs(element, field_dim)
local_sol = vec(full(u[gdofs]))
# if solving vector field, modify local solution vector
# to array of vectors
if field_dim != 1
local_sol = reshape(local_sol, field_dim, length(element))
local_sol = [local_sol[:,i] for i=1:size(local_sol,2)]
end
2015-11-27 10:10:00 +02:00
if haskey(element, field_name)
push!(element[field_name], time => local_sol)
else
element[field_name] = (time => local_sol)
end
2015-10-09 23:45:28 +03:00
end
t1 = round(Base.time()-t0, 2)
info("solved problem in $t1 seconds.")
2015-11-27 10:10:00 +02:00
return norm(u)
2015-10-09 23:45:28 +03:00
end
2016-02-01 09:13:07 +02:00
# Tuple{Symbol,Any,Any} or Function
type Solver
2016-02-05 12:27:36 +02:00
name :: ASCIIString # some descriptive name for problem
time :: Real # current time
iteration :: Int # iteration counter
norms :: Vector{Tuple} # solution norms for convergence studies
2016-02-05 12:27:36 +02:00
ndofs :: Int # total dimension of global stiffness matrix, i.e., dim*nnodes
2016-02-03 06:49:42 +02:00
problems :: Vector{Problem}
2016-02-05 12:27:36 +02:00
is_linear_system :: Bool # setting this to true makes assumption of one step convergence
nonlinear_system_min_iterations :: Int64
2016-02-01 09:13:07 +02:00
nonlinear_system_max_iterations :: Int64
nonlinear_system_convergence_tolerance :: Float64
nonlinear_system_error_if_no_convergence :: Bool
2016-02-01 09:13:07 +02:00
linear_system_solver :: Symbol
end
function Solver(name::ASCIIString="default solver", time::Real=0.0)
return Solver(
2016-02-05 12:27:36 +02:00
name,
time,
0, # iteration #
[], # solution norms in (norm(u), norm(la)) tuples
2016-02-05 12:27:36 +02:00
0, # ndofs
2016-02-01 09:13:07 +02:00
[], # array of problems
2016-02-05 12:27:36 +02:00
false, # is_linear_system
1, # min nonlinear iterations
2016-02-01 09:13:07 +02:00
10, # max nonlinear iterations
5.0e-5, # nonlinear iteration convergence tolerance
true, # throw error if no convergence
2016-02-01 09:13:07 +02:00
:DirectLinearSolver # linear system solution method
)
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
# one-liner helpers to identify problem types
function is_field_problem(problem)
2016-02-03 06:49:42 +02:00
return false
end
function is_field_problem{P<:FieldProblem}(problem::Problem{P})
return true
2016-02-01 09:13:07 +02:00
end
function is_boundary_problem(problem)
2016-02-03 06:49:42 +02:00
return false
end
function is_boundary_problem{P<:BoundaryProblem}(problem::Problem{P})
return true
2016-02-01 09:13:07 +02:00
end
function is_dirichlet_problem(problem)
2016-02-03 06:49:42 +02:00
return false
end
function is_dirichlet_problem{P<:Problem{Dirichlet}}(problem::P)
return true
2016-02-01 09:13:07 +02:00
end
2016-02-03 06:49:42 +02:00
#=
function is_mortar_problem{P<:Problem{Mortar}}(problem::P)
return true
2016-02-01 09:13:07 +02:00
end
2016-02-03 06:49:42 +02:00
=#
2016-02-01 09:13:07 +02:00
function get_field_problems(solver::Solver)
filter(is_field_problem, solver.problems)
end
function get_boundary_problems(solver::Solver)
filter(is_boundary_problem, solver.problems)
end
function get_dirichlet_problems(solver::Solver)
filter(is_dirichlet_problem, solver.problems)
end
function get_mortar_problems(solver::Solver)
filter(is_mortar_problem, solver.problems)
end
2016-02-05 14:03:27 +02:00
""" Posthook for field assembly. By default, do nothing. """
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-02-05 12:27:36 +02:00
K, f :: 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.
"""
function get_field_assembly(solver::Solver)
2016-02-05 12:27:36 +02:00
problems = get_field_problems(solver)
2016-02-01 09:13:07 +02:00
K = SparseMatrixCOO()
f = SparseMatrixCOO()
for problem in problems
2016-02-05 14:03:27 +02:00
assembly = problem.assembly
append!(K, assembly.K)
append!(f, assembly.f)
2016-02-01 09:13:07 +02:00
end
2016-02-05 12:27:36 +02:00
K = sparse(K)
solver.ndofs = size(K, 1)
f = sparse(f, solver.ndofs, 1)
2016-02-05 14:03:27 +02:00
# run any posthook for assembly if defined
args = Tuple{Solver, SparseMatrixCSC, SparseMatrixCSC}
if method_exists(field_assembly_posthook!, args)
field_assembly_posthook!(solver, K, f)
end
2016-02-01 09:13:07 +02:00
return K, f
end
2016-02-05 14:03:27 +02:00
""" Posthook for boundary assembly. By default, do nothing. """
function boundary_assembly_posthook!
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
-------
2016-02-05 12:27:36 +02:00
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.
2016-02-01 09:13:07 +02:00
"""
function get_boundary_assembly(solver::Solver)
2016-02-05 12:27:36 +02:00
ndofs = solver.ndofs
@assert ndofs != 0
Kc = spzeros(ndofs, ndofs)
2016-02-05 12:27:36 +02:00
C1 = spzeros(ndofs, ndofs)
C2 = spzeros(ndofs, ndofs)
D = spzeros(ndofs, ndofs)
g = spzeros(ndofs, 1)
for problem in get_boundary_problems(solver)
assembly = problem.assembly
Kc_ = 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)
g_ = sparse(assembly.g, ndofs, 1)
2016-02-05 14:03:27 +02:00
# check for overconstraint situation and handle it if possible
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
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
Kc += Kc_
2016-02-05 12:27:36 +02:00
C1 += C1_
C2 += C2_
D += D_
g += g_
2016-02-01 09:13:07 +02:00
end
return Kc, C1, C2, D, g
2016-02-01 09:13:07 +02:00
end
2016-02-05 12:27:36 +02:00
2016-02-01 09:13:07 +02:00
""" Solve linear system using LU factorization (UMFPACK).
"""
2016-02-03 20:39:03 +02:00
function solve_linear_system(solver::Solver, ::Type{Val{:DirectLinearSolver}})
2016-02-01 09:13:07 +02:00
info("solving linear system of $(length(solver.problems)) problems.")
t0 = time()
# assemble field problems
K, f = get_field_assembly(solver)
# assemble boundary problems
Kc, C1, C2, D, g = get_boundary_assembly(solver)
2016-02-01 09:13:07 +02:00
# construct global system Ax=b and solve using lu factorization
A = [K+Kc C1'; C2 D]
2016-02-01 09:13:07 +02:00
b = [f; g]
2016-02-05 12:27:36 +02:00
nz = get_nonzero_rows(A)
2016-02-01 09:13:07 +02:00
x = zeros(length(b))
2016-02-05 12:27:36 +02:00
x[nz] = lufact(A[nz,nz]) \ full(b[nz])
2016-02-01 09:13:07 +02:00
2016-02-05 12:27:36 +02:00
ndofs = solver.ndofs
u = x[1:ndofs]
la = x[ndofs+1:end]
2016-02-01 09:13:07 +02:00
info("UMFPACK: solved in ", time()-t0, " seconds. norm = ", norm(u))
2016-02-03 20:39:03 +02:00
return u, la
2016-02-01 09:13:07 +02: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-02-11 02:51:27 +02:00
function has_converged(solver::Solver; check_convergence_for_boundary_problems=false)
2016-02-01 09:13:07 +02:00
converged = true
2016-02-03 20:39:03 +02:00
eps = solver.nonlinear_system_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)
has_converged = true
end
2016-02-11 02:51:27 +02:00
info("Details for problem $(problem.name)")
info("Norm: $(norm(problem.assembly.u))")
info("Norm change: $(problem.assembly.u_norm_change)")
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-02-11 02:51:27 +02:00
info("Details for problem $(problem.name)")
info("Norm: $(norm(problem.assembly.la))")
info("Norm change: $(problem.assembly.la_norm_change)")
info("Has converged? $(has_converged)")
2016-02-01 09:13:07 +02:00
end
converged &= has_converged
end
return converged || solver.is_linear_system
end
type NonlinearConvergenceError <: Exception
solver :: Solver
end
function Base.showerror(io::IO, exception::NonlinearConvergenceError)
max_iters = exception.solver.nonlinear_system_max_iterations
print(io, "nonlinear iteration did not converge in $max_iters iterations!")
end
""" Main solver loop.
"""
function call(solver::Solver)
2016-02-01 09:13:07 +02:00
# 1. initialize each problem so that we can start nonlinear iterations
for problem in solver.problems
initialize!(problem, solver.time)
end
# 2. start non-linear iterations
for solver.iteration=1:solver.nonlinear_system_max_iterations
# 2.1 update linearized assemblies (if needed)
for problem in solver.problems
problem.assembly.changed = true # force reassembly
assemble!(problem, solver.time)
end
# 2.2 call solver for linearized system (default: direct lu factorization)
2016-02-03 20:39:03 +02:00
u, la = solve_linear_system(solver, Val{solver.linear_system_solver})
push!(solver.norms, (norm(u), norm(la)))
2016-02-01 09:13:07 +02:00
# 2.3 update solution back to elements
for problem in solver.problems
u_new, la_new = update_assembly!(problem, u, la)
update_elements!(problem, u_new, la_new)
2016-02-01 09:13:07 +02:00
end
# 2.4 check convergence
if has_converged(solver)
info("Converged in $(solver.iteration) iterations.")
if solver.iteration < solver.nonlinear_system_min_iterations
info("Converged but continuing")
else
return true
end
2016-02-01 09:13:07 +02:00
end
end
# 3. did not converge
if solver.nonlinear_system_error_if_no_convergence
throw(NonlinearConvergenceError(solver))
end
2016-02-01 09:13:07 +02:00
end