Calculate shape functions using FEMBasis.jl

A lot of code is moved to FEMBasis.jl regarding
calculating basis / shape functions of finite elements.

* add FEMBasis to REQUIRE
* remove obsolete files
* remove obsolete test files
* make integration point iterable
* loosen type definitions
* get length of element rather from basis than connectivity
* calculate midpoint of reference element
* wrong input argument to eval_basis! fixed
This commit is contained in:
Jukka Aho
2017-08-05 11:33:43 +03:00
parent dcd24e8e01
commit fffb0071a0
16 changed files with 85 additions and 1231 deletions
-287
View File
@@ -1,287 +0,0 @@
# 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.Testing
importall Base
import JuliaFEM: get_basis, get_dbasis
type TestElement <: AbstractElement
end
function get_basis(element::Element{TestElement}, xi, time)
1/4*[
(1-xi[1])*(1-xi[2])
(1+xi[1])*(1-xi[2])
(1+xi[1])*(1+xi[2])
(1-xi[1])*(1+xi[2])]'
end
function get_dbasis(element::Element{TestElement}, xi, time)
1/4*[
-(1-xi[2]) (1-xi[2]) (1+xi[2]) -(1+xi[2])
-(1-xi[1]) -(1+xi[1]) (1+xi[1]) (1-xi[1])]
end
function length(element::Element{TestElement})
return 4
end
function size(element::Element{TestElement})
return (2, 4)
end
function get_element()
element = Element(TestElement, [1, 2, 3, 4])
X = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [1.0, 0.0],
3 => [1.0, 1.0],
4 => [0.0, 1.0])
T = Dict{Int64, Float64}(
1 => 1.0,
2 => 2.0,
3 => 3.0,
4 => 4.0)
u1 = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [0.0, 0.0],
3 => [1/4, 0.0],
4 => [0.0, 0.0])
u2 = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [1.0, -1.0],
3 => [2.0, 3.0],
4 => [0.0, 0.0])
update!(element, "geometry", X)
update!(element, "temperature", T)
update!(element, "displacement1", u1)
update!(element, "displacement2", u2)
return element
end
@testset "spatial interpolation in basis" begin
element = get_element()
@test isapprox(element([0.0, 0.0], 0.0), 1/4*[1 1 1 1])
@test isapprox(element([0.0, 0.0], 1.0), 1/4*[1 1 1 1])
end
@testset "gradient of shape functions" begin
element = get_element()
grad = element([0.0, 0.0], 0.0, Val{:Grad})
@test isapprox(grad, 1/2*[-1 1 1 -1; -1 -1 1 1])
end
@testset "interpolation of scalar field in spatial domain" begin
# in unit square: T(X,t) = t*(1 + X[1] + 3*X[2] - 2*X[1]*X[2])
element = get_element()
T_known(X) = 1 + X[1] + 3*X[2] - 2*X[1]*X[2]
T_interpolated = element("temperature", [0.0, 0.0], 0.0)
@test isapprox(T_interpolated, T_known([0.5, 0.5]))
end
@testset "interpolation of gradient of scalar field in spatial domain" begin
# in unit square: grad(T)(X) = [1-2X[2], 3-2*X[1]]
element = get_element()
gradT = element("temperature", [0.0, 0.0], 0.0, Val{:Grad})
gradT_expected(X) = [1-2*X[2] 3-2*X[1]]
@test isapprox(gradT, gradT_expected([0.5, 0.5]))
end
@testset "test interpolation of vector field" begin
# in unit square, u(X,t) = [1/4*t*X[1]*X[2], 0, 0]
element = get_element()
u = element("displacement1", [0.0, 0.0], 0.0)
# x = X+u
u_expected(X) = [1/4*X[1]*X[2], 0]
# @test isapprox(x, [9/16, 1/2])
@test isapprox(u, u_expected([0.5, 0.5]))
end
@testset "interpolation of gradient of vector_field" begin
# in unit square, u(X) = t*[X[1]*(X[2]+1), X[1]*(4*X[2]-1)]
# => u_i,j = t*[X[2]+1 X[1]; 4*X[2]-1 4*X[1]]
element = get_element()
# displacement = Field(
# (0.5, Vector[[0.0, 0.0], [0.5, -0.5], [1.0, 1.5], [0.0, 0.0]]),
# (1.5, Vector[[0.0, 0.0], [1.5, -1.5], [3.0, 4.5], [0.0, 0.0]]))
gradu = element("displacement2", [0.0, 0.0], 0.0, Val{:Grad})
gradu_expected(X) = [X[2]+1 X[1]; 4*X[2]-1 4*X[1]]
@test isapprox(gradu, gradu_expected([0.5, 0.5]))
end
#= TODO: Fix test
@testset "linear time extrapolation of field" begin
#T_known(X,t) = t*(1 + X[1] + 3*X[2] - 2*X[1]*X[2])
T = DVTV()
update!(T, 0.0 => [0.0, 0.0, 0.0, 0.0])
update!(T, 1.0 => [1.0, 2.0, 3.0, 4.0])
@test T(-1.0) == -1.0*[1.0, 2.0, 3.0, 4.0]
@test T( 3.0) == 3.0*[1.0, 2.0, 3.0, 4.0]
# when going to \pm infinity, return the last one.
@test T(-Inf) == 0.0*[1.0, 2.0, 3.0, 4.0]
@test T(+Inf) == 1.0*[1.0, 2.0, 3.0, 4.0]
end
=#
#= TODO: Fix test
@testset "constant time extrapolation of field" begin
#T_known(X,t) = t*(1 + X[1] + 3*X[2] - 2*X[1]*X[2])
T = DVTV()
update!(T, 0.0 => [0.0, 0.0, 0.0, 0.0])
update!(T, 1.0 => [1.0, 2.0, 3.0, 4.0])
@test isapprox(T(-1.0, Val{:constant}), [0.0, 0.0, 0.0, 0.0])
@test isapprox(T( 3.0, Val{:constant}), [1.0, 2.0, 3.0, 4.0])
end
=#
#= TODO: Fix test
@testset "time extrapolation of field with only one timestep" begin
T = DVTV()
update!(T, 0.0 => [1.0, 2.0, 3.0, 4.0])
@test isapprox(T(1.0), [1.0, 2.0, 3.0, 4.0])
end
=#
#= TODO: Fix test
@testset "interpolation in temporal direction" begin
field = DCTV()
update!(field, 0.0 => 0.0)
update!(field, 2.0 => 1.0)
update!(field, 4.0 => 2.0)
@test isapprox(field(-Inf), 0.0)
@test isapprox(field( 0.0), 0.0)
@test isapprox(field( 1.0), 0.5)
@test isapprox(field( 2.0), 1.0)
@test isapprox(field( 3.0), 1.5)
@test isapprox(field( 4.0), 2.0)
@test isapprox(field(+Inf), 2.0)
end
=#
#= TODO: Fix test
@testset "time derivative interpolation in temporal basis in constant velocity" begin
field = DCTV()
update!(field, 0.0 => 0.0)
update!(field, 2.0 => 1.0)
update!(field, 4.0 => 2.0)
@test isapprox(field(+Inf, Val{:diff}), 0.5)
@test isapprox(field(-Inf, Val{:diff}), 0.5)
@test isapprox(field( 0.0, Val{:diff}), 0.5)
@test isapprox(field( 0.5, Val{:diff}), 0.5)
@test isapprox(field( 1.0, Val{:diff}), 0.5)
@test isapprox(field( 1.5, Val{:diff}), 0.5)
@test isapprox(field( 2.0, Val{:diff}), 0.5)
end
=#
#= TODO: Fix test
@testset "time derivative interpolation in temporal basis in variable velocity" begin
pos = DCTV()
for ti in linspace(0, 2, 5)
update!(pos, ti => 1/2*ti^2)
end
# => ((0.0,0.0),(0.5,0.125),(1.0,0.5),(1.5,1.125),(2.0,2.0))
velocity = pos(1.0, Val{:diff})
v1 = (0.500 - 0.125)/0.5
v2 = (1.125 - 0.500)/0.5
@test isapprox(velocity, mean([v1, v2])) # = 1.00
velocity = pos(2.0, Val{:diff})
@test isapprox(velocity, (2.0-1.125)/0.5) # = 1.75
end
=#
function test_time_derivative_gradient_interpolation_of_field()
# in unit square, u(X) = t*[X[1]*(X[2]+1), X[1]*(4*X[2]-1)]
# => u_i,j = t*[X[2]+1 X[1]; 4*X[2]-1 4*X[1]]
# => d(u_i,j)/dt = [X[2]+1 X[1]; 4*X[2]-1 4*X[1]]
X = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [1.0, 0.0],
3 => [1.0, 1.0],
4 => [0.0, 1.0])
u1 = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [0.5, -0.5],
3 => [1.0, 1.5],
4 => [0.0, 0.0])
u2 = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [1.5, -1.5],
3 => [3.0, 4.5],
4 => [0.0, 0.0])
element = Element(TestElement, [1, 2, 3, 4])
update!(element, "geometry", X)
update!(element, "displacement", 0.5 => u1)
update!(element, "displacement", 1.5 => u2)
xi = [0.0, 0.0]
time = 1.2
diffgradu = element("displacement", xi, time, Val{:diff}, Val{:Grad})
diffgradu_expected(X, t) = [X[2]+1 X[1]; 4*X[2]-1 4*X[1]]
@test diffgradu == diffgradu_expected([0.5, 0.5], 1.2)
end
@testset "some continuum mechanics interpolations" begin
X = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [1.0, 0.0],
3 => [1.0, 1.0],
4 => [0.0, 1.0])
u1 = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [0.0, 0.0],
3 => [0.0, 0.0],
4 => [0.0, 0.0])
u2 = Dict{Int64, Vector{Float64}}(
1 => [0.0, 0.0],
2 => [0.0, 0.0],
3 => [1/4, 0.0],
4 => [0.0, 0.0])
element = Element(Quad4, [1, 2, 3, 4])
update!(element, "geometry", X)
update!(element, "displacement", 0.0 => u1)
update!(element, "displacement", 1.0 => u2)
# from my old home works
X = element("geometry", [0.0, 0.0], 1.0)
u = element("displacement", [0.0, 0.0], 1.0)
x = X + u
x_expected = [9/16, 1/2]
gradu = element("displacement", [0.0, 0.0], 1.0, Val{:Grad})
epsilon = 1/2*(gradu + gradu')
rotation = 1/2*(gradu - gradu')
k = 0.25
epsilon_expected = [
X[2]*k 1/2*X[1]*k
1/2*X[1]*k 0]
rotation_expected = [
0 k/2*X[1]
-k/2*X[1] 0]
F = I + gradu
F_expected = [
X[2]*k+1 X[1]*k
0 1]
C = F'*F
C_expected = [
(X[2]*k+1)^2 (X[2]*k+1)*X[1]*k
(X[2]*k+1)*X[1]*k X[1]^2*k^2+1]
E = 1/2*(F'*F - I)
E_expected = [
1/2*(X[2]*k + 1)^2-1/2 1/2*(X[2]*k+1)*X[1]*k
1/2*(X[2]*k + 1)*X[1]*k 1/2*X[1]^2*k^2]
U = 1/sqrt(trace(C) + 2*sqrt(det(C)))*(C + sqrt(det(C))*I)
# U_expected = [1.24235 0.13804; 0.13804 1.02149]
@test isapprox(x, x_expected)
@test isapprox(epsilon, epsilon_expected)
@test isapprox(rotation, rotation_expected)
@test isapprox(F, F_expected)
@test isapprox(C, C_expected)
@test isapprox(E, E_expected)
# TODO: Fix test
# @test isapprox(U, U_expected)
end
-65
View File
@@ -1,65 +0,0 @@
# 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.Testing
importall Base
import JuliaFEM: get_basis, get_dbasis, get_integration_points
type MyQuad4 <: AbstractElement
end
function get_basis(element::Element{MyQuad4}, ip, time)
1/4*[(1-ip[1])*(1-ip[2]) (1+ip[1])*(1-ip[2]) (1+ip[1])*(1+ip[2]) (1-ip[1])*(1+ip[2])]
end
function get_dbasis(element::Element{MyQuad4}, ip, time)
1/4*[-(1-ip[2]) (1-ip[2]) (1+ip[2]) -(1+ip[2])
-(1-ip[1]) -(1+ip[1]) (1+ip[1]) (1-ip[1])]
end
function get_integration_points(element::MyQuad4)
[
(1.0, 1.0/sqrt(3.0)*[-1, -1]),
(1.0, 1.0/sqrt(3.0)*[ 1, -1]),
(1.0, 1.0/sqrt(3.0)*[ 1, 1]),
(1.0, 1.0/sqrt(3.0)*[-1, 1])
]
end
function length(element::Element{MyQuad4})
return 4
end
function size(element::Element{MyQuad4})
return (2, 4)
end
@testset "test new element" begin
el = Element(MyQuad4, Int[])
el["geometry"] = Vector{Float64}[[0.0,0.0], [1.0,0.0], [1.0,1.0], [0.0,1.0]]
el["displacement"] = Vector{Float64}[[0.0,0.0], [0.0,0.0], [1.0,0.0], [0.0,0.0]]
@test isapprox(el("geometry", [0.0, 0.0], 0.0), [0.5, 0.5])
@test isapprox(el("displacement", [0.0, 0.0], 0.0), [0.25, 0.0])
el["temperature thermal conductivity"] = 6.0
dim = length(el)
K = zeros(dim, dim)
A = 0.0
time = 0.0
for ip in get_integration_points(el)
dN = el(ip, time, Val{:Grad})
detJ = el(ip, time, Val{:detJ})
w = ip.weight*detJ
c = el("temperature thermal conductivity", ip, time)
K += w*c*dN'*dN
A += w
end
@test isapprox(A, 1.0)
K_expected = [
4.0 -1.0 -2.0 -1.0
-1.0 4.0 -1.0 -2.0
-2.0 -1.0 4.0 -1.0
-1.0 -2.0 -1.0 4.0]
@test isapprox(K, K_expected)
end
-28
View File
@@ -1,28 +0,0 @@
# 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.Testing
@testset "NSeg interpolate" begin
element = Element(NSeg, [1, 2])
@test element([0.0], 0.0) == [0.5 0.5]
@test size(element) == (1, 2)
@test is_nurbs(element)
element2 = Element(Seg2, [1, 2])
@test !is_nurbs(element2)
end
@testset "NSurf interpolate" begin
element = Element(NSurf, [1, 2, 3, 4])
@test element([0.0, 0.0], 0.0) == [0.25 0.25 0.25 0.25]
@test size(element) == (2, 4)
@test is_nurbs(element)
end
@testset "NSolid interpolate" begin
element = Element(NSolid, [1, 2, 3, 4, 5, 6, 7, 8])
@test element([0.0, 0.0, 0.0], 0.0) == [0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125]
@test size(element) == (3, 8)
@test is_nurbs(element)
end
-89
View File
@@ -1,89 +0,0 @@
# 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.Testing
ALL_ELEMENTS = [
Seg2, Seg3,
Tri3, Tri6, Tri7,
Quad4, Quad8, Quad9,
Tet4, Tet10,
Wedge6,
Hex8, Hex20, Hex27
]
info("basic data for elements implemented so far:")
for element_type in [Poi1; ALL_ELEMENTS]
element = Element(element_type, Int[])
element_length = length(element)
element_size = size(element)
element_description = description(element)
info("Element $element_type, description = $element_description, length = $element_length, size = $element_size")
end
ALL_ELEMENTS_NODES = [
[1,2], [1,2,3],
[1,2,3], [1,2,3,4,5,6], [1,2,3,4,5,6,7],
[1,2,3,4], [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8,9],
[1,2,3,4], [1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6],
[1,2,3,4,5,6,7,8],
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15,17,18,19,20],
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15,17,18,19,20,
21,22,23,24,25,26,27]
]
@testset "Evaluating basis" begin
for (T, nod) in zip(ALL_ELEMENTS,ALL_ELEMENTS_NODES)
el = Element(T,nod)
nnodes = length(el)
for (i, X) in enumerate(get_reference_coordinates(T))
Ni = vec(el(X))
expected = zeros(nnodes)
expected[i] = 1.0
@test isapprox(Ni, expected)
end
end
end
function get_volume{T<:AbstractElement}(::Type{T},nodes)
X = get_reference_coordinates(T)
element = Element(T,nodes)
update!(element, "geometry", X)
V = 0.0
for ip in get_integration_points(element)
V += ip.weight*element(ip, 0.0, Val{:detJ})
end
return V
end
RESULTS = [2.0, 2.0, 0.5, 0.5, 0.5, 2.0^2, 2.0^2,
2.0^2, 1/6, 1/6, 1.0, 2.0^3, 2.0^3, 2.0^3,]
@testset "Calculate reference element length/area/volume" begin
for (T, nod, res) in zip(ALL_ELEMENTS,ALL_ELEMENTS_NODES,
RESULTS)
@test isapprox(get_volume(T,nod), res)
end
end
SIZES = [(1,2), (1,3), (2,3), (2,6), (2,7), (2,4),
(2,8), (2,9), (3,4), (3,10), (3,6), (3,8),
(3,20), (3,27)]
@testset "element size" begin
for (T, nod, res) in zip(ALL_ELEMENTS,ALL_ELEMENTS_NODES, SIZES)
@test size(Element(T,nod)) == res
end
end
@testset "element length" begin
for i in 1:length(ALL_ELEMENTS)
typ = ALL_ELEMENTS[i]
vec = ALL_ELEMENTS_NODES[i]
el = Element(typ,vec)
@test length(el) == length(vec)
end
end
+1 -1
View File
@@ -41,7 +41,7 @@ using JuliaFEM.Testing
# Postprocess.
# Interpolate temperature field along boundary of Γ₁ at time t=1.0
xi = [0.0, -1.0]
xi = (0.0, )
X = el2("geometry", xi, 1.0)
T = el2("temperature", xi, 1.0)
info("Temperature at point X = $X is T = $T")