Files
JuliaFEM.jl/src/problems_mortar_3d.jl
T

878 lines
30 KiB
Julia
Raw Normal View History

# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
2016-07-03 23:40:12 +03:00
typealias MortarElements3D Union{Tri3, Tri6, Quad4}
2016-07-03 23:40:12 +03:00
function project_vertex_to_auxiliary_plane(p::Vector, x0::Vector, n0::Vector)
return p - dot(p-x0, n0)*n0
end
2016-07-03 23:40:12 +03:00
function inv3(P::Matrix)
n, m = size(P)
@assert n == m == 3
a, b, c, d, e, f, g, h, i = P
A = e*i - f*h
B = -d*i + f*g
C = d*h - e*g
D = -b*i + c*h
E = a*i - c*g
F = -a*h + b*g
G = b*f - c*e
H = -a*f + c*d
I = a*e - b*d
return 1/(a*A + b*B + c*C)*[A B C; D E F; G H I]
end
2016-10-03 23:12:00 +03:00
function vertex_inside_polygon(q, P; atol=1.0e-3)
2016-07-03 23:40:12 +03:00
N = length(P)
angle = 0.0
for i=1:N
A = P[i] - q
B = P[mod(i,N)+1] - q
c = norm(A)*norm(B)
isapprox(c, 0.0; atol=atol) && return true
cosa = dot(A,B)/c
isapprox(cosa, 1.0; atol=atol) && return false
isapprox(cosa, -1.0; atol=atol) && return true
2017-01-30 12:28:33 +02:00
#try
angle += acos(cosa)
#catch
# info("Unable to calculate acos($(ForwardDiff.get_value(cosa))) when determining is a vertex inside polygon.")
# info("Polygon is: $(ForwardDiff.get_value(P)) and vertex under consideration is $(ForwardDiff.get_value(q))")
# info("Polygon corner point in loop: A=$(ForwardDiff.get_value(A)), B=$(ForwardDiff.get_value(B))")
# info("c = ||A||*||B|| = $(ForwardDiff.get_value(c))")
# rethrow()
#end
end
2016-07-03 23:40:12 +03:00
return isapprox(angle, 2*pi; atol=atol)
end
2016-07-03 23:40:12 +03:00
function calculate_centroid(P)
N = length(P)
P0 = P[1]
areas = [norm(1/2*cross(P[i]-P0, P[mod(i,N)+1]-P0)) for i=2:N]
centroids = [1/3*(P0+P[i]+P[mod(i,N)+1]) for i=2:N]
C = 1/sum(areas)*sum(areas.*centroids)
return C
end
2016-10-03 23:12:00 +03:00
function get_cells(P, C; allow_quads=false)
2016-07-03 23:40:12 +03:00
N = length(P)
cells = Vector[]
# shared edge etc.
N < 3 && return cells
2016-10-03 23:12:00 +03:00
# trivial cases, polygon already triangle / quadrangle
if N == 3
return Vector[P]
end
if N == 4 && allow_quads
return Vector[P]
end
2016-07-03 23:40:12 +03:00
cells = Vector[Vector[C, P[i], P[mod(i,N)+1]] for i=1:N]
return cells
end
""" Test does vector P contain approximately q. This function uses isapprox()
internally to make boolean test.
Examples
--------
julia> P = Vector[[1.0, 1.0], [2.0, 2.0]]
2-element Array{Array{T,1},1}:
[1.0,1.0]
[2.0,2.0]
julia> q = [1.0, 1.0] + eps(Float64)
2-element Array{Float64,1}:
1.0
1.0
julia> in(q, P)
false
julia> approx_in(q, P)
true
"""
function approx_in{T}(q::T, P::Vector{T}; rtol=1.0e-4, atol=0.0)
for p in P
if isapprox(q, p; rtol=rtol, atol=atol)
return true
end
end
return false
end
function get_polygon_clip(xs, xm, n)
2016-07-03 23:40:12 +03:00
# objective: search does line xm1 - xm2 clip xs
nm = length(xm)
ns = length(xs)
2016-10-03 23:12:00 +03:00
P = Vector[]
2016-07-03 23:40:12 +03:00
# 1. test is master point inside slave, if yes, add to clip
for i=1:nm
if vertex_inside_polygon(xm[i], xs)
2017-02-25 18:40:14 +02:00
# debug("1. $(xm[i]) inside S -> push")
2016-07-03 23:40:12 +03:00
push!(P, xm[i])
end
end
2016-07-03 23:40:12 +03:00
# 2. test is slave point inside master, if yes, add to clip
for i=1:ns
if vertex_inside_polygon(xs[i], xm)
approx_in(xs[i], P) && continue
2017-02-25 18:40:14 +02:00
# debug("2. $(xs[i]) inside M -> push")
2016-07-03 23:40:12 +03:00
push!(P, xs[i])
end
end
2016-07-03 23:40:12 +03:00
for i=1:nm
# 2. find possible intersection
xm1 = xm[i]
xm2 = xm[mod(i,nm)+1]
#info("intersecting line $xm1 -> $xm2")
for j=1:ns
xs1 = xs[j]
xs2 = xs[mod(j,ns)+1]
#info("clipping polygon edge $xs1 -> $xs2")
tnom = dot(cross(xm1-xs1, xm2-xm1), n)
tdenom = dot(cross(xs2-xs1, xm2-xm1), n)
isapprox(tdenom, 0) && continue
t = tnom/tdenom
(0 <= t <= 1) || continue
q = xs1 + t*(xs2 - xs1)
#info("t=$t, q=$q, q ∈ xm ? $(vertex_inside_polygon(q, xm))")
if vertex_inside_polygon(q, xm)
approx_in(q, P) && continue
2017-02-25 18:40:14 +02:00
# debug("3. $q inside M -> push")
2016-07-03 23:40:12 +03:00
push!(P, q)
end
end
end
2016-07-03 23:40:12 +03:00
return P
end
""" Project some vertex p to surface of element E using Newton's iterations. """
2016-07-03 23:40:12 +03:00
function project_vertex_to_surface{E}(p::Vector, x0::Vector, n0::Vector,
element::Element{E}, x::DVTI, time::Real;
max_iterations::Int=10, iter_tol::Float64=1.0e-6)
2016-07-03 23:40:12 +03:00
basis(xi) = get_basis(element, xi, time)
dbasis(xi) = get_dbasis(element, xi, time)
nnodes = length(element)
2016-07-03 23:40:12 +03:00
f(theta) = basis(theta[1:2])*x - theta[3]*n0 - p
L(theta) = inv3([dbasis(theta[1:2])*x -n0])
2016-02-25 10:44:55 +02:00
theta = zeros(3)
dtheta = zeros(3)
for i=1:max_iterations
invA = L(theta)
b = f(theta)
dtheta = invA * b
2016-07-03 23:40:12 +03:00
theta -= dtheta
if norm(dtheta) < iter_tol
return theta[1:2], theta[3]
end
end
2017-01-30 12:28:33 +02:00
#=
2016-02-25 10:44:55 +02:00
info("failed to project vertex from auxiliary plane back to surface")
info("element type: $E")
info("element connectivity: $(get_connectivity(element))")
2016-07-03 23:40:12 +03:00
info("auxiliary plane: x0 = $x0, n0 = $n0")
info("element geometry: $(x.data)")
info("vertex to project: $p")
info("parameter vector before giving up: $theta")
info("increment in parameter vector before giving up: $dtheta")
info("norm(dtheta) before giving up: $(norm(dtheta))")
info("f([0.0, 0.0, 0.0]) = $(f([0.0, 0.0, 0.0]))")
info("L([0.0, 0.0, 0.0]) = $(L([0.0, 0.0, 0.0]))")
info("iterations:")
2016-02-25 10:44:55 +02:00
theta = zeros(3)
dtheta = zeros(3)
for i=1:max_iterations
2016-07-03 23:40:12 +03:00
info("iter $i, theta = $theta")
info("f = $(f(theta))")
info("L = $(L(theta))")
dtheta = L(theta) * f(theta)
info("dtheta = $(dtheta)")
theta -= dtheta
end
2017-01-30 12:28:33 +02:00
=#
2016-12-14 18:12:25 +02:00
throw(error("project_point_to_surface: did not converge in $max_iterations iterations!"))
end
2016-07-03 23:40:12 +03:00
function calculate_normals(elements, time, ::Type{Val{2}}; rotate_normals=false)
normals = Dict{Int64, Vector{Float64}}()
for element in elements
conn = get_connectivity(element)
J = transpose(element([0.0, 0.0], time, Val{:Jacobian}))
normal = cross(J[:,1], J[:,2])
for nid in conn
if haskey(normals, nid)
normals[nid] += normal
else
normals[nid] = normal
end
end
end
2016-07-03 23:40:12 +03:00
# normalize to unit normal
S = collect(keys(normals))
for j in S
normals[j] /= norm(normals[j])
end
2016-07-03 23:40:12 +03:00
if rotate_normals
for j in S
normals[j] = -normals[j]
end
2016-07-03 23:40:12 +03:00
end
return normals
end
2017-02-25 18:40:14 +02:00
""" Given polygon P and normal direction n, check that polygon vertices are
ordered in counter clock wise direction with respect to surface normal and
sort if necessary. It is assumed that polygon is convex.
Examples
--------
Unit triangle, normal in z-direction:
julia> P = Vector[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]
3-element Array{Array{T,1},1}:
[0.0,0.0,0.0]
[0.0,1.0,0.0]
[1.0,0.0,0.0]
julia> n = [0.0, 0.0, 1.0]
3-element Array{Float64,1}:
0.0
0.0
1.0
julia> check_orientation!(P, n)
3-element Array{Array{T,1},1}:
[1.0,0.0,0.0]
[0.0,0.0,0.0]
[0.0,1.0,0.0]
"""
function check_orientation!(P, n)
2016-07-03 23:40:12 +03:00
C = mean(P)
np = length(P)
s = [dot(n, cross(P[i]-C, P[mod(i+1,np)+1]-C)) for i=1:np]
all(s .< 0) && return
2017-02-25 18:40:14 +02:00
# debug("polygon not in ccw order, fixing")
2016-07-03 23:40:12 +03:00
# project points to new orthogonal basis Q and sort there
t1 = (P[1]-C)/norm(P[1]-C)
t2 = cross(n, t1)
Q = [n t1 t2]
sort!(P, lt=(A, B) -> begin
A_proj = Q'*(A-C)
B_proj = Q'*(B-C)
a = atan2(A_proj[3], A_proj[2])
b = atan2(B_proj[3], B_proj[2])
return a > b
end)
end
function convert_to_linear_element{E}(element::Element{E})
return element
end
function convert_to_linear_element(element::Element{Tri6})
new_element = Element(Tri3, element.connectivity[1:3])
new_element.id = element.id
new_element.fields = element.fields
return new_element
end
function split_quadratic_element{E}(element::Element{E}, time::Float64)
2016-12-22 10:05:08 +02:00
return [element]
end
function split_quadratic_element(element::Element{Tri6}, time::Float64)
element_maps = Vector{Int}[[1,4,6], [4,5,6], [4,2,5], [6,5,3]]
new_elements = Element[]
connectivity = get_connectivity(element)
for elmap in element_maps
new_element = Element(Tri3, connectivity[elmap])
X = element("geometry", time)
update!(new_element, "geometry", time => X[elmap])
2017-01-30 12:28:33 +02:00
if haskey(element, "displacement")
u = element("displacement", time)
update!(new_element, "displacement", time => u[elmap])
end
2017-02-25 18:40:14 +02:00
if haskey(element, "normal")
n = element("normal", time)
update!(new_element, "normal", time => n[elmap])
end
push!(new_elements, new_element)
end
return new_elements
end
function split_quadratic_elements(elements::DVTI, time::Float64)
return DVTI(split_quadratic_elements(elements.data, time))
end
""" Split quadratic surface elements to linear elements. """
function split_quadratic_elements(elements::Vector, time::Float64)
new_elements = Element[]
for element in elements
for splitted_element in split_quadratic_element(element, time)
push!(new_elements, splitted_element)
end
end
n1 = length(elements)
n2 = length(new_elements)
2017-02-25 18:40:14 +02:00
if n1 != n2
info("Splitted $n1 elements to $n2 (linear) sub-elements")
end
return new_elements
end
2017-02-25 18:40:14 +02:00
""" Assemble linear surface element to problem.
Dual basis is constructed such that partially integrated slave segments are taken into account in a proper way.
Notes
-----
For full integrated slave element, coefficient matrix for Tri3 is
Ae = [3.0 -1.0 -1.0; -1.0 3.0 -1.0; -1.0 -1.0 3.0]
2017-02-25 18:40:14 +02:00
References
----------
[Popp2013] Popp, Alexander, et al. "Improved robustness and consistency of 3D contact algorithms based on a dual mortar approach." Computer Methods in Applied Mechanics and Engineering 264 (2013): 67-80.
"""
function assemble!{E<:Union{Tri3, Quad4}}(problem::Problem{Mortar}, slave_element::Element{E}, time::Real; first_slave_element=false)
2016-07-03 23:40:12 +03:00
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
area = 0.0
2017-02-25 18:40:14 +02:00
slave_element_nodes = get_connectivity(slave_element)
nsl = length(slave_element)
X1 = slave_element("geometry", time)
n1 = slave_element("normal", time)
2017-02-25 18:40:14 +02:00
# project slave nodes to auxiliary plane (x0, Q)
xi = mean(get_reference_coordinates(slave_element))
first_slave_element && debug("midpoint xi = $xi")
N = vec(get_basis(slave_element, xi, time))
x0 = N*X1
n0 = N*n1
S = Vector[project_vertex_to_auxiliary_plane(X1[i], x0, n0) for i=1:nsl]
2016-07-03 23:40:12 +03:00
2017-02-25 18:40:14 +02:00
master_elements = slave_element("master elements", time)
2017-02-25 18:40:14 +02:00
if props.dual_basis
2016-07-03 23:40:12 +03:00
2017-02-25 18:40:14 +02:00
debug("Creating dual basis for element $(slave_element.id)")
2017-02-25 18:40:14 +02:00
De = zeros(nsl, nsl)
Me = zeros(nsl, nsl)
for master_element in master_elements
2016-07-03 23:40:12 +03:00
master_element_nodes = get_connectivity(master_element)
nm = length(master_element)
X2 = master_element("geometry", time)
2016-11-13 13:24:08 +02:00
if norm(mean(X1) - mean(X2)) > problem.properties.distval
continue
end
2016-07-03 23:40:12 +03:00
# 3.1 project master nodes to auxiliary plane and create polygon clipping
M = Vector[project_vertex_to_auxiliary_plane(X2[i], x0, n0) for i=1:nm]
2016-07-03 23:40:12 +03:00
P = get_polygon_clip(S, M, n0)
length(P) < 3 && continue # no clipping or shared edge (no volume)
check_orientation!(P, n0)
2016-12-14 18:12:25 +02:00
N_P = length(P)
P_area = sum([norm(1/2*cross(P[i]-P[1], P[mod(i,N_P)+1]-P[1])) for i=2:N_P])
2017-01-30 12:28:33 +02:00
2016-12-14 18:12:25 +02:00
if isapprox(P_area, 0.0)
info("Polygon P has zero area: $P_area")
continue
end
2017-01-30 12:28:33 +02:00
2016-07-03 23:40:12 +03:00
# 4. loop integration cells
2017-02-25 18:40:14 +02:00
C0 = calculate_centroid(P)
2016-12-14 18:12:25 +02:00
all_cells = get_cells(P, C0)
for cell in all_cells
virtual_element = Element(Tri3, Int[])
2016-07-03 23:40:12 +03:00
update!(virtual_element, "geometry", cell)
2017-02-25 18:40:14 +02:00
for ip in get_integration_points(virtual_element, 3)
detJ = virtual_element(ip, time, Val{:detJ})
w = ip.weight*detJ
x_gauss = virtual_element("geometry", ip, time)
xi_s, alpha = project_vertex_to_surface(x_gauss, x0, n0, slave_element, X1, time)
N1 = slave_element(xi_s, time)
De += w*diagm(vec(N1))
Me += w*N1'*N1
end
end # integration cells done
2016-07-03 23:40:12 +03:00
2017-02-25 18:40:14 +02:00
end # master elements done
Ae = De*inv(Me)
info("Dual basis coefficient matrix: $Ae")
else
Ae = eye(nsl)
end
for master_element in master_elements
master_element_nodes = get_connectivity(master_element)
nm = length(master_element)
X2 = master_element("geometry", time)
if norm(mean(X1) - mean(X2)) > problem.properties.distval
continue
end
# 3.1 project master nodes to auxiliary plane and create polygon clipping
M = Vector[project_vertex_to_auxiliary_plane(X2[i], x0, n0) for i=1:nm]
P = get_polygon_clip(S, M, n0)
length(P) < 3 && continue # no clipping or shared edge (no volume)
check_orientation!(P, n0)
N_P = length(P)
P_area = sum([norm(1/2*cross(P[i]-P[1], P[mod(i,N_P)+1]-P[1])) for i=2:N_P])
if first_slave_element
debug("Polygon clip info for first slave element:")
debug("S = $S")
debug("M = $M")
debug("P = $P")
debug("N_P = $N_P")
debug("P_area = $P_area")
end
if isapprox(P_area, 0.0)
info("Polygon P has zero area: $P_area")
continue
end
C0 = calculate_centroid(P)
De = zeros(nsl, nsl)
Me = zeros(nsl, nm)
ge = zeros(field_dim*nsl)
# 4. loop integration cells
all_cells = get_cells(P, C0)
for cell in all_cells
virtual_element = Element(Tri3, Int[])
update!(virtual_element, "geometry", cell)
# 5. loop integration point of integration cell
for ip in get_integration_points(virtual_element, 3)
detJ = virtual_element(ip, time, Val{:detJ})
w = ip.weight*detJ
# project gauss point from auxiliary plane to master and slave element
x_gauss = virtual_element("geometry", ip, time)
xi_s, alpha = project_vertex_to_surface(x_gauss, x0, n0, slave_element, X1, time)
xi_m, alpha = project_vertex_to_surface(x_gauss, x0, n0, master_element, X2, time)
# add contributions
N1 = vec(get_basis(slave_element, xi_s, time))
N2 = vec(get_basis(master_element, xi_m, time))
Phi = Ae*N1
# Phi = [3.0-4.0*xi_s[1]-4.0*xi_s[2], 4.0*xi_s[1]-1.0, 4.0*xi_s[2]-1.0]
De += w*Phi*N1'
Me += w*Phi*N2'
if props.adjust && haskey(slave_element, "displacement") && haskey(master_element, "displacement")
u1 = slave_element("displacement", time)
u2 = master_element("displacement", time)
x_s = N1*(X1+u1)
x_m = N2*(X2+u2)
ge += w*vec((x_m-x_s)*Phi')
end
area += w
end # integration points done
end # integration cells done
# 6. add contribution to contact virtual work
sdofs = get_gdofs(problem, slave_element)
mdofs = get_gdofs(problem, master_element)
for i=1:field_dim
lsdofs = sdofs[i:field_dim:end]
lmdofs = mdofs[i:field_dim:end]
add!(problem.assembly.C1, lsdofs, lsdofs, De)
add!(problem.assembly.C1, lsdofs, lmdofs, -Me)
add!(problem.assembly.C2, lsdofs, lsdofs, De)
add!(problem.assembly.C2, lsdofs, lmdofs, -Me)
end
add!(problem.assembly.g, sdofs, ge)
end # master elements done
return area
end
""" Assemble quadratic surface element to problem.
In polygon clipping element is divided to linear sub-elements proposed in [Puso2008].
References
----------
[Puso2008] Puso, Michael A., T. A. Laursen, and Jerome Solberg. "A segment-to-segment mortar contact method for quadratic elements and large deformations." Computer Methods in Applied Mechanics and Engineering 197.6 (2008): 555-566.
[Popp1012] Popp, Alexander, et al. "Dual quadratic mortar finite element methods for 3D finite deformation contact." SIAM Journal on Scientific Computing 34.4 (2012): B421-B446.
"""
function assemble!{E<:Union{Tri6}}(problem::Problem{Mortar}, slave_element::Element{E}, time::Real; first_slave_element=false)
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
area = 0.0
Xs = slave_element("geometry", time)
alp = props.alpha
if alp != 0.0
T = [
1.0 0.0 0.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0 0.0 0.0
alp alp 0.0 1.0-2*alp 0.0 0.0
0.0 alp alp 0.0 1.0-2*alp 0.0
alp 0.0 alp 0.0 0.0 1.0-2*alp
]
else
T = eye(6)
end
#=
invT = [
1.0 0.0 0.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0 0.0 0.0
-alp/(1-2*alp) -alp/(1-2*alp) 0.0 1/(1-2*alp) 0.0 0.0
0.0 -alp/(1-2*alp) -alp/(1-2*alp) 0.0 1/(1-2*alp) 0.0
-alp/(1-2*alp) 0.0 -alp/(1-2*alp) 0.0 0.0 1/(1-2*alp)
]
=#
if props.dual_basis
# info("Creating dual basis for element $(slave_element.id)")
nsl = length(slave_element)
De = zeros(nsl, nsl)
Me = zeros(nsl, nsl)
# split slave element to linear sub-elements and loop
for sub_slave_element in split_quadratic_element(slave_element, time)
slave_element_nodes = get_connectivity(sub_slave_element)
nsl = length(sub_slave_element)
X1 = sub_slave_element("geometry", time)
n1 = sub_slave_element("normal", time)
# create auxiliary plane
xi = mean(get_reference_coordinates(sub_slave_element))
first_slave_element && debug("midpoint xi = $xi")
N = vec(get_basis(sub_slave_element, xi, time))
x0 = N*X1
n0 = N*n1
# project slave nodes to auxiliary plane
S = Vector[project_vertex_to_auxiliary_plane(X1[i], x0, n0) for i=1:nsl]
# 3. loop all master elements
master_elements = slave_element("master elements", time)
for master_element in master_elements
Xm = master_element("geometry", time)
if norm(mean(Xs) - mean(Xm)) > problem.properties.distval
continue
end
# split master element to linear sub-elements and loop
for sub_master_element in split_quadratic_element(master_element, time)
master_element_nodes = get_connectivity(sub_master_element)
nm = length(sub_master_element)
X2 = sub_master_element("geometry", time)
# 3.1 project master nodes to auxiliary plane
M = Vector[project_vertex_to_auxiliary_plane(X2[i], x0, n0) for i=1:nm]
# create polygon clipping P
P = get_polygon_clip(S, M, n0)
length(P) < 3 && continue # no clipping or shared edge (no volume)
check_orientation!(P, n0)
N_P = length(P)
P_area = sum([norm(1/2*cross(P[i]-P[1], P[mod(i,N_P)+1]-P[1])) for i=2:N_P])
C0 = calculate_centroid(P)
# 4. loop integration cells
all_cells = get_cells(P, C0)
for cell in all_cells
virtual_element = Element(Tri3, Int[])
update!(virtual_element, "geometry", cell)
for ip in get_integration_points(virtual_element, 3)
2016-12-14 18:12:25 +02:00
x_gauss = virtual_element("geometry", ip, time)
2017-02-25 18:40:14 +02:00
xi_s, alpha = project_vertex_to_surface(x_gauss, x0, n0, slave_element, Xs, time)
2016-12-14 18:12:25 +02:00
detJ = virtual_element(ip, time, Val{:detJ})
w = ip.weight*detJ
2017-02-25 18:40:14 +02:00
N1 = vec(slave_element(xi_s, time)*T)
De += w*diagm(N1)
2016-12-14 18:12:25 +02:00
Me += w*N1*N1'
2017-02-25 18:40:14 +02:00
end
end # integration cells done
end # sub aster elements done
end # master elements done
end # sub slave elements done
Ae = De*inv(Me)
# info("Dual basis construction finished.")
# info("Slave element geometry = $Xs")
# info("De = $De")
# info("Me = $Me")
# info("Dual basis coefficient matrix: $Ae")
else
nsl = length(slave_element)
Ae = eye(nsl)
end
2016-10-18 11:20:15 +03:00
2017-02-25 18:40:14 +02:00
# split slave element to linear sub-elements and loop
for sub_slave_element in split_quadratic_element(slave_element, time)
2016-07-03 23:40:12 +03:00
2017-02-25 18:40:14 +02:00
slave_element_nodes = get_connectivity(sub_slave_element)
nsl = length(sub_slave_element)
X1 = sub_slave_element("geometry", time)
n1 = sub_slave_element("normal", time)
2016-07-03 23:40:12 +03:00
2017-02-25 18:40:14 +02:00
# create auxiliary plane
xi = mean(get_reference_coordinates(sub_slave_element))
first_slave_element && debug("midpoint xi = $xi")
N = vec(get_basis(sub_slave_element, xi, time))
x0 = N*X1
n0 = N*n1
# project slave nodes to auxiliary plane
S = Vector[project_vertex_to_auxiliary_plane(X1[i], x0, n0) for i=1:nsl]
2016-07-03 23:40:12 +03:00
2017-02-25 18:40:14 +02:00
# 3. loop all master elements
master_elements = slave_element("master elements", time)
for master_element in master_elements
Xm = master_element("geometry", time)
if norm(mean(Xs) - mean(Xm)) > problem.properties.distval
continue
end
2017-02-25 18:40:14 +02:00
# split master element to linear sub-elements and loop
for sub_master_element in split_quadratic_element(master_element, time)
master_element_nodes = get_connectivity(sub_master_element)
nm = length(sub_master_element)
X2 = sub_master_element("geometry", time)
# 3.1 project master nodes to auxiliary plane
M = Vector[project_vertex_to_auxiliary_plane(X2[i], x0, n0) for i=1:nm]
# create polygon clipping P
P = get_polygon_clip(S, M, n0)
length(P) < 3 && continue # no clipping or shared edge (no volume)
check_orientation!(P, n0)
N_P = length(P)
P_area = sum([norm(1/2*cross(P[i]-P[1], P[mod(i,N_P)+1]-P[1])) for i=2:N_P])
if first_slave_element
debug("Polygon clip info for first slave element:")
debug("S = $S")
debug("M = $M")
debug("P = $P")
debug("N_P = $N_P")
debug("P_area = $P_area")
end
if isapprox(P_area, 0.0)
warn("Polygon P has zero area: $P_area")
continue
end
C0 = calculate_centroid(P)
# while our polygon clipping algorithm is working in linear sub elements
# contributions is calculated using quadratic shape functions
De = zeros(length(slave_element), length(slave_element))
Me = zeros(length(slave_element), length(master_element))
ge = zeros(field_dim*length(slave_element))
# 4. loop integration cells
all_cells = get_cells(P, C0)
for cell in all_cells
virtual_element = Element(Tri3, Int[])
update!(virtual_element, "geometry", cell)
# 5. loop integration point of integration cell
for ip in get_integration_points(virtual_element, 3)
x_gauss = virtual_element("geometry", ip, time)
xi_s, alpha = project_vertex_to_surface(x_gauss, x0, n0, slave_element, Xs, time)
xi_m, alpha = project_vertex_to_surface(x_gauss, x0, n0, master_element, Xm, time)
# add contributions
N1 = vec(slave_element(xi_s, time)*T)
N2 = vec(master_element(xi_m, time))
Phi = Ae*N1
detJ = virtual_element(ip, time, Val{:detJ})
w = ip.weight*detJ
De += w*Phi*N1'
Me += w*Phi*N2'
if props.adjust && haskey(slave_element, "displacement") && haskey(master_element, "displacement")
u1 = slave_element("displacement", time)
u2 = master_element("displacement", time)
xs = N1*(Xs+u1)
xm = N2*(Xm+u2)
ge += w*vec((xm-xs)*Phi')
end
area += w
end # integration points done
end # integration cells done
# 6. add contribution to contact virtual work
sdofs = get_gdofs(problem, slave_element)
mdofs = get_gdofs(problem, master_element)
for i=1:field_dim
lsdofs = sdofs[i:field_dim:end]
lmdofs = mdofs[i:field_dim:end]
add!(problem.assembly.C1, lsdofs, lsdofs, De)
add!(problem.assembly.C1, lsdofs, lmdofs, -Me)
add!(problem.assembly.C2, lsdofs, lsdofs, De)
add!(problem.assembly.C2, lsdofs, lmdofs, -Me)
end
add!(problem.assembly.g, sdofs, ge)
end # sub aster elements done
2016-07-03 23:40:12 +03:00
end # master elements done
2017-02-25 18:40:14 +02:00
end # sub slave elements done
return area
end
function assemble!(problem::Problem{Mortar}, time::Real, ::Type{Val{2}}, ::Type{Val{false}})
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
slave_elements = get_slave_elements(problem)
area = 0.0
#=
if props.split_quadratic_slave_elements
if !props.linear_surface_elements
warn("Mortar3D: split_quadratic_surfaces = true and linear_surface_elements = false maybe have unexpected behavior")
end
slave_elements = split_quadratic_elements(slave_elements, time)
end
=#
2017-02-25 18:40:14 +02:00
# 1. calculate nodal normals and tangents for slave element nodes j ∈ S
normals = calculate_normals(slave_elements, time, Val{2};
rotate_normals=props.rotate_normals)
update!(slave_elements, "normal", time => normals)
# 2. loop all slave elements
first_slave_element = true
for slave_element in slave_elements
area += assemble!(problem, slave_element, time; first_slave_element=first_slave_element)
first_slave_element = false
2016-07-03 23:40:12 +03:00
end # slave elements done, contact virtual work ready
2017-02-25 18:40:14 +02:00
C1 = sparse(problem.assembly.C1)
C2 = sparse(problem.assembly.C2)
maxdim = maximum(size(C1))
if problem.properties.alpha != 0.0
debug("mortar_3d: size C1 = ", size(C1), " max dim = $maxdim")
debug("alpha != 0.0, applying transformation D = Dh*T^-1")
alp = problem.properties.alpha
Te = [
1.0 0.0 0.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0 0.0 0.0
alp alp 0.0 1.0-2*alp 0.0 0.0
0.0 alp alp 0.0 1.0-2*alp 0.0
alp 0.0 alp 0.0 0.0 1.0-2*alp
]
invTe = [
1.0 0.0 0.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0 0.0 0.0
0.0 0.0 1.0 0.0 0.0 0.0
-alp/(1-2*alp) -alp/(1-2*alp) 0.0 1/(1-2*alp) 0.0 0.0
0.0 -alp/(1-2*alp) -alp/(1-2*alp) 0.0 1/(1-2*alp) 0.0
-alp/(1-2*alp) 0.0 -alp/(1-2*alp) 0.0 0.0 1/(1-2*alp)
]
# construct global transformation matrices T and invT
T = SparseMatrixCOO()
invT = SparseMatrixCOO()
for element in slave_elements
dofs = get_gdofs(problem, element)
for i=1:field_dim
ldofs = dofs[i:field_dim:end]
add!(T, ldofs, ldofs, Te)
add!(invT, ldofs, ldofs, invTe)
end
end
T = sparse(T, maxdim, maxdim, (a, b) -> b)
invT = sparse(invT, maxdim, maxdim, (a, b) -> b)
# fill diagonal
d = ones(size(T, 1))
d[get_nonzero_rows(T)] = 0.0
T += spdiagm(d)
invT += spdiagm(d)
#invT2 = sparse(inv(full(T)))
#info("invT == invT2? ", invT == invT2)
#maxabsdiff = maximum(abs(invT - invT2))
#info("max diff = $maxabsdiff")
C1 = C1*invT
C2 = C2*invT
2016-11-13 13:24:08 +02:00
end
2017-02-25 18:40:14 +02:00
tol = problem.properties.drop_tolerance
debug("Dropping small values from C1 & C2, tolerace = $tol")
SparseArrays.droptol!(C1, tol)
SparseArrays.droptol!(C2, tol)
problem.assembly.C1 = C1
problem.assembly.C2 = C2
debug("area of interface: $area")
2016-07-03 23:40:12 +03:00
end