Files
JuliaFEM.jl/src/mortar_3d.jl
T
2016-02-25 10:44:55 +02:00

673 lines
21 KiB
Julia
Raw 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
# Mortar projection calculation for 3d cases
""" Construct auxiliary plane for surface. """
function create_auxiliary_plane{E}(element::Element{E}, time::Real)
xi = get_reference_element_midpoint(E)
x0 = element("geometry", xi, time)
ntbasis = element("normal-tangential coordinates", xi, time)
return x0, ntbasis
end
"""
Project point q onto a plane given by a point p and normal n.
Parameters
----------
q::Array{Float64, 2}
point to project (row vector)
x0::Array{Float64, 2}
origo of plane
n::Array{Float64, 2}
normal vector of plane
Returns
-------
y::Array{Float64, 2}
projected point
Examples
--------
julia> p = [-0.5 -1.0 4.0]'
julia> x0 = [0.0 0.075 0.675]'
julia> n = [0.1485860 0.0784519 0.9857830]'
julia> project_node_to_auxiliary_plane(p, x0, n)
3-element Array{Float64,1}:
0.963455
-1.2447
0.925247
Notes
-----
[1](http://stackoverflow.com/questions/8942950/how-do-i-find-the-orthogonal-projection-of-a-point-onto-a-plane)
"""
function project_vertex_to_auxiliary_plane(p::Vector, x0::Vector, Q::Matrix)
n = Q[:,1]
ph = p - dot(p-x0, n)*n
qproj = Q'*(ph-x0)
if abs(qproj[1]) > 1.0e-2
# we should have something very little for normal direction if projected
# properly
info("project_point_to_auxiliary_plane(): vertex not projected correctly.")
info("p: $(ForwardDiff.get_value(p))")
info("x0: $(ForwardDiff.get_value(x0))")
info("Q: \n$(ForwardDiff.get_value(Q))")
info("qproj: $(ForwardDiff.get_value(qproj))")
error("Failed to project vertex to auxiliary plane.")
end
return qproj[2:3]
end
project_point_to_auxiliary_plane = project_vertex_to_auxiliary_plane
"""
Find edge intersections of two planar arbitrary shape polygons.
Parameters
----------
S::Array{Float64,2}
M::Array{Float64,2}
Matrices with size (2, n) where n is number of vertices of each polygon.
Returns
-------
P::Array{Float64,2}
Intersection points of polygons
n::Array{Float64,2}
Neighbour info matrix with size (ns, mn). This keeps information which
edges of polygons are intersecting. See further explanation in example
below.
Examples
--------
Find intersection points of two triangles:
julia> S = [0 0; 3 0; 0 3]'
julia> M = [-1 1; 2 -1/2; 1 3/2]'
julia> P, n = get_edge_intersections(S, M)
julia> P
2x4 Array{Float64,2}:
1.0 1.75 0.0 0.0
0.0 0.0 0.5 1.25
julia> n
3x3 Array{Int64,2}:
1 1 0
0 0 0
1 0 1)
So intersection points are: (1.00, 0.00), (1.75, 0.00), (0.00, 0.50), (0.00, 1.25).
"Neighbour matrix" can be interpreted as following:
1 1 0 <--> First edge of S intersects edges 1 and 2 of M
0 0 0 <--> Second edge of S doesn't intersect at all
1 0 1 <--> Third edge of S intersects with edges 1 and 3 of M
"""
function get_edge_intersections(S::Matrix, M::Matrix)
ns = size(S, 2)
nm = size(M, 2)
P = zeros(2, 0)
n = zeros(Int64, ns, nm)
k = 0
for i=1:ns
for j=1:nm
b = M[:,j]-S[:,i]
A = [S[:,mod(i,ns)+1]-S[:,i] -M[:,mod(j,nm)+1]+M[:,j]]
if rank(ForwardDiff.get_value(A)) == 2
r = A\b
if (r[1]>=0) & (r[1]<=1) & (r[2]>=0) & (r[2]<=1) # intersection found
k += 1
f = S[:,i]+r[1]*(S[:,mod(i,ns)+1] - S[:,i])
f = f''
P = hcat(P, f)
n[i, j] = 1
end
end
end
end
return P, n
end
"""
Find any points laying inside or border of triangle.
Parameters
----------
Y::Array{Float64, 2}
Triangle coordinates in 2×3 matrix
X::Array{Float64, 2}
List of points to test in 2×n matrix
Returns
-------
P::Array{Float64, 2}
List of points in triangle in 2×m matrix, where m is number of points inside triangle
Examples
--------
julia> S = [0.0 0.0; 3.0 0.0; 0.0 3.0]' # triangle corner points
julia> pts = [-1.0 1.0; 2.0 -0.5; 1.0 1.5; 0.5 1.5]' # points to tests
julia> points_in_triangle(S, pts)
2x2 Array{Float64,2}:
1.0 0.5
1.5 1.5
"""
function get_points_inside_triangle(Y::Matrix, X::Matrix)
@assert size(Y, 2) == 3 # "Point in TRIANGLE..."
P = zeros(2, 0)
v0 = Y[:,2] - Y[:,1]
v1 = Y[:,3] - Y[:,1] # find interior points of X in Y
d00 = (v0'*v0)[1]
d01 = (v0'*v1)[1]
d11 = (v1'*v1)[1] # using baricentric coordinates
id = 1/(d00*d11 - d01*d01)
for i=1:size(X, 2)
v2 = X[:,i] - Y[:,1]
d02 = (v0'*v2)[1]
d12 = (v1'*v2)[1]
u = (d11*d02-d01*d12)*id
v = (d00*d12-d01*d02)*id
if (u>=0) & (v>=0) & (u+v<=1) # also include nodes on the boundary
P = hcat(P, X[:,i]'')
end
end
return P
end
"""
Determine is point P inside or on boudary of polygon X.
http://paulbourke.net/geometry/polygonmesh/#insidepoly
"""
function is_point_inside_convex_polygon(P, X)
x, y = P
for i=1:length(X)
x0, y0 = X[i]
x1, y1 = X[mod(i, length(X))+1]
if (y-y0)*(x1-x0) - (x-x0)*(y1-y0) < 0
return false
end
end
return true
end
function get_points_inside_convex_polygon(pts, X)
# TODO: Make more readable
X2 = [X[:,i] for i=1:size(X,2)]
c = filter(P->is_point_inside_convex_polygon(P, X2), [pts[:,i] for i=1:size(pts, 2)])
return length(c) == 0 ? zeros(2, 0) : hcat(c...)
end
""" Return unique objects with some given tolerance. This is used in next function
because traditional unique() command returns row vectors as non-unique if they
differs only a "little".
"""
function uniquetol(P, dim::Int; args...)
@assert dim == 2
items = Vector[P[:,i] for i=1:size(P,dim)]
new_items = Vector[]
for item in items
has_found = false
for new_item in new_items
if isapprox(ForwardDiff.get_value(item), ForwardDiff.get_value(new_item); args...)
has_found = true
break
end
end
if !has_found
push!(new_items, item)
end
end
return reshape([new_items...;], length(new_items[]), length(new_items))
end
"""
Make polygon clipping of shapes S and M.
Parameters
----------
S::Array{Float64, 2}
M::Array{Float64, 2}
Shapes to clip. Needs to be triangles at the moment.
Returns
-------
Array{Float64, 2}, Array{Float64, 2}
- Polygon vertices in 2×n matrix, sorted in counter-clockwise order.
- 3×3 "neighbouring" matrix, see example.
Examples
--------
julia> S = [0 0; 3 0; 0 3]'
julia> M = [-1 1; 2 -1/2; 2 2]'
julia> P, n = clip_polygon(S, M)
julia> P
2x6 Array{Float64,2}:
0.0 1.0 2.0 2.0 1.25 0.0
0.5 0.0 0.0 1.0 1.75 1.33333,
julia> n
3x3 Array{Int64,2}:
1 0 1 <- first edge of M ([-1 1; 2 -1/2]') intersects with edges 1 and 3 of S ([0 0; 3 0]' and [0 3; 0 0]')
1 1 0 <- second edge of M ([2 -1/2; 2 2]') intersects with edges 1 and 2 of S
0 1 1 <- third edge of M ([2 2; -1 1]') intersects with edgse 2 and 3 of S
"""
function clip_polygon(S::Matrix, M::Matrix)
P1, neighbours = get_edge_intersections(M, S)
#P2 = get_points_inside_triangle(M, S)
#P3 = get_points_inside_triangle(S, M)
P2 = get_points_inside_convex_polygon(M, S)
P3 = get_points_inside_convex_polygon(S, M)
# info("polygon clipping: P1 = $P1")
# info("polygon clipping: P2 = $P2")
# info("polygon clipping: P3 = $P3")
# info("hcat P = $P")
P = hcat(P1, P2, P3)
if length(P) == 0
return nothing, nothing
end
P = uniquetol(P, 2)
meanval = mean(P, 2)
tmp = P .- meanval
angles = atan2(tmp[2,:], tmp[1,:])
angles = reshape(angles, length(angles))
order = sortperm(angles)
return P[:, order], neighbours
end
"""
Calculate polygon geometric center point
Parameters
----------
P::Array{Float64, 2}
Polygon vertices in 2×n matrix
Returns
-------
Array{Float63, 2}
Center point
Examples
--------
julia> P
2x6 Array{Float64,2}:
0.0 1.0 2.0 2.0 1.25 0.0
0.5 0.0 0.0 1.0 1.75 1.33333,
julia> C = get_polygon_cp(P)
2x1 Array{Float64,2}:
1.039740
0.804701
"""
function calculate_polygon_centerpoint(P::Matrix)
n = size(P, 2)
A = 0.0
for i=1:n
A += 1/2*(P[1,i]*P[2,mod(i,n)+1] - P[1,mod(i,n)+1]*P[2,i])
end
Cx = 0.0
Cy = 0.0
for i=1:n
inext = mod(i, n)+1
Cx += 1/(6*A)*(P[1,i] + P[1,inext])*(P[1,i]*P[2,inext] - P[1,inext]*P[2,i])
Cy += 1/(6*A)*(P[2,i] + P[2,inext])*(P[1,i]*P[2,inext] - P[1,inext]*P[2,i])
end
return [Cx, Cy]
end
"""
Project point from auxiliary plane to parametric surface given by (ξ₁, ξ₂)
Parameters
----------
p::Array{Float64,1}
point in auxiliary plane, in (n,t1,t2) coordinate system
x0::Array{Float64,1}
origo of auxiliary plane cs
Q::Array{Float64,2}
basis of auxiliary plane cs
x::Array{Float64,2}
surface node coords
basis::Array{Float64,2}
surface basis functions
dbasis::Array{Float64,2}
partial derivatives of surface basis functions
Returns
-------
Array{Float64,2}
solution vector (d, ξ₁, ξ₂) where d is distance to surface
Examples
--------
Define surface with node points, basis + dbasis
julia> xquad = [
... -2.5 -2.0 1.0
... 2.5 -2.0 0.7
... 2.0 2.3 0.0
... -2.0 2.0 1.0]'
julia> basis(xi) = [
... (1-xi[1])(1-xi[2])/4
... (1+xi[1])(1-xi[2])/4
... (1+xi[1])(1+xi[2])/4
... (1-xi[1])(1+xi[2])/4]
julia> dbasis(xi) = [
... -(1-xi[2])/4 -(1-xi[1])/4
... (1-xi[2])/4 -(1+xi[1])/4
... (1+xi[2])/4 (1+xi[1])/4
... -(1+xi[2])/4 (1-xi[1])/4]
We aim to find point p, which we first project to auxiliary plane defined as following
julia> p = [-2.5 -2.0 1.0]'
julia> x0 = [0.0 0.075 0.675]'
julia> Q = [
... 0.1485860 0.9888990 0.0000000
... 0.0784519 -0.0117877 0.9968480
... 0.9857830 -0.1481180 -0.0793325]
Our projected point is therefore
julia> n = Q[:,1] # first component is normal direction
julia> ph = project_node_to_auxiliary_plane(p, x0, n)
julia> ph = Q'(ph-x0)
julia> ph
3x1 Array{Float64,2}:
1.33264e-7
-2.49593
-2.09424
Our point ph is now in auxiliary plane in n,t1,t2 coordinate system. Next we
project it back to surface defined by xquad*basis
julia> theta = project_point_from_plane_to_surface(ph, x0, Q, xquad, basis, dbasis)
julia> theta
3x1 Array{Float64,2}:
-0.213874
-0.999999
-1.0
We see that our ξ₁ = ξ₂ = -1 so we found first point of xquad
[-2.5 -2.0 1.0]' correctly.
julia> xquad*basis(theta[2:3])
3-element Array{Float64,1}:
-2.5
-2.0
1.0
"""
function project_point_from_plane_to_surface{E}(p::Vector, x0::Vector, Q::Matrix,
element::Element{E}, time::Real; max_iterations::Int=10, iter_tol::Float64=1.0e-9)
x = element("geometry", time)
return project_point_from_plane_to_surface(p, x0, Q, element, x, time;
max_iterations=max_iterations, iter_tol=iter_tol)
end
function project_vertex_from_plane_to_surface{E}(p::Vector, x0::Vector, Q::Matrix,
element::Element{E}, x, time::Real; max_iterations::Int=10, iter_tol::Float64=1.0e-9)
basis(xi) = get_basis(E, xi)
dbasis(xi) = get_dbasis(E, xi)
ph = Q*[0; p] + x0
n = Q[:,1]
b(theta) = ph + theta[1]*n - basis(theta[2:3])*x
J(theta) = [n -dbasis(theta[2:3])*x]
theta = zeros(3)
dtheta = zeros(3)
for i=1:max_iterations
# FIXME: gives NaN if partials in J
dtheta = ForwardDiff.get_value(J(theta)) \ -b(theta)
theta += dtheta
if norm(ForwardDiff.get_value(dtheta)) < iter_tol
return theta
end
end
info("failed to project vertex from auxiliary plane back to surface")
info("element type: $E")
info("element connectivity: $(get_connectivity(element))")
info("auxiliary plane: x0 = $(ForwardDiff.get_value(x0)), Q = $(ForwardDiff.get_value(Q))")
info("point coordinates on plane: $(ForwardDiff.get_value(p))")
info("element geometry: $(ForwardDiff.get_value(x.data))")
info("ph: $(ForwardDiff.get_value(ph))")
info("normal direction: $(ForwardDiff.get_value(n))")
info("parameter vector before giving up: $(ForwardDiff.get_value(theta))")
info("increment in parameter vector before giving up: $(ForwardDiff.get_value(dtheta))")
info("b([0.0, 0.0, 0.0]) = $(ForwardDiff.get_value(b([0.0, 0.0, 0.0])))")
info("J([0.0, 0.0, 0.0]) = $(ForwardDiff.get_value(J([0.0, 0.0, 0.0])))")
info("iterations were")
theta = zeros(3)
dtheta = zeros(3)
for i=1:max_iterations
info("iter $i, theta = $(ForwardDiff.get_value(theta))")
info("b = $(ForwardDiff.get_value(b(theta)))")
info("J = $(ForwardDiff.get_value(J(theta)))")
dtheta = ForwardDiff.get_value(J(theta)) \ -b(theta)
info("dtheta = $(ForwardDiff.get_value(dtheta))")
theta += dtheta
if norm(dtheta) < iter_tol
return theta
end
end
error("project_point_to_surface: did not converge in $max_iterations iterations!")
end
typealias MortarElements3D Union{Tri3, Quad4}
function assemble!{E<:MortarElements3D}(assembly::Assembly, problem::Problem{Mortar},
slave_element::Element{E}, time::Real, ::Type{Val{:total}})
assemble!(assembly, problem, slave_element, time, Val{problem.properties.formulation})
end
function assemble!{E<:MortarElements3D}(assembly::Assembly, problem::Problem{Mortar},
slave_element::Element{E}, time::Real, ::Type{Val{:total}})
haskey(slave_element, "master elements") || return
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
slave_dofs = get_gdofs(slave_element, field_dim)
props = problem.properties
if props.formulation == :Standard && props.normal_condition == :Contact
error("for contact choose Dual formulation.""")
end
# create auxiliary plane and project slave nodes to it
# x0 = origo, Q = local basis
x0, Q = create_auxiliary_plane(slave_element, time)
# 1. project slave nodes to auxiliary plane
Sl = Vector{Float64}[]
for p in slave_element("geometry", time)
push!(Sl, project_point_to_auxiliary_plane(p, x0, Q))
end
S = hcat(Sl...)
for master_element in slave_element["master elements"]
# if distance between elements is "far enough" cannot expect contact
if (props.normal_condition == :Contact) || props.inequality_constraints
slave_midpoint = slave_element("geometry", [0.0, 0.0], time)
master_midpoint = master_element("geometry", [0.0, 0.0], time)
if norm(slave_midpoint - master_midpoint) > props.minimum_distance
continue
end
end
master_dofs = get_gdofs(master_element, field_dim)
# 2. project master nodes to auxiliary plane
M = Vector{Float64}[]
for p in master_element("geometry", time)
push!(M, project_point_to_auxiliary_plane(p, x0, Q))
end
M = hcat(M...)
# 3. create polygon clipping on auxiliary plane
P = nothing
neighbours = nothing
try
P, neighbours = clip_polygon(S, M)
catch
info("polygon clipping failed")
info("S = ")
dump(S)
info("M = ")
dump(M)
info("original Sl = ")
info(Sl)
error("cannot continue")
end
isa(P, Void) && continue # no clipping
# shared edge but no shared volume. skipping
size(P, 2) < 3 && continue
C = calculate_polygon_centerpoint(P)
npts = size(P, 2) # number of vertices in polygon
# loop vertices and create temporary integrate cells
# TODO: basically when npts == 3 or npts == 4 we could integrate without splitting to cells.
nnodes = size(slave_element, 2)
C1S3 = zeros(3*nnodes, 3*nnodes)
C1M3 = zeros(3*nnodes, 3*nnodes)
for pnt=1:npts # integration of mortar matrices begin
cell = Field(Vector{Float64}[C, P[:,pnt], P[:,mod(pnt,npts)+1]])
# calculate slave side projection matrix D
# construct dual basis
Ae = zeros(nnodes, nnodes)
De = zeros(nnodes, nnodes)
Me = zeros(nnodes, nnodes)
if problem.properties.formulation == :Dual # Construct dual basis
for ip in get_integration_points(Tri3, Val{5})
N = get_basis(Tri3, ip.xi)
xi = vec(N*cell)
theta = project_point_from_plane_to_surface(xi, x0, Q, slave_element, time)
xi_slave = theta[2:3]
N1 = slave_element(xi_slave, time)
# jacobian determinant on integration cell
dNC = get_dbasis(Tri3, ip.xi)
JC = sum([kron(dNC[:,j], cell[j]') for j=1:length(cell)])
wC = ip.weight*det(JC)
De += wC*diagm(vec(N1))
Me += wC*N1'*N1
end
Ae = De*inv(Me)
end
for i=1:field_dim
C1S3[i:field_dim:end,i:field_dim:end] += De
end
# Calculate master side projection matrix M
for ip in get_integration_points(Tri3, Val{5})
# gauss point in auxiliary plane
#N = get_basis(E, ip.xi)
N = get_basis(Tri3, ip.xi)
xi = vec(N*cell) # xi defined in auxilary plane
# find projection of gauss point to master and slave elements
theta1 = project_point_from_plane_to_surface(xi, x0, Q, slave_element, time)
theta2 = project_point_from_plane_to_surface(xi, x0, Q, master_element, time)
xi_slave = theta1[2:3]
xi_master = theta2[2:3]
# evaluate shape functions values in gauss point and add contribution to matrices
N1 = slave_element(xi_slave, time)
N2 = master_element(xi_master, time)
# jacobian determinant on integration cell
dNC = get_dbasis(Tri3, ip.xi)
JC = sum([kron(dNC[:,j], cell[j]') for j=1:length(cell)])
wC = ip.weight*det(JC)
# extend matrices according to the problem dimension (3)
@assert length(slave_dofs) == length(master_dofs)
Me = wC*Ae*N1'*N2
for k=1:field_dim
C1M3[k:field_dim:end,k:field_dim:end] += Me
end
end
end # integration of mortar matrices done.
# constraints in normal-tangential direction and initial weighted gap
X1 = vec(slave_element("geometry", time))
X2 = vec(master_element("geometry", time))
Q_ = slave_element("normal-tangential coordinates", time)
Z = zeros(3, 3)
if nnodes == 3
Q3 = [Q Z Z; Z Q Z; Z Z Q]
elseif nnodes == 4
Q3 = [Q Z Z Z; Z Q Z Z; Z Z Q Z; Z Z Z Q]
end
D3 = zeros(3*nnodes, 3*nnodes)
C2S3 = Q3'*C1S3
C2M3 = Q3'*C1M3
G = -(C2S3*X1 - C2M3*X2)
# complementarity condition
if haskey(slave_element, "displacement")
u1 = vec(slave_element("displacement", time))
else
u1 = zeros(3*nnodes)
end
if haskey(master_element, "displacement")
u2 = vec(master_element("displacement", time))
else
u2 = zeros(3*nnodes)
end
x1 = X1 + u1
x2 = X2 + u2
if haskey(slave_element, "reaction force")
la = vec(slave_element("reaction force", time))
else
la = zeros(3*nnodes)
end
g = -(C2S3*x1 - C2M3*x2)
c = Q3'*la - g
inactive_nodes = find(c[1:field_dim:end] .<= 0)
active_nodes = find(c[1:field_dim:end] .> 0)
# normal constraint: remove inactive nodes if normal condition is set to contact
if problem.properties.normal_condition == :Contact
for j in inactive_nodes
dofs = [3*(j-1)+1, 3*(j-1)+2, 3*(j-1)+3]
G[dofs] = 0
C1S3[dofs,:] = 0
C1M3[dofs,:] = 0
C2S3[dofs,:] = 0
C2M3[dofs,:] = 0
end
end
# tangential constraint: stick or slip
if problem.properties.tangential_condition == :Slip
D3 = copy(C2S3)
D3[1:field_dim:end, :] = 0
C2S3[2:field_dim:end, :] = 0
C2M3[2:field_dim:end, :] = 0
C2S3[3:field_dim:end, :] = 0
C2M3[3:field_dim:end, :] = 0
end
# add contributions
add!(assembly.C1, slave_dofs, slave_dofs, C1S3)
add!(assembly.C1, slave_dofs, master_dofs, -C1M3)
add!(assembly.C2, slave_dofs, slave_dofs, C2S3)
add!(assembly.C2, slave_dofs, master_dofs, -C2M3)
add!(assembly.D, slave_dofs, slave_dofs, D3)
add!(assembly.c, slave_dofs, c)
add!(assembly.g, slave_dofs, G)
end
end