Files
JuliaFEM.jl/test/interface/test_volume_interface_coupling.jl

184 lines
6.4 KiB
Julia
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using JuliaFEM
using LinearAlgebra: norm
using Test
"""Return local vertex index `i` with `conn[i] == g` (1-based)."""
function _hex8_local_vertex(conn::NTuple{8, UInt32}, g::UInt32)
for i in 1:8
@inbounds conn[i] == g && return i
end
error("global node $g not found in connectivity $conn")
end
"""Reference-coordinate midpoint of the linear hex edge joining two vertices."""
function _hex8_edge_midpoint_ref(conn::NTuple{8, UInt32}, g1::UInt32, g2::UInt32)
R = reference_coordinates(Hex8())
i1 = _hex8_local_vertex(conn, g1)
i2 = _hex8_local_vertex(conn, g2)
return Vec{3, Float64}(0.5 * (R[i1] + R[i2]))
end
"""Global node ids on hex face `lf` (1-based), cyclic order from topology."""
function _hex8_face_global_nodes(conn::NTuple{8, UInt32}, lf::Int)
F = faces(Hex8())[lf]
return ntuple(i -> conn[F.vertices[i]], length(F.vertices))
end
"""Two-element brick sharing an interior face (nx = 2)."""
function _two_hex_brick_mesh()
return create_structured_box_mesh(Hex8;
xmin = 0.0, xmax = 1.0, nx = 2,
ymin = 0.0, ymax = 1.0, ny = 1,
zmin = 0.0, zmax = 1.0, nz = 1,
)
end
"""Find local face indices on elements 1 and 2 that share the same global mesh face."""
function _shared_hex8_face_pair(maps::Hex8FacetMaps)
lf1 = lf2 = 0
for a in 1:6, b in 1:6
if maps.elem_face_gid[a, 1] == maps.elem_face_gid[b, 2]
lf1, lf2 = a, b
break
end
end
lf1 == 0 && error("expected conforming shared face between elements 1 and 2")
return lf1, lf2
end
@testset "volumeinterface coupling (1) geometry & facet topology" begin
mesh = _two_hex_brick_mesh()
@test nelements(mesh) == 2
maps = build_hex8_facet_maps(mesh)
lf1, lf2 = _shared_hex8_face_pair(maps)
@test maps.elem_face_gid[lf1, 1] == maps.elem_face_gid[lf2, 2]
conn1 = mesh.connectivity[1]
conn2 = mesh.connectivity[2]
face1 = Set(collect(_hex8_face_global_nodes(conn1, lf1)))
face2 = Set(collect(_hex8_face_global_nodes(conn2, lf2)))
@test face1 == face2
face_cycle = collect(_hex8_face_global_nodes(conn1, lf1))
nfv = length(face_cycle)
seg_pairs = Tuple{UInt32, UInt32}[]
for k in 1:nfv
a = face_cycle[k]
b = face_cycle[mod1(k + 1, nfv)]
push!(seg_pairs, (UInt32(a), UInt32(b)))
end
iface_nodes = [mesh.nodes[Int(i)] for i in face_cycle]
iface_conn = [(UInt32(k), UInt32(mod1(k + 1, nfv))) for k in 1:nfv]
coup = [
InterfaceVolumeCoupling(UInt32(1), UInt32(1), UInt8(lf1), UInt32(1), UInt32(2), UInt8(lf2))
for _ in 1:nfv
]
im = InterfaceMesh(Seg2, iface_nodes, iface_conn, coup)
vol_gid = UInt32.(face_cycle)
for i in 1:interface_nnodes(im)
@test im.nodes[i] mesh.nodes[Int(vol_gid[i])]
end
for (s, (ga, gb)) in enumerate(seg_pairs)
@test ga face1 && gb face1
la = findfirst(==(ga), vol_gid)
lb = findfirst(==(gb), vol_gid)
@test im.nodes[la] mesh.nodes[Int(ga)]
@test im.nodes[lb] mesh.nodes[Int(gb)]
c = im.connectivity[s]
@test sort(UInt32[vol_gid[Int(c[1])], vol_gid[Int(c[2])]]) == sort(UInt32[ga, gb])
end
end
@testset "volumeinterface coupling (2) scalar restriction along shared face" begin
mesh = _two_hex_brick_mesh()
maps = build_hex8_facet_maps(mesh)
lf1, lf2 = _shared_hex8_face_pair(maps)
conn1 = mesh.connectivity[1]
face_cycle = collect(_hex8_face_global_nodes(conn1, lf1))
nfv = length(face_cycle)
iface_nodes = [mesh.nodes[Int(i)] for i in face_cycle]
iface_conn = [(UInt32(k), UInt32(mod1(k + 1, nfv))) for k in 1:nfv]
coup = [
InterfaceVolumeCoupling(UInt32(1), UInt32(1), UInt8(lf1), UInt32(1), UInt32(2), UInt8(lf2))
for _ in 1:nfv
]
im = InterfaceMesh(Seg2, iface_nodes, iface_conn, coup)
S = @DOFSet{T::DOF{Float64, Vertex}}
vol_elements, vol_h = create_elements!(mesh, Element{Hex8, Lagrange{1}, S})
_, iface_h = create_interface_elements!(im, Element{Seg2, Lagrange{1}, S})
u_vol = zeros(Float64, vol_h.total_dofs)
for n in 1:nnodes_total(mesh)
g = vol_h.field_starts[1][n]
u_vol[g] = mesh.nodes[n][1]
end
vol_gid = UInt32.(face_cycle)
u_iface = zeros(Float64, iface_h.total_dofs)
for i in 1:interface_nnodes(im)
v = vol_gid[i]
u_iface[iface_h.field_starts[1][i]] = u_vol[vol_h.field_starts[1][Int(v)]]
end
for i in 1:interface_nnodes(im)
@test u_iface[iface_h.field_starts[1][i]] im.nodes[i][1]
end
end
@testset "volumeinterface coupling (3) segment midpoint jump & mortar-style residual" begin
mesh = _two_hex_brick_mesh()
maps = build_hex8_facet_maps(mesh)
lf1, lf2 = _shared_hex8_face_pair(maps)
conn1 = mesh.connectivity[1]
conn2 = mesh.connectivity[2]
face_cycle = collect(_hex8_face_global_nodes(conn1, lf1))
nfv = length(face_cycle)
iface_nodes = [mesh.nodes[Int(i)] for i in face_cycle]
iface_conn = [(UInt32(k), UInt32(mod1(k + 1, nfv))) for k in 1:nfv]
coup = [
InterfaceVolumeCoupling(UInt32(1), UInt32(1), UInt8(lf1), UInt32(1), UInt32(2), UInt8(lf2))
for _ in 1:nfv
]
im = InterfaceMesh(Seg2, iface_nodes, iface_conn, coup)
S = @DOFSet{T::DOF{Float64, Vertex}}
vol_elements, vol_h = create_elements!(mesh, Element{Hex8, Lagrange{1}, S})
u_vol = zeros(Float64, vol_h.total_dofs)
for n in 1:nnodes_total(mesh)
u_vol[vol_h.field_starts[1][n]] = mesh.nodes[n][1]
end
penalty = 0.0
mortar_sum = 0.0
for s in 1:interface_nelements(im)
g1 = UInt32(face_cycle[s])
g2 = UInt32(face_cycle[mod1(s + 1, nfv)])
ξ1 = _hex8_edge_midpoint_ref(conn1, g1, g2)
ξ2 = _hex8_edge_midpoint_ref(conn2, g1, g2)
u_slave = interpolate_field_value(vol_elements[1], u_vol, :T, ξ1)
u_master = interpolate_field_value(vol_elements[2], u_vol, :T, ξ2)
@test u_slave u_master
L = norm(im.nodes[s] - im.nodes[mod1(s + 1, nfv)])
umid_line = 0.5 * (u_vol[vol_h.field_starts[1][Int(g1)]] + u_vol[vol_h.field_starts[1][Int(g2)]])
@test umid_line u_slave
penalty += (u_slave - u_master)^2 * L
λ = 1.0
mortar_sum += λ * L * (u_slave - u_master)
end
@test penalty 0.0 atol = 1e-13 rtol = 1e-13
@test mortar_sum 0.0 atol = 1e-13 rtol = 1e-13
end