Files
JuliaFEM.jl/src/solvers.jl
T

694 lines
21 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
const Solver = Analysis
const AbstractSolver = AbstractAnalysis
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...)
solver = Solver{S}(name, 0.0, [], [], 0, nothing, false, [], [], 0.0, Dict(), variant)
return solver
2016-02-01 09:13:07 +02:00
end
=#
2016-02-01 09:13:07 +02:00
2018-07-05 11:21:10 +03:00
function Solver(::Type{S}, problems::Problem...) where S<:AbstractSolver
2016-08-04 13:15:19 +03:00
solver = Solver(S, "$(S)Solver")
push!(solver.problems, problems...)
return solver
end
function push!(solver::Solver, problem::Problem)
2016-02-01 09:13:07 +02:00
push!(solver.problems, problem)
end
function getindex(solver::Solver, problem_name::String)
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
function haskey(solver::Solver, field_name::String)
return haskey(solver.fields, field_name)
end
2016-06-27 16:11:33 +03:00
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
"""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.
"""
2017-03-21 08:36:18 +02:00
function get_field_assembly(solver::Solver)
2016-02-05 12:27:36 +02:00
problems = get_field_problems(solver)
2016-06-27 16:11:33 +03:00
problem = problems[1]
2018-10-28 20:40:55 -04:00
M = problem.assembly.M
K = problem.assembly.K
f = problem.assembly.f
2018-11-29 17:40:55 -05:00
K_csc = problem.assembly.K_csc
f_csc = problem.assembly.f_csc
2018-10-28 20:40:55 -04:00
Kg = problem.assembly.Kg
fg = problem.assembly.fg
2016-06-27 16:11:33 +03:00
2018-10-28 20:40:55 -04:00
for problem in problems[2:end]
2016-06-27 16:11:33 +03:00
append!(M, problem.assembly.M)
2018-11-29 17:40:55 -05:00
append!(K, problem.assembly.K)
append!(Kg, problem.assembly.Kg)
2018-11-29 17:40:55 -05:00
append!(f, problem.assembly.f)
# Use in place addition with .+= ?
K_csc += problem.assembly.K_csc
f_csc += problem.assembly.f_csc
2016-06-27 16:11:33 +03:00
append!(fg, problem.assembly.fg)
end
2016-06-27 16:11:33 +03:00
N = size(K, 1)
2016-06-27 16:11:33 +03:00
M = sparse(M, N, N)
2018-11-29 17:40:55 -05:00
K = sparse(K, N, N)
2016-07-03 21:16:03 +03:00
if nnz(K) == 0
2018-09-06 12:02:56 +03:00
@warn("Field assembly seems to be empty. Check that elements are ",
"pushed to problem and formulation is correct.")
2016-07-03 21:16:03 +03:00
end
2018-11-29 17:40:55 -05:00
f = sparse(f, N, 1)
Kg = sparse(Kg, N, N)
fg = sparse(fg, N, 1)
2016-02-05 14:03:27 +02:00
2018-11-29 17:40:55 -05:00
return M, problem.assemble_csc ? K_csc : K, Kg, problem.assemble_csc ? f_csc : 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}()
2017-02-25 18:40:14 +02:00
all_overconstrained_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)
2017-02-25 18:40:14 +02:00
all_overconstrained_dofs = union(all_overconstrained_dofs, overconstrained_dofs)
if length(overconstrained_dofs) != 0
2018-09-06 12:02:56 +03:00
@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_
2018-09-06 12:02:56 +03:00
@warn("overconstrained dof $dof defined in problem $(problem_.name)")
end
end
2018-09-06 12:02:56 +03:00
@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
2018-09-06 12:02:56 +03:00
@warn("List of all overconstrained dofs:")
@warn(sort(collect(all_overconstrained_dofs)))
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, N)
check_for_overconstrained_dofs(solver)
K = spzeros(N, N)
C1 = spzeros(N, N)
C2 = spzeros(N, N)
D = spzeros(N, N)
f = spzeros(N, 1)
g = spzeros(N, 1)
2016-02-05 12:27:36 +02:00
for problem in get_boundary_problems(solver)
assembly = problem.assembly
2018-11-29 17:40:55 -05:00
K_ = sparse(assembly.K, N, N)
C1_ = sparse(assembly.C1, N, N)
C2_ = sparse(assembly.C2, N, N)
D_ = sparse(assembly.D, N, N)
2018-11-29 17:40:55 -05:00
f_ = sparse(assembly.f, N, 1)
g_ = sparse(assembly.g, N, 1)
for dof in assembly.removed_dofs
2018-09-06 12:02:56 +03:00
@info("$(problem.name): removing dof $dof from assembly")
C1_[dof,:] .= 0.0
C2_[dof,:] .= 0.0
end
2017-01-30 12:28:33 +02:00
SparseArrays.dropzeros!(C1_)
SparseArrays.dropzeros!(C2_)
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
2018-09-06 12:02:56 +03:00
@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)
error("overconstrained dofs, not solving problem.")
2016-02-05 12:27:36 +02:00
end
2018-11-29 17:40:55 -05:00
K .+= K_
C1 .+= C1_
C2 .+= C2_
D .+= D_
f .+= f_
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.
"""
2017-01-30 12:28:33 +02:00
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{1}})
2016-08-04 13:15:19 +03:00
nnz(D) == 0 || 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)
2018-06-06 15:36:46 +03:00
2016-11-28 12:20:54 +02:00
if length(B) == 0
2018-09-06 12:02:56 +03:00
@warn("No rows in C2, forget to set Dirichlet boundary conditions to model?")
2016-11-28 12:20:54 +02:00
else
2018-09-06 12:02:56 +03:00
u[B] = lu(C2[B,B2]) \ Vector(g[B])
end
# solve interior domain using LDLt factorization
2018-09-06 12:02:56 +03:00
F = ldlt(K[I,I])
u[I] = F \ Vector(f[I] - K[I,B]*u[B])
2016-11-28 12:20:54 +02:00
# solve lagrange multipliers
2018-09-06 12:02:56 +03:00
la[B] = lu(C1[B2,B]) \ Vector(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.
2017-02-01 12:36:26 +02:00
It is assumed that C1 == C2 and D = 0, so problem is symmetric and zero rows
cand be removed from total system before solution. This kind of system arises
in e.g. mesh tie problem
"""
2016-08-04 13:15:19 +03:00
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{2}})
2017-02-01 12:36:26 +02:00
C1 == C2 || return false
length(D) == 0 || return false
A = [K C1'; C2 D]
b = [f; g]
ndofs = size(K, 2)
2017-02-01 12:36:26 +02:00
nz1 = get_nonzero_rows(A)
nz2 = get_nonzero_columns(A)
nz1 == nz2 || return false
2018-06-06 15:36:46 +03:00
x = zeros(2*ndofs)
2017-02-01 12:36:26 +02:00
x[nz1] = lufact(A[nz1,nz2]) \ full(b[nz1])
u[:] = x[1:ndofs]
la[:] = x[ndofs+1:end]
2017-02-01 12:36:26 +02:00
return true
end
"""
Solve linear system using LU factorization (UMFPACK). This version solves
directly the saddle point problem without elimination of boundary conditions.
If matrix has zero rows, diagonal term is added to that matrix is invertible.
"""
function solve!(solver::Solver, K, C1, C2, D, f, g, u, la, ::Type{Val{3}})
A = [K C1'; C2 D]
b = [f; g]
ndofs = size(K, 2)
2018-09-06 12:02:56 +03:00
nonzero_rows = zeros(2*ndofs)
for j in rowvals(A)
nonzero_rows[j] = 1.0
end
A += sparse(Diagonal(1.0 .- nonzero_rows))
2017-02-01 12:36:26 +02:00
2018-09-06 12:02:56 +03:00
x = lu(A) \ Vector(b[:])
2017-02-01 12:36:26 +02:00
2018-09-06 12:02:56 +03:00
u[:] .= x[1:ndofs]
la[:] .= x[ndofs+1:end]
2017-02-25 18:40:14 +02:00
2016-08-04 13:15:19 +03:00
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. """
2017-01-30 12:28:33 +02:00
function solve!(solver::Solver; empty_assemblies_before_solution=true, symmetric=true)
2016-11-28 12:20:54 +02:00
2018-09-06 12:02:56 +03:00
@info("Solving linear system.")
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)
N = size(K, 2)
Kb, C1, C2, D, fb, g = get_boundary_assembly(solver, N)
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
2017-01-30 12:28:33 +02:00
if empty_assemblies_before_solution
# free up some memory before solution by emptying field assemblies from problems
for problem in get_field_problems(solver)
2016-06-27 16:11:33 +03:00
empty!(problem.assembly)
end
end
2018-06-06 15:36:46 +03:00
#=
if !haskey(solver, "fint")
2018-01-19 21:01:33 +07:00
solver.fields["fint"] = field(solver.time => f)
else
2018-01-19 21:01:33 +07:00
update!(solver.fields["fint"], solver.time => f)
end
fint = solver.fields["fint"]
if length(fint) > 1
# kick in generalized alpha rule for time integration
alpha = solver.alpha
K = (1-alpha)*K
C1 = (1-alpha)*C1
2018-01-19 21:01:33 +07:00
f = (1-alpha)*f + alpha*fint.data[end-1].second
end
=#
ndofs = N
2016-08-04 13:15:19 +03:00
u = zeros(ndofs)
la = zeros(ndofs)
2017-01-30 12:28:33 +02:00
is_solved = false
2018-09-06 12:02:56 +03:00
local i
2017-02-01 12:36:26 +02:00
for i in [1, 2, 3]
2017-01-30 12:28:33 +02:00
is_solved = solve!(solver, K, C1, C2, D, f, g, u, la, Val{i})
if is_solved
2018-09-06 12:02:56 +03:00
t1 = round(Base.time()-t0; digits=2)
norms = (norm(u), norm(la))
@info("Solved linear system in $t1 seconds using solver $i. " *
"Solution norms (||u||, ||la||): $norms.")
2017-01-30 12:28:33 +02:00
break
end
end
if !is_solved
error("Failed to solve linear system!")
2016-06-09 01:27:56 +03:00
end
#push!(solver.norms, norms)
#solver.u = u
#solver.la = la
2016-08-04 13:15:19 +03:00
2018-09-06 12:02:56 +03:00
@info("")
2016-11-28 12:20:54 +02:00
return u, la
2016-06-27 16:11:33 +03:00
end
2017-08-19 12:05:32 +03:00
"""
assemble!(solver; with_mass_matrix=false)
Default assembler for solver.
This function loops over all problems defined in problem and launches
standard assembler for them. As a result, each problem.assembly is
populated with global stiffness matrix, force vector, and, optionally,
mass matrix.
"""
function assemble!(solver::Solver, time::Float64; with_mass_matrix=false)
2018-09-06 12:02:56 +03:00
@info("Assembling problems ...")
2016-10-13 00:59:38 +03:00
2017-08-19 12:05:32 +03:00
for problem in get_problems(solver)
timeit("assemble $(problem.name)") do
empty!(problem.assembly)
assemble!(problem, time)
2016-10-13 00:59:38 +03:00
end
end
2017-08-19 12:05:32 +03:00
if with_mass_matrix
for problem in get_field_problems(solver)
timeit("assemble $(problem.name) mass matrix") do
assemble!(problem, time, Val{:mass_matrix})
2017-08-19 12:05:32 +03:00
end
end
end
2016-10-13 00:59:38 +03:00
#=
2016-10-13 00:59:38 +03:00
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
solver.ndofs = ndofs
=#
2018-09-06 12:02:56 +03:00
@info("Assembly done!")
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. """
2017-03-21 08:36:18 +02:00
function initialize!(solver::Solver)
2016-08-04 13:15:19 +03:00
if solver.initialized
2018-09-06 12:02:56 +03:00
@warn("initialize!(): solver already initialized")
2016-08-04 13:15:19 +03:00
return
end
2018-09-06 12:02:56 +03:00
@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)
2018-09-06 12:02:56 +03:00
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)
2018-09-06 12:02:56 +03:00
@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)
2018-09-06 12:02:56 +03:00
@info("Total number of nodes in problems: $nnodes")
maxdof = maximum(nodes)*field_dim
2018-09-06 12:02:56 +03:00
@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
2018-09-06 12:02:56 +03:00
t1 = round(Base.time()-t0; digits=2)
@info("Initialized solver in $t1 seconds.")
2016-08-04 13:15:19 +03:00
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
2017-06-28 13:54:04 +03:00
"""
Return nodal field from all problems defined in solver.
Examples
--------
To return e.g. geometry defined in nodal points at time t=0.0, one can write:
julia> solver("geometry", 0.0)
"""
2017-03-21 08:36:18 +02:00
function (solver::Solver)(field_name::String, time::Float64)
2016-11-13 13:24:08 +02:00
fields = []
for problem in get_problems(solver)
field = problem(field_name, time)
2017-06-28 13:54:04 +03:00
if field == nothing
continue
end
2016-11-13 13:24:08 +02:00
if length(field) == 0
2018-09-06 12:02:56 +03:00
@warn("no field $field_name found for problem $(problem.name)")
2017-06-28 13:54:04 +03:00
continue
2016-11-13 13:24:08 +02:00
end
2017-06-28 13:54:04 +03:00
push!(fields, field)
end
if length(fields) == 0
return Dict{Integer, Vector{Float64}}()
2016-11-13 13:24:08 +02:00
end
2016-08-01 01:15:41 +03:00
return merge(fields...)
end
2018-07-05 11:21:10 +03:00
function update!(solver::Solver{S}, u, la, time) where S
2017-03-21 08:36:18 +02:00
for problem in get_problems(solver)
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, time)
2016-06-27 16:11:33 +03:00
end
end
2017-03-21 08:36:18 +02:00
""" Default postprocess for solver. Loop all problems and run postprocess
functions to calculate secondary fields, i.e. contact pressure, stress,
heat flux, reaction force etc. quantities.
"""
function postprocess!(solver::Solver, time)
2018-09-06 12:02:56 +03:00
problems = get_problems(solver)
nproblems = length(problems)
@info("Postprocessing $nproblems problems.")
for problem in problems
2017-03-21 08:36:18 +02:00
for field_name in problem.postprocess_fields
field = Val{Symbol(field_name)}
2018-09-06 12:02:56 +03:00
@info("Running postprocess for problem $(problem.name), field $field_name")
postprocess!(problem, time, field)
2016-08-04 13:15:19 +03:00
end
2016-11-28 12:20:54 +02:00
end
2017-03-21 08:36:18 +02:00
end
"""
write_results!(solver, time)
Default xdmf update for solver. Loop all problems and write them individually
2017-03-21 08:36:18 +02:00
to Xdmf file. By default write the main unknown field (displacement, temperature,
...) and any fields requested separately in `problem.postprocess_fields` vector
(stress, strain, ...)
"""
function write_results!(solver, time)
results_writers = get_results_writers(solver)
if length(results_writers) == 0
2018-09-06 12:02:56 +03:00
@info("No result writers are attached to analysis, not writing output.")
@info("To write results to Xdmf file, attach Xdmf to analysis, i.e.")
@info("xdmf_output = Xdmf(\"simulation_results\")")
@info("add_results_writer!(analysis, xdmf_output)")
2017-03-21 08:36:18 +02:00
return
2016-08-04 13:15:19 +03:00
end
# FIXME: result writer can be anything, not only Xdmf
for xdmf in results_writers
for problem in get_problems(solver)
fields = [get_unknown_field_name(problem); problem.postprocess_fields]
if is_boundary_problem(problem)
fields = [fields; get_parent_field_name(problem)]
end
update_xdmf!(xdmf, problem, time, fields)
2017-03-21 08:36:18 +02:00
end
2016-08-04 13:15:19 +03:00
end
end
2016-06-27 16:11:33 +03:00
### Nonlinear quasistatic solver
2018-07-05 11:21:10 +03:00
mutable struct Nonlinear <: AbstractSolver
2018-06-06 15:36:46 +03:00
time :: Float64
2016-06-27 16:11:33 +03:00
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()
2018-06-06 15:36:46 +03:00
solver = Nonlinear(0.0, 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.
"""
2017-01-30 12:28:33 +02:00
function has_converged(solver::Solver{Nonlinear})
properties = solver.properties
2016-02-01 09:13:07 +02:00
converged = true
eps = properties.convergence_tolerance
2017-01-30 12:28:33 +02:00
for problem in get_field_problems(solver)
has_converged = problem.assembly.u_norm_change < eps
if isapprox(norm(problem.assembly.u), 0.0)
# trivial solution
has_converged = true
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
""" Default solver for quasistatic nonlinear problems. """
2018-06-06 15:36:46 +03:00
function FEMBase.run!(solver::Solver{Nonlinear})
2018-06-06 15:36:46 +03:00
time = solver.properties.time
problems = get_problems(solver)
properties = solver.properties
2016-02-01 09:13:07 +02:00
# 1. initialize each problem so that we can start nonlinear iterations
for problem in problems
initialize!(problem, time)
end
2016-02-01 09:13:07 +02:00
# 2. start non-linear iterations
for properties.iteration=1:properties.max_iterations
2018-09-06 12:02:56 +03:00
@info(repeat("-", 80))
@info("Starting nonlinear iteration #$(properties.iteration)")
@info("Increment time t=$(round(time; digits=3))")
@info(repeat("-", 80))
2016-02-24 01:20:39 +02:00
2017-03-21 08:36:18 +02:00
# 2.1 update assemblies
for problem in problems
empty!(problem.assembly)
assemble!(problem, time)
end
2017-03-21 08:36:18 +02:00
2016-06-27 16:11:33 +03:00
# 2.2 call solver for linearized system
u, la = solve!(solver)
2017-03-21 08:36:18 +02:00
2016-02-01 09:13:07 +02:00
# 2.3 update solution back to elements
update!(solver, u, la, time)
2016-02-01 09:13:07 +02:00
# 2.4 check convergence
2017-03-21 08:36:18 +02:00
if properties.iteration >= properties.min_iterations && has_converged(solver)
2018-09-06 12:02:56 +03:00
@info("Converged in $(properties.iteration) iterations.")
2017-03-21 08:36:18 +02:00
# 2.4.1 run any postprocessing of problems
postprocess!(solver, time)
2017-03-21 08:36:18 +02:00
# 2.4.2 update Xdmf output
write_results!(solver, time)
2017-03-21 08:36:18 +02:00
return true
2016-02-01 09:13:07 +02:00
end
end
# 3. did not converge
2017-01-30 12:28:33 +02:00
if properties.error_if_no_convergence
error("nonlinear iteration did not converge in $(properties.iteration) iterations!")
end
2016-06-27 16:11:33 +03:00
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)
"""
2018-07-05 11:21:10 +03:00
mutable struct Linear <: AbstractSolver
2018-06-06 15:36:46 +03:00
time :: Float64
end
function Linear()
return Linear(0.0)
2016-06-27 16:11:33 +03:00
end
2018-09-06 12:02:56 +03:00
function FEMBase.run!(analysis::Analysis{Linear})
time = analysis.properties.time
@info("Running linear quasistatic analysis `$(analysis.name)` at time $time.")
problems = get_problems(analysis)
nproblems = length(problems)
@info("Assembling $nproblems problems.")
@timeit "assemble problems" for problem in problems
isempty(problem.assembly) || continue
initialize!(problem, time)
assemble!(problem, time)
2016-06-27 16:11:33 +03:00
end
2018-09-06 12:02:56 +03:00
@timeit "solve linear system" u, la = solve!(analysis)
@timeit "update problems" update!(analysis, u, la, time)
postprocess!(analysis, time)
write_results!(analysis, time)
@info("Quasistatic linear analysis ready.")
2016-06-27 16:11:33 +03:00
end
# Convenience functions
function LinearSolver(name::String="Linear solver")
return Solver(Linear, name)
2016-02-01 09:13:07 +02:00
end
2016-02-24 01:20:39 +02:00
2016-07-01 02:55:56 +03:00
function LinearSolver(problems::Problem...)
solver = LinearSolver()
add_problems!(solver, collect(problems))
2016-06-27 16:11:33 +03:00
return solver
end
function NonlinearSolver(name::String="Nonlinear solver")
return Solver(Nonlinear, name)
end
function NonlinearSolver(problems::Problem...)
solver = NonlinearSolver()
add_problems!(solver, collect(problems))
2016-07-01 02:55:56 +03:00
return solver
end
2016-06-27 16:11:33 +03:00
# will be deprecated
2018-06-06 15:36:46 +03:00
function (solver::Solver)(time::Float64=0.0)
2018-09-06 12:02:56 +03:00
@warn("analysis(time) is deprecated. Instead, use run!(analysis)")
2018-06-06 15:36:46 +03:00
solver.properties.time = time
run!(solver)
end
function solve!(solver::Solver, time::Float64)
2018-09-06 12:02:56 +03:00
@warn("solve!(analysis, time) is deprecated. Instead, use run!(analysis)")
2018-06-06 15:36:46 +03:00
solver.properties.time = time
run!(solver)
end