heat problem

This commit is contained in:
Jukka Aho
2016-07-01 02:55:56 +03:00
parent ce9eb715ec
commit 9f4eface6c
11 changed files with 328 additions and 33 deletions
+5 -2
View File
@@ -9,8 +9,10 @@ module JuliaFEM
importall Base
using ForwardDiff
using JLD
autodiffcache = ForwardDiffCache()
# export derivative, jacobian, hessian
#Grad = Val{:Grad}
#detJ = Val{:detJ}
#export Grad, detJ
include("common.jl")
@@ -169,4 +171,5 @@ module Interfaces
include("interfaces.jl")
end
end # module
+17 -4
View File
@@ -44,17 +44,30 @@ end
function assemble_posthook!
end
function assemble!(problem::Problem, time::Real)
function assemble!(problem::Problem, time=0.0; auto_initialize=true)
if !isempty(problem.assembly)
warn("problem.assembly is not empty and assembling, are you sure you know what are you doing?")
warn("Assemble problem $(problem.name): problem.assembly is not empty and assembling, are you sure you know what are you doing?")
end
if method_exists(assemble_prehook!, Tuple{typeof(problem), Real})
if isempty(problem.elements)
warn("Assemble problem $(problem.name): problem.elements is empty, no elements in problem?")
else
first_element = first(problem.elements)
unknown_field_name = get_unknown_field_name(problem)
if !haskey(first_element, unknown_field_name)
warn("Assemble problem $(problem.name): seems that problem is uninitialized.")
if auto_initialize
info("Initializing problem $(problem.name) at time $time automatically.")
initialize!(problem, time)
end
end
end
if method_exists(assemble_prehook!, Tuple{typeof(problem), Float64})
assemble_prehook!(problem, time)
end
for element in get_elements(problem)
assemble!(problem.assembly, problem, element, time)
end
if method_exists(assemble_posthook!, Tuple{typeof(problem), Real})
if method_exists(assemble_posthook!, Tuple{typeof(problem), Float64})
assemble_posthook!(problem, time)
end
end
+24 -3
View File
@@ -29,10 +29,18 @@ function setindex!(element::Element, data, field_name::ASCIIString)
element.fields[field_name] = Field(data)
end
function call(element::Element, field_name::ASCIIString)
return element[field_name]
end
function call(element::Element, field_name::ASCIIString, time)
return element[field_name](time)
end
function last(element::Element, field_name::ASCIIString)
return last(element[field_name])
end
function call(element::Element, ip, time)
return get_basis(element, ip, time)
end
@@ -80,6 +88,10 @@ function call(element::Element, field::DCTI, ip, time::Float64)
return field.data
end
function call(element::Element, field::DCTV, ip, time::Float64)
return field(time).data
end
function call(element::Element, field::CVTV, ip, time::Float64)
return field(ip, time)
end
@@ -89,7 +101,9 @@ function call(element::Element, field::Field, ip, time::Float64)
basis = element(ip, time)
n = length(element)
m = length(field_)
@assert n == m
if n != m
error("Error when trying to interpolate field $field at coords $ip and time $time: element length is $n and field length is $m, f = Nᵢfᵢ makes no sense!")
end
return sum([field_[i]*basis[i] for i=1:n])
end
@@ -118,7 +132,7 @@ function update!{K,V}(element::Element, field_name::ASCIIString, data::Pair{Floa
update!(element, field_name, time => element_data)
end
function update!(element::Element, field_name::ASCIIString, datas::Union{Real, Vector, Pair{Float64, Union{Real, Vector{Any}}}}...)
function update!(element::Element, field_name::ASCIIString, datas::Union{Real, Vector, Pair{Float64, Union{Float64, Real, Vector{Any}}}}...)
for data in datas
if haskey(element, field_name)
update!(element[field_name], data)
@@ -132,6 +146,12 @@ function update!(element::Element, field_name::ASCIIString, datas::Union{Real, V
end
end
function update!(element::Element, field_name::ASCIIString, datas::Pair...)
for data in datas
update!(element, field_name, data)
end
end
function update!(element::Element, field_name::ASCIIString, data::Pair{Float64, Vector{Any}})
if haskey(element, field_name)
update!(element[field_name], data)
@@ -200,11 +220,12 @@ function update!(elements::Vector, field_name::ASCIIString, data)
end
end
dbasis_cache = ForwardDiff.jacobian
""" Evaluate partial derivatives of basis functions using ForwardDiff. """
function get_dbasis(element::Element, ip, time)
xi = isa(ip, IP) ? ip.coords : ip
basis(xi) = vec(get_basis(element, xi, time))
return ForwardDiff.jacobian(basis, xi, cache=autodiffcache)'
return ForwardDiff.jacobian(basis, xi)'
end
""" Check existence of field. """
+119 -4
View File
@@ -22,23 +22,59 @@ where
f = temperature load defined on volume elements
g = temperature flux defined on boundary elements
Parameters
----------
temperature thermal conductivity
temperature load
temperature flux
thermal conductivity
heat source
heat flux
heat transfer coefficient
external temperature
Formulations
------------
1D, 2D, 3D
References
----------
https://en.wikipedia.org/wiki/Heat_equation
https://en.wikipedia.org/wiki/Heat_capacity
https://en.wikipedia.org/wiki/Thermal_conduction
https://en.wikipedia.org/wiki/Thermal_conductivity
https://en.wikipedia.org/wiki/Thermal_diffusivity
https://en.wikipedia.org/wiki/Volumetric_heat_capacity
"""
type Heat <: FieldProblem
formulation :: ASCIIString
end
function Heat()
return Heat("3D")
end
function get_unknown_field_name(problem::Problem{Heat})
return "temperature"
end
function get_unknown_field_type(problem::Problem{Heat})
return Float64
function assemble!(assembly::Assembly, problem::Problem{Heat}, element::Element, time=0.0)
formulation = Val{Symbol(problem.properties.formulation)}
assemble!(assembly, problem, element, time, formulation)
end
function assemble!(assembly::Assembly, problem::Problem{Heat}, element::Element, time=0.0)
# 3d heat problems
function assemble!{E}(assembly::Assembly, problem::Problem{Heat}, element::Element{E}, time, ::Type{Val{Symbol("3D")}})
info("Unknown element type $E for 3d heat problem!")
end
typealias Heat3DVolumeElements Union{Tet4, Tet10, Hex8}
typealias Heat3DSurfaceElements Union{Tri3, Tri6, Quad4}
function assemble!{E<:Heat3DVolumeElements}(assembly::Assembly, problem::Problem{Heat}, element::Element{E}, time, ::Type{Val{Symbol("3D")}})
gdofs = get_gdofs(problem, element)
field_name = get_unknown_field_name(problem)
nnodes = length(element)
@@ -57,6 +93,85 @@ function assemble!(assembly::Assembly, problem::Problem{Heat}, element::Element,
f = element("$field_name load", ip, time)
fq += w*N'*f
end
end
T = vec(element[field_name](time))
fq -= K*T
add!(assembly.K, gdofs, gdofs, K)
add!(assembly.f, gdofs, fq)
end
function assemble!{E<:Heat3DSurfaceElements}(assembly::Assembly, problem::Problem{Heat}, element::Element{E}, time, ::Type{Val{Symbol("3D")}})
gdofs = get_gdofs(problem, element)
field_name = get_unknown_field_name(problem)
nnodes = length(element)
K = zeros(nnodes, nnodes)
fq = zeros(nnodes)
for ip in get_integration_points(element)
detJ = element(ip, time, Val{:detJ})
w = ip.weight*detJ
N = element(ip, time)
if haskey(element, "$field_name flux")
q = element("$field_name flux", ip, time)
fq += w*N'*q
end
if haskey(element, "$field_name heat transfer coefficient")
h = element("$field_name heat transfer coefficient", ip, time)
Tu = element("$field_name external temperature", ip, time)
K += w*h*N'*N
fq += w*N'*h*Tu
end
end
T = vec(element[field_name](time))
fq -= K*T
add!(assembly.K, gdofs, gdofs, K)
add!(assembly.f, gdofs, fq)
end
# 2d heat problems
function assemble!{E}(assembly::Assembly, problem::Problem{Heat}, element::Element{E}, time, ::Type{Val{Symbol("2D")}})
info("Unknown element type $E for 2d heat problem!")
end
typealias Heat2DVolumeElements Union{Tri3, Tri6, Quad4}
typealias Heat2DSurfaceElements Union{Seg2, Seg3}
function assemble!{E<:Heat2DVolumeElements}(assembly::Assembly, problem::Problem{Heat}, element::Element{E}, time, ::Type{Val{Symbol("2D")}})
gdofs = get_gdofs(problem, element)
field_name = get_unknown_field_name(problem)
nnodes = length(element)
K = zeros(nnodes, nnodes)
fq = zeros(nnodes)
for ip in get_integration_points(element)
detJ = element(ip, time, Val{:detJ})
w = ip.weight*detJ
N = element(ip, time)
if haskey(element, "$field_name thermal conductivity")
dN = element(ip, time, Val{:Grad})
k = element("$field_name thermal conductivity", ip, time)
K += w*k*dN'*dN
end
if haskey(element, "$field_name load")
f = element("$field_name load", ip, time)
fq += w*N'*f
end
end
T = vec(element[field_name](time))
fq -= K*T
add!(assembly.K, gdofs, gdofs, K)
add!(assembly.f, gdofs, fq)
end
function assemble!{E<:Heat2DSurfaceElements}(assembly::Assembly, problem::Problem{Heat}, element::Element{E}, time, ::Type{Val{Symbol("2D")}})
gdofs = get_gdofs(problem, element)
field_name = get_unknown_field_name(problem)
nnodes = length(element)
K = zeros(nnodes, nnodes)
fq = zeros(nnodes)
for ip in get_integration_points(element)
detJ = element(ip, time, Val{:detJ})
w = ip.weight*detJ
N = element(ip, time)
if haskey(element, "$field_name flux")
g = element("$field_name flux", ip, time)
fq += w*N'*g
+22 -3
View File
@@ -95,17 +95,36 @@ end
(xi) -> [1.0, xi[1], xi[2], xi[1]^2, xi[2]^2, xi[1]*xi[2]])
@create_lagrange_element(Quad4, "4 node bilinear quadrangle element",
[-1.0 1.0 1.0 -1.0
-1.0 -1.0 1.0 1.0],
[-1.0 1.0 1.0 -1.0
-1.0 -1.0 1.0 1.0],
(xi) -> [1.0, xi[1], xi[2], xi[1]*xi[2]])
@create_lagrange_element(Quad9, "9 node bilinear quadrangle element",
[-1.0 1.0 1.0 -1.0 0.0 1.0 0.0 -1.0
-1.0 -1.0 1.0 1.0 -1.0 0.0 1.0 0.0],
(xi) -> [1.0, xi[1], xi[2], xi[1]*xi[2],
xi[1]^2, xi[2]^2, xi[1]^2*xi[2], xi[1]*xi[2]^2])
# 3d Lagrange elements
@create_lagrange_element(Hex8, "8 node hexahedra",
[-1.0 1.0 1.0 -1.0 -1.0 1.0 1.0 -1.0
-1.0 -1.0 1.0 1.0 -1.0 -1.0 1.0 1.0
-1.0 -1.0 -1.0 -1.0 1.0 1.0 1.0 1.0],
(xi) -> [1.0, xi[1], xi[2], xi[1]*xi[2], xi[3], xi[1]*xi[3], xi[2]*xi[3], xi[1]*xi[2]*xi[3]])
(xi) -> [1.0, xi[1], xi[2], xi[1]*xi[2], xi[3],
xi[1]*xi[3], xi[2]*xi[3], xi[1]*xi[2]*xi[3]])
#=
@create_lagrange_element(Hex20, "20 node hexahedra",
[
-1.0 1.0 1.0 -1.0 -1.0 1.0 1.0 -1.0 0.0 1.0 0.0 -1.0 -1.0 1.0 1.0 -1.0 0.0 1.0 0.0 -1.0
-1.0 -1.0 1.0 1.0 -1.0 -1.0 1.0 1.0 -1.0 0.0 1.0 0.0 -1.0 -1.0 1.0 1.0 -1.0 0.0 1.0 0.0
-1.0 -1.0 -1.0 -1.0 1.0 1.0 1.0 1.0 -1.0 -1.0 -1.0 -1.0 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0
],
(xi) -> [1.0, xi[1], xi[2], xi[1]*xi[2], xi[3], xi[1]*xi[3], xi[2]*xi[3], xi[1]*xi[2]*xi[3]
x[1]^2,
])
=#
@create_lagrange_element(Tet4, "4 node tetrahedron",
[0.0 1.0 0.0 0.0
+5 -9
View File
@@ -309,21 +309,17 @@ function get_element_sets(med::MEDFile, mesh_name)
return es
end
# hex8 nodes rotating cw first in yz plane then x+1
global const med_elmap = Dict{Symbol, Vector{Int}}(
:PO1 => [1],
:SE2 => [1, 2],
:SE3 => [1, 2, 3],
:TR3 => [1, 2, 3],
:TR6 => [1, 2, 3, 4, 5, 6],
:QU4 => [1, 2, 3, 4],
:HE8 => [4, 8, 7, 3, 1, 5, 6, 2], # ..?
:TE4 => [3, 2, 1, 4],
:T10 => [3, 2, 1, 4, 6, 5, 7, 10, 9, 8],
:PO1 => [1]
# :T10 => [3, 4, 1, 2, 10, 8, 7, 6, 9, 5]
# :T10 => [5, 9, 6, 7, 8, 10, 2, 1, 4, 3]
)
:TR6 => [1, 2, 3, 4, 5, 6],
:QU8 => [1, 2, 3, 4, 5, 6, 7, 8],
:HE8 => [4, 8, 7, 3, 1, 5, 6, 2], # ..?
:T10 => [3, 2, 1, 4, 6, 5, 7, 10, 9, 8])
function get_connectivity(med::MEDFile, elsets, mesh_name)
elsets[0] = :OTHER
+7 -3
View File
@@ -145,7 +145,7 @@ end
take last known value and set it as a initial quess for next
time increment.
"""
function initialize!(problem::Problem, time::Real)
function initialize!(problem::Problem, time=0.0)
field_name = get_unknown_field_name(problem)
field_dim = get_unknown_field_dimension(problem)
for element in get_elements(problem)
@@ -289,8 +289,12 @@ function get_parent_field_name{P<:BoundaryProblem}(problem::Problem{P})
return problem.parent_field_name
end
function push!(problem::Problem, element)
push!(problem.elements, element)
function push!(problem::Problem, elements...)
push!(problem.elements, elements...)
end
function push!(problem::Problem, elements::Vector)
push!(problem.elements, elements...)
end
function get_gdofs(element::Element, dim::Int)
+6 -1
View File
@@ -511,13 +511,18 @@ function call(solver::Solver{Linear}; F=nothing, show_info=true, return_factoriz
end
""" Convenience function to call linear solver. """
function LinearSolver(problems...)
function LinearSolver(problems::Problem...)
solver = Solver(Linear, "default linear solver")
if length(problems) != 0
push!(solver, problems...)
end
return solver
end
function LinearSolver(name::ASCIIString, problems::Problem...)
solver = LinearSolver(problems...)
solver.name = name
return solver
end
### End of linear quasistatic solver
+8
View File
@@ -100,3 +100,11 @@ end
@test isapprox(fb, 1.0)
end
@testset "add two time dependent fields to element at once" begin
el = Element(Seg2, [1, 2])
update!(el, "foo1", 1.0 => 1.0)
update!(el, "foo1", 2.0 => 2.0)
update!(el, "foo2", 1.0 => 1.0, 2.0 => 2.0)
@test isapprox(el("foo1", 1.5), el("foo2", 1.5))
end
+87 -4
View File
@@ -3,7 +3,7 @@
using JuliaFEM
using JuliaFEM.Test
using JuliaFEM.Preprocess
@testset "test one element heat problem" begin
@@ -19,7 +19,6 @@ using JuliaFEM.Test
update!(el1, "geometry", X)
update!(el1, "temperature thermal conductivity", 6.0)
update!(el1, "temperature load", 12.0)
update!(el1, "density", 36.0)
# define boundary element for flux
el2 = Element(Seg2, [1, 2])
@@ -29,6 +28,7 @@ using JuliaFEM.Test
# define heat problem and push elements to problem
problem = Problem(Heat, "one element heat problem", 1)
problem.properties.formulation = "2D"
push!(problem, el1, el2)
# define boundary element for dirichlet boundary condition
@@ -53,8 +53,7 @@ using JuliaFEM.Test
@test isapprox(A[free_dofs, free_dofs] \ b[free_dofs], [1.0, 1.0])
# using Solver
solver = Solver("solve heat problem")
solver.is_linear_system = true
solver = LinearSolver("solve heat problem")
push!(solver, problem, boundary_condition)
# Set constant source f=12 with k=6. Accurate solution is
@@ -75,3 +74,87 @@ using JuliaFEM.Test
@test isapprox(T[1], 2.0)
end
function T_acc(x)
# accurate solution
a = 0.01
L = 0.20
k = 50.0
Tᵤ = 20.0
h = 10.0
P = 4*a
A = a^2
α = h
β = sqrt((h*P)/(k*A))
= 100.0
C = [1.0 1.0; (α+k*β)*exp(β*L) (α-k*β)*exp(-β*L)] \ [-Tᵤ, 0.0]
return dot(C, [exp(β*x), exp(-β*x)]) + Tᵤ
end
#=
@testset "test 1d heat problem" begin
X = Dict{Int, Vector{Float64}}(
1 => [0.0, 0.0, 0.0],
2 => [0.1, 0.0, 0.0],
3 => [0.2, 0.0, 0.0])
e1 = Element(Seg2, [1, 2])
e2 = Element(Seg2, [2, 3])
e3 = Element(Poi1, [3])
p1 = Problem(Heat, "1d heat problem", 1)
p1.properties.formulation = "1D"
push!(p1, e1, e2, e3)
update!(p1, "geometry", X)
a = 0.010
update!(p1, "cross-section area", a^2)
update!(p1, "cross-section perimeter", 4*a)
update!(p1, "temperature thermal conductivity", 50.0) # k [W/(mC)]
update!(p1, "temperature heat transfer coefficient", 10.0) # h [W/(m²C)]
update!(p1, "temperature external temperature", 20.0)
p2 = Problem(Dirichlet, "left boundary", 1, "temperature")
e3 = Element(Poi1, [1])
update!(e3, "geometry", X)
update!(e3, "temperature 1", 100.0)
push!(p2, e3)
solver = LinearSolver(p1, p2)
call(solver)
T_min = minimum(p1.assembly.u)
@test isapprox(T_max, T_acc(0.2); rtol=4.5e-2)
end
=#
@testset "test 3d heat problem" begin
fn = Pkg.dir("JuliaFEM") * "/test/testdata/rod_short.med"
mesh = aster_read_mesh(fn, "SHORT_ROD_RECTANGLE_HEX8")
p1 = Problem(Heat, "rod", 1)
push!(p1, create_elements(mesh, "ROD"))
push!(p1, create_elements(mesh, "SIDES"))
push!(p1, create_elements(mesh, "RIGHT"))
update!(p1, "temperature thermal conductivity", 50.0)
update!(p1, "temperature external temperature", 20.0)
update!(p1, "temperature heat transfer coefficient", 10.0)
p2 = Problem(Dirichlet, "left support T=100", 1, "temperature")
push!(p2, create_elements(mesh, "LEFT"))
update!(p2, "temperature 1", 100.0)
solver = LinearSolver(p1, p2)
call(solver)
T_min = minimum(p1.assembly.u)
# Code Aster solution
T_CA_HEX20 = 4.58158267950429E+01
T_CA_HEX8 = 3.77215189873436E+01
info("T_min = $T_min")
info("T_acc = $(T_acc(0.2))")
rtol1 = norm(T_min-T_CA_HEX8)/max(T_min,T_CA_HEX8)*100.0
rtol2 = norm(T_min-T_acc(0.2))/max(T_min,T_acc(0.2))*100.0
info("rel. tol to CA solution: $rtol1 %")
info("rel. tol to accurate solution: $rtol2 %")
@test isapprox(T_min, T_acc(0.2); rtol=18.0e-2)
@test isapprox(T_min, T_CA_HEX8; rtol=1.0e-9)
end
+28
View File
@@ -0,0 +1,28 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using JuliaFEM
using JuliaFEM.Test
@testset "test initialize field problem" begin
el = Element(Seg2, [1, 2])
pr = Problem(Heat, 1)
push!(pr, el)
initialize!(pr)
@test haskey(el, "temperature")
# one timestep in field "temperature"
@test length(el("temperature")) == 1
@test length(el["temperature"]) == 1
# length of single increment
@test length(el("temperature", 0.0)) == 2
@test length(last(el, "temperature").data) == 2
end
@testset "test initialize boundary problem" begin
el = Element(Seg2, [1, 2])
pr = Problem(Dirichlet, "bc", 1, "temperature")
push!(pr, el)
initialize!(pr)
@test haskey(el, "reaction force")
@test haskey(el, "temperature")
end