first attemps to make properly linearized version of mortar projection for finite sliding. not working at the moment, it has convergence issues.

This commit is contained in:
Jukka Aho
2016-02-23 11:32:53 +02:00
parent 21d7592c80
commit 65eb014de9
18 changed files with 1865 additions and 739 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+29
View File
@@ -132,6 +132,35 @@ function call{E}(element::Element{E}, xi::VecOrIP, time::Float64=0.0)
return get_basis(element, xi)
end
""" Given a list of elementa and nodes, find a subset of elements
containing nodes.
"""
function find_elements(elements, nodes)
s = Set{Element}()
for element in elements
conn = get_connectivity(element)
for j in nodes
if j in conn
push!(s, element)
break
end
end
end
return collect(s)
end
function get_gdofs(element::Element)
return get_gdofs(element, 1)
end
function get_dbasis{E}(element::Element{E}, ip::IntegrationPoint)
return get_dbasis(E, ip.xi)
end
function get_basis{E, T<:Real}(element::Element{E}, xi::T)
return get_basis(E, xi)
end
""" Return dual basis transformation matrix Ae. """
function get_dualbasis(element::Element, time::Real)
if length(element.A) == 0
+9
View File
@@ -216,6 +216,15 @@ function Base.(:*){T<:Real}(c::T, field::DVTI)
return DVTI(c*field.data)
end
""" Multiply DVTI field with another vector T. Vector length
must match to the field length and this can be used mainly
for interpolation purposes, i.e., u = ∑ Nᵢuᵢ
"""
function Base.(:*)(T::Vector, f::DVTI)
@assert length(T) == length(f)
return sum([T[i]*f[i] for i=1:length(f)])
end
function Base.vec(field::DVTI)
return [field.data...;]
end
+8
View File
@@ -140,3 +140,11 @@ end
(xi) -> [ 1.0, xi[1], xi[2], xi[3], xi[1]^2,
xi[2]^2, xi[3]^2, xi[1]*xi[2], xi[2]*xi[3], xi[3]*xi[1]])
# some helpers to make accessing 1d basis functions more easily
function get_basis{T<:Real, E<:Union{Seg2,Seg3}}(::Type{E}, xi::T)
get_basis(E, [xi])
end
function get_dbasis{T<:Real, E<:Union{Seg2,Seg3}}(::Type{E}, xi::T)
get_dbasis(E, [xi])
end
+5 -715
View File
@@ -19,7 +19,7 @@ b) Remove inactive inequality constraints in assembly level. This is done in
"""
type Mortar <: BoundaryProblem
formulation :: Symbol # :total or :incremental
formulation :: Symbol # :total, :incremental, :autodiff
dual_basis :: Bool
inequality_constraints :: Bool # Launch PDASS to solve inequality constraints
normal_condition :: Symbol # Tie or Contact
@@ -31,10 +31,11 @@ type Mortar <: BoundaryProblem
always_in_slip :: Vector{Int64} # nodes in this list always in slip
contact :: Bool
friction :: Bool
gap_sign :: Int # gap sign convention
end
function Mortar()
Mortar(:total, true, false, :Tie, :Stick, Inf, false, [], [], [], false, false)
Mortar(:total, true, false, :Tie, :Stick, Inf, false, [], [], [], false, false, -1)
end
function get_unknown_field_name(::Type{Mortar})
@@ -51,719 +52,8 @@ macro debug(msg)
end
include("mortar_2d.jl")
### Mortar projection calculation for 3d cases
"""
Construct auxiliary plane for surface.
Parameters
----------
x::Array{Float64, 2}
Node coordinates
ximp::Array{Float64, 1}
Element mid-point in dimensionless mother element coordinates ξ
normals::Array{Float64, 2}
Normal directions in nodes
Returns
-------
x0, Q
x0::Array{Float64, 1} - origo of auxiliary plane
Q::Array{Float64, 2} - orthogonal basis, first vector is normal direction
and two rest vectors create orthonormal right-handed basis.
Examples
--------
Calculate auxiliary plane given nodal coordinates, midpoint of mother element,
node normals and suitable function space:
julia> xquad = [
... -2.5 2.5 2.0 -2.0
... -2.0 -2.0 2.3 2.0
... 1.0 0.7 0.0 1.0]
julia> m_midpoint = [0.0, 0.0]
julia> normals = [
... 0.05989060 0.0590504 0.225612 0.2445800
... -0.00748633 0.1670810 0.182034 -0.0305725
... 0.99817700 0.9841730 0.957059 0.9691470]
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> x0, Q = create_auxiliary_plane(xquad, mmidpoint, normals, basis)
julia> x0
3-element Array{Float64,1}:
0.0
0.075
0.675
julia> Q
3x3 Array{Float64,2}:
0.148586 0.988899 0.0
0.0784519 -0.0117877 0.996848
0.985783 -0.148118 -0.0793325
Notes
-----
- Midpoint in mother element typically (0, 0) for quadrangles and (1/3, 1/3)
for triangles.
- Uses Gram-Schmidt process to find orthogonal basis
- [1](http://www.math.umn.edu/~olver/aims_/qr.pdf)
- [2](http://www.ecs.umass.edu/ece/ece313/Online_help/gram.pdf)
- [3](http://www.terathon.com/code/tangent.html)
"""
# function create_auxiliary_plane(x, ximp, normals, basis)
function create_auxiliary_plane{E}(element::Element{E}, time::Real)
# proj(u, v) = dot(v, u) / dot(u, u) * u
# xi = [1.0/3.0, 1.0/3.0]
xi = get_reference_element_midpoint(E)
x0 = element("geometry", xi, time)
ntbasis = element("normal-tangential coordinates", xi, time)
return x0, ntbasis
#=
n = element("normal-tangential coordinates", xi, time)[:, 1]
n /= norm(n)
# gram-schmidt
u1 = n
j = indmax(abs(u1))
v2 = zeros(3)
v2[mod(j,3)+1] = 1.0
u2 = v2 - proj(u1, v2)
u3 = cross(u1, u2)
t1 = u2/norm(u2)
t2 = u3/norm(u3)
new_basis = [n t1 t2]
return x0, new_basis
=#
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_point_to_auxiliary_plane(p::Vector, x0::Vector, Q::Matrix)
n = Q[:,1]
ph = p - dot(p-x0, n)*n
qproj = Q'*(ph-x0)
if !isapprox(qproj[1], 0.0; atol=1.0e-12)
info("project_point_to_auxiliary_plane(): point not projected correctly.")
info("p: $p")
info("x0: $x0")
info("Q: \n$Q")
info("qproj: $qproj")
error("Failed to project point to auxiliary plane.")
end
return qproj[2:3]
end
"""
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(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{Float64}[P[:,i] for i=1:size(P,dim)]
new_items = Vector{Float64}[]
for item in items
has_found = false
for new_item in new_items
if isapprox(item, 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 Float64[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)
basis(xi) = get_basis(E, xi)
dbasis(xi) = get_dbasis(E, xi)
x = element("geometry", time)
ph = Q*[0; p] + x0
theta = Float64[0.0, 0.0, 0.0]
n = Q[:,1]
for i=1:max_iterations
b = ph + theta[1]*n - basis(theta[2:3])*x
J = [n -dbasis(theta[2:3])*x]
dtheta = J \ -b
theta += dtheta
if norm(dtheta) < iter_tol
return theta
end
end
begin
info("projecting point from auxiliary plane back to surface didn't go very well.")
info("element type: $E")
info("element connectivity: $(get_connectivity(element))")
info("auxiliary plane: x0 = $x0, Q = $Q")
info("point coordinates on plane: $p")
info("element geometry: $x")
info("ph: $ph")
info("normal direction: $n")
info("parameter vector before giving up: $theta")
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)
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
include("mortar_2d_autodiff.jl")
include("mortar_3d.jl")
""" Remove inactive inequality constraints by using primal-dual active set strategy. """
function boundary_assembly_posthook!(solver::Solver, problem::Problem{Mortar}, C1, C2, D, g)
+2 -3
View File
@@ -217,7 +217,6 @@ function project_from_master_to_slave{S,M}(slave::Element{S}, master::Element{M}
error("find projection from master to slave: did not converge")
end
# Mortar assembly 2d
# quadratic not tested yet
@@ -546,8 +545,8 @@ function assemble!{E<:MortarElements2D}(assembly::Assembly, problem::Problem{Mor
# Calculate normal-tangential constraints and weighted gap
C2S2 = Q2'*C1S2
C2M2 = Q2'*C1M2
G += -(C2S2*X1 - C2M2*X2)
g += -(C2S2*x1 - C2M2*x2)
G += props.gap_sign*(C2S2*X1 - C2M2*X2)
g += props.gap_sign*(C2S2*x1 - C2M2*x2)
# Add contributions
add!(local_assembly.C1, slave_dofs, slave_dofs, C1S2)
+674
View File
@@ -0,0 +1,674 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
""" Find segment from slave element corresponding to master element nodes.
x1_, n1_
slave element geometry and normal direction
x2_ master element nodes to project onto slave
"""
function project_from_master_to_slave{E<:MortarElements2D}(
slave_element::Element{E}, x1_::DVTI, n1_::DVTI, x2::Vector;
tol=1.0e-10, max_iterations=20)
function x1(xi1)
N = get_basis(E, xi1)
return vec(N)*x1_
end
function dx1(xi1)
dN = get_dbasis(E, xi1)
return vec(dN)*x1_
end
function n1(xi1)
N = get_basis(E, xi1)
return vec(N)*n1_
end
function dn1(xi1)
dN = get_dbasis(E, xi1)
return vec(dN)*n1_
end
cross2(a, b) = cross([a; 0], [b; 0])[3]
R(xi1) = cross2(x1(xi1)-x2, n1(xi1))
dR(xi1) = cross2(dx1(xi1), n1(xi1)) + cross2(x1(xi1)-x2, dn1(xi1))
xi1 = 0.0
dxi1 = 0.0
for i=1:max_iterations
dxi1 = -R(xi1)/dR(xi1)
xi1 += dxi1
if norm(dxi1) < tol
return xi1
end
end
info("x1 = $(ForwardDiff.get_value(x1_.data))")
info("n1 = $(ForwardDiff.get_value(n1_.data))")
info("x2 = $(ForwardDiff.get_value(x2))")
info("xi1 = $(ForwardDiff.get_value(xi1)), dxi1 = $(ForwardDiff.get_value(dxi1))")
info("-R(xi1) = $(ForwardDiff.get_value(-R(xi1)))")
info("dR(xi1) = $(ForwardDiff.get_value(dR(xi1)))")
error("find projection from master to slave: did not converge")
end
function project_from_slave_to_master{E<:MortarElements2D}(
master_element::Element{E}, x1::Vector, n1::Vector, x2_::DVTI;
tol=1.0e-10, max_iterations=20)
function x2(xi2)
N = get_basis(E, xi2)
return vec(N)*x2_
end
function dx2(xi2)
dN = get_dbasis(E, xi2)
return vec(dN)*x2_
end
cross2(a, b) = cross([a; 0], [b; 0])[3]
R(xi2) = cross2(x2(xi2)-x1, n1)
dR(xi2) = cross2(dx2(xi2), n1)
xi2 = 0.0
dxi2 = 0.0
for i=1:max_iterations
dxi2 = -R(xi2) / dR(xi2)
xi2 += dxi2
if norm(dxi2) < tol
return xi2
end
end
error("find projection from slave to master: did not converge, last val: $xi2 and $dxi2")
end
function assemble!{E<:MortarElements2D}(assembly::Assembly, problem::Problem{Mortar}, slave_element::Element{E}, time::Real,
::Type{Val{:forwarddiff_old}})
haskey(slave_element, "master elements") || return
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
slave_dofs = get_gdofs(slave_element, field_dim)
nnodes = size(slave_element, 2)
X1 = slave_element("geometry", time)
#u1 = slave_element("displacement", time)
#x1 = X1 + u1
slave_element_nodes = get_connectivity(slave_element)
adjacent_elements = find_elements(get_elements(problem), slave_element_nodes)
adjacent_nodes = get_nodes(adjacent_elements) # including also nodes from adjacent elements
Q = [0.0 -1.0; 1.0 0.0]
X = spzeros(10000, 1)
for element in get_elements(problem)
conn = get_connectivity(element)
geom = element("geometry", time)
for (c, g) in zip(conn, geom)
dofs = [field_dim*(c-1)+1, field_dim*(c-1)+2]
X[dofs] = g
end
end
# here x does not mean deformed configuration
x = [problem.assembly.u; problem.assembly.la]
if length(x) == 0
info("mortar_2d_autodiff: length(x) == 0")
# resize solution vectors according to initial configuration of this problem
X = vec(full(sparse(findnz(X)...)))
x = zeros(length(X)*2)
else
# resize initial configuration to match real dimension
I, J, V = findnz(X)
X = vec(full(sparse(I, J, V, length(problem.assembly.u), 1)))
end
ndofs = round(Int, length(x)/2)
# at the end we should have
# info("mortar_2d_autodiff: size of x = $(size(x))")
# info("mortar_2d_autodiff: size of X = $(size(X))")
# info("mortar_2d_autodiff: ndofs = $ndofs")
""" Calculate normal vector for slave element nodes in current configuration. """
function calculate_normals(u::Matrix)
normals = zeros(u)
# 1. update nodal normals
for element in adjacent_elements
conn = get_connectivity(element)
gdofs = get_gdofs(element, field_dim)
X_el = element("geometry", time)
u_el = Field(Vector[u[:, i] for i in conn])
x_el = X_el + u_el
for ip in get_integration_points(element, Val{3})
dN = get_dbasis(element, ip)
N = element(ip, time)
t = sum([kron(dN[:,i], x_el[i]') for i=1:length(x_el)])
normals[:, conn] += ip.weight*Q*t'*N
end
end
slave_normals = Field(Vector[normals[:,i]/norm(normals[:,i]) for i in slave_element_nodes])
return slave_normals
end
function calculate_mortar_projection(u::Matrix, n1::DVTI)
B = SparseMatrixCOO{Real}([], [], [])
u1 = Field([u[:,i] for i in slave_element_nodes])
x1 = X1 + u1
for master_element in slave_element["master elements"]
X2 = master_element("geometry", time)
master_element_nodes = get_connectivity(master_element)
u2 = Field([u[:,i] for i in master_element_nodes])
x2 = X2 + u2
#info("master element coordinate 1 = $(ForwardDiff.get_value(x2[1]))")
#info("master element coordinate 2 = $(ForwardDiff.get_value(x2[2]))")
# calculate segmentation: we care only about endpoints
# note: these are quadratic/cubic functions, analytical solution possible
xi1a = project_from_master_to_slave(slave_element, x1, n1, x2[1])
xi1b = project_from_master_to_slave(slave_element, x1, n1, x2[end])
xi1 = clamp([xi1a; xi1b], -1.0, 1.0)
l = 1/2*abs(xi1[2]-xi1[1])
isapprox(l, 0.0) && continue # no contribution
# integrate slave side
D = zeros(nnodes, nnodes)
Me = zeros(nnodes, nnodes)
for ip in get_integration_points(slave_element, Val{5})
dN = get_dbasis(slave_element, ip)
# jacobian of slave element in deformed state
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N = get_basis(slave_element, xi_s)
D += w*diagm(vec(N))
Me += w*N'*N
end
Ae = D*inv(Me)
# integrate master side
M = zeros(nnodes, nnodes)
for ip in get_integration_points(slave_element, Val{5})
dN = get_dbasis(slave_element, ip)
# jacobian of slave element in deformed state
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_g = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = get_basis(slave_element, xi_g)
x_g = vec(N1)*x1
n_g = vec(N1)*n1
#info("slave gauss point coordinates $(ForwardDiff.get_value(x_g))")
#info("slave gauss point normal direction $(ForwardDiff.get_value(n_g))")
xi_m = project_from_slave_to_master(master_element, x_g, n_g, x2)
N2 = get_basis(master_element, xi_m)
M += w*kron(Ae*N1', N2)
end
slave_dofs = get_gdofs(slave_element, field_dim)
master_dofs = get_gdofs(master_element, field_dim)
for i=1:field_dim
add!(B, slave_dofs[i:field_dim:end], slave_dofs[i:field_dim:end], D)
add!(B, slave_dofs[i:field_dim:end], master_dofs[i:field_dim:end], -M)
end
end
return B
end
function calculate_contact_rhs(x::Vector)
ndofs = round(Int, length(x)/2)
u = x[1:ndofs]
la = x[ndofs+1:end]
# info("calculate_contact_rhs: size of u = $(size(u))")
# info("calculate_contact_rhs: size of X = $(size(X))")
# info("calculate_contact_rhs: size of la = $(size(la))")
# info("calculate_contact_rhs: ndofs = $ndofs")
u2 = reshape(u, field_dim, round(Int, length(u)/field_dim))
normals = calculate_normals(u2)
B = calculate_mortar_projection(u2, normals)
B = sparse(B, ndofs, ndofs)
fc = B' * la # contact force residual for r = fint + fc - fext = 0
N = SparseMatrixCOO{Real}([], [], [])
T = SparseMatrixCOO{Real}([], [], [])
for (i, j) in enumerate(slave_element_nodes)
dofs = [2*(j-1)+1, 2*(j-1)+2]
add!(N, dofs, [dofs[1]], reshape(normals[i], 2, 1))
add!(T, dofs, [dofs[2]], reshape(Q'*normals[i], 2, 1))
end
N = sparse(N, ndofs, ndofs)
T = sparse(T, ndofs, ndofs)
gn = -N*B*(X+u)
gt = -T*B*(X+u)
# gn = -N*B*u
lan = N*la
lat = T*la
cn = 1.0e3
C = lan - max(0, lan - cn*gn) + lat
# C = lan - gn + lat - gt <-- ihan viturallensa
# C = gn+gt <-- not working
# C = B*(X+u)
# C = B*u <- pitää kiinni, "tie".
# C = N*B*u + T*la <- palikat menee väärään suuntaan
# C = -N*B*(X+u) + T*la <- toimii suht hyvin mut kääntyy väärään suuntaan (t-suunnassa)
# C = -N*B*(X+u) - T*la <- sama
# C = -N*B*(X+u) - T*B*la <- sama
# C = N*la + T*la - max(0, N*la + N*B*(X+u))
cond = lan[1:field_dim:end] - cn*gn[1:field_dim:end]
all_nodes = slave_element_nodes
inactive_nodes = find(cond .<= 0)
active_nodes = find(cond .> 0)
inactive_nodes = setdiff(all_nodes, inactive_nodes)
active_nodes = setdiff(all_nodes, active_nodes)
info("S = $all_nodes, I = $inactive_nodes, A = $active_nodes")
info("lambda = $(ForwardDiff.get_value(lan[slave_dofs]))")
info("gn = $(ForwardDiff.get_value(gn[slave_dofs]))")
#for j in active_nodes
# dofs = [field_dim*(j-1)+1, field_dim*(j-1)+2]
# C[dofs] = 0
#end
# C = -(N+T)*B*(X+u)
# C = -B*(X+u)
return [fc; C]
end
A, allresults = ForwardDiff.jacobian(calculate_contact_rhs, x, ForwardDiff.AllResults)
b = -ForwardDiff.value(allresults)
A = sparse(A)
b = sparse(b)
K = A[1:ndofs,1:ndofs]
C1 = A[1:ndofs,ndofs+1:end]'
C2 = A[ndofs+1:end,1:ndofs]
D = A[ndofs+1:end,ndofs+1:end]
f = b[1:ndofs]
g = b[ndofs+1:end]
C2[2:field_dim:end] = 0
g[2:field_dim:end] = 0
# C2[2:field_dim:end] = 0
# g[2:field_dim:end] = 0
# inactives = find(g[1:field_dim:end] .<= 0)
# actives = find(g[1:field_dim:end] .> 0)
# inactives = setdiff(slave_element_nodes, inactives)
# actives = setdiff(slave_element_nodes, actives)
# info("all nodes = $slave_element_nodes, inactives = $inactives, actives = $actives")
# info("g = $g")
# for j in inactives
# dofs = [field_dim*(j-1)+1, field_dim*(j-1)+2]
# K[dofs,:] = 0
# C1[dofs,:] = 0
# C2[dofs,:] = 0
# D[dofs,:] = 0
# g[dofs,:] = 0
#end
#for j in actives
# dofs = [field_dim*(j-1)+1, field_dim*(j-1)+2]
# C2[dofs[1],:] = 0
#end
add!(assembly.K, K)
add!(assembly.C1, C1)
add!(assembly.C2, C2)
add!(assembly.D, D)
add!(assembly.f, f)
add!(assembly.g, g)
end
function assemble!{E<:MortarElements2D}(assembly::Assembly,
problem::Problem{Mortar}, slave_element::Element{E},
time::Real, ::Type{Val{:forwarddiff_old2}})
haskey(slave_element, "master elements") || return
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
function calculate_interface(x::Vector)
ndofs = round(Int, length(x)/2)
nnodes = round(Int, ndofs/field_dim)
u = reshape(x[1:ndofs], field_dim, nnodes)
la = reshape(x[ndofs+1:end], field_dim, nnodes)
fc = zeros(u)
C = zeros(la)
slave_element_nodes = get_connectivity(slave_element)
X1 = slave_element("geometry", time)
u1 = Field(Vector[u[:,i] for i in slave_element_nodes])
la1 = Field(Vector[la[:,i] for i in slave_element_nodes])
x1 = X1 + u1
# 1. update nodal normals for this element. average nodes from adjacent elements
adjacent_elements = find_elements(get_elements(problem), slave_element_nodes)
adjacent_nodes = get_nodes(adjacent_elements) # including also nodes from adjacent elements
Q = [0.0 -1.0; 1.0 0.0]
normals = zeros(u)
for element in adjacent_elements
conn = get_connectivity(element)
gdofs = get_gdofs(element, field_dim)
X_el = element("geometry", time)
u_el = Field(Vector[u[:, i] for i in conn])
x_el = X_el + u_el
for ip in get_integration_points(element, Val{3})
dN = get_dbasis(element, ip)
N = element(ip, time)
t = sum([kron(dN[:,i], x_el[i]') for i=1:length(x_el)])
normals[:, conn] += ip.weight*Q*t'*N
end
end
# --> slave side normals in deformed state
n1 = Field(Vector[normals[:,i]/norm(normals[:,i]) for i in slave_element_nodes])
for master_element in slave_element["master elements"]
master_element_nodes = get_connectivity(master_element)
X2 = master_element("geometry", time)
u2 = Field(Vector[u[:,i] for i in master_element_nodes])
x2 = X2 + u2
# calculate segmentation: we care only about endpoints
# note: these are quadratic/cubic functions, analytical solution possible
xi1a = project_from_master_to_slave(slave_element, x1, n1, x2[1])
xi1b = project_from_master_to_slave(slave_element, x1, n1, x2[end])
xi1 = clamp([xi1a; xi1b], -1.0, 1.0)
l = 1/2*abs(xi1[2]-xi1[1])
isapprox(l, 0.0) && continue # no contribution in this master element
nnodes = size(slave_element, 2)
De = zeros(nnodes, nnodes)
Me = zeros(nnodes, nnodes)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = get_basis(slave_element, xi_s)
De += w*diagm(vec(N1))
Me += w*N1'*N1
end
Ae = De*inv(Me)
slave_dofs = get_gdofs(slave_element, field_dim)
master_dofs = get_gdofs(master_element, field_dim)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
# project gauss point from slave element to master element
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = vec(get_basis(slave_element, xi_s))
x_s = N1*x1 # coordinate in gauss point
n_s = N1*n1 # normal direction in gauss point
t_s = Q'*n_s # tangent direction in gauss point
R_s = [n_s t_s]
xi_m = project_from_slave_to_master(master_element, x_s, n_s, x2)
N2 = vec(get_basis(master_element, xi_m))
x_m = N2*x2
Phi = Ae*N1
u_s = N1*u1
u_m = N2*u2
la_s = Phi*la1 # traction force in gauss point
#lan = dot(n_s, la_s) # normal component
#lat = dot(t_s, la_s) # tangential component
la_nt = R_s*la_s
g = x_s-x_m
gn = props.gap_sign*dot(n_s, g) # normal gap
#gu = dot(n_s, u_s - u_m) # normal displacement gap
#gt = dot(t_s, x_s - x_m) # tangential gap
fc[:,slave_element_nodes] += w*la_s*N1'
fc[:,master_element_nodes] -= w*la_s*N2'
C[1,slave_element_nodes] += w*gn*Phi'
#C[2,slave_element_nodes] += w*la_nt[2]*Phi'
#C[2,slave_element_nodes] += w*la_nt[2,:]*Phi'
#C[2,slave_element_nodes] += w*dot(t_s, u_s - u_m)*Phi' # <-- for tie
#R += w*R_s
end
end # master elements done
for (i, j) in enumerate(slave_element_nodes)
n = n1[i]
t = Q'*n
R = [n t]
la_nt = R*la[:,j]
C[2,j] += la_nt[2]
end
return vec([fc C])
end
# x doesn't mean deformed configuration here
x = [problem.assembly.u; problem.assembly.la]
ndofs = round(Int, length(x)/2)
#if ndofs == 0
# info("INITIALIZING THINGS")
# problem.assembly.u = zeros(16)
# problem.assembly.la = zeros(16)
# x = [problem.assembly.u; problem.assembly.la]
# ndofs = round(Int, length(x)/2)
#end
A, allresults = ForwardDiff.jacobian(calculate_interface, x, ForwardDiff.AllResults)
b = -ForwardDiff.value(allresults)
#b = -calculate_interface(x)
#info("PE = $(ForwardDiff.value(allresults))")
A = sparse(A)
b = sparse(b)
SparseMatrix.droptol!(A, 1.0e-12)
SparseMatrix.droptol!(b, 1.0e-12)
#println(A)
K = A[1:ndofs,1:ndofs]
C1 = transpose(A[1:ndofs,ndofs+1:end])
C2 = A[ndofs+1:end,1:ndofs]
D = A[ndofs+1:end,ndofs+1:end]
f = b[1:ndofs]
g = b[ndofs+1:end]
add!(assembly.K, K)
add!(assembly.C1, C1)
add!(assembly.C2, C2)
add!(assembly.D, D)
add!(assembly.f, f)
add!(assembly.g, g)
return
end
function assemble!{E<:MortarElements2D}(assembly::Assembly,
problem::Problem{Mortar}, slave_element::Element{E},
time::Real, ::Type{Val{:forwarddiff}})
haskey(slave_element, "master elements") || return
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
function calculate_interface(x::Vector)
ndofs = round(Int, length(x)/2)
nnodes = round(Int, ndofs/field_dim)
u = reshape(x[1:ndofs], field_dim, nnodes)
la = reshape(x[ndofs+1:end], field_dim, nnodes)
fc = zeros(u)
C = zeros(la)
slave_element_nodes = get_connectivity(slave_element)
X1 = slave_element("geometry", time)
u1 = Field(Vector[u[:,i] for i in slave_element_nodes])
la1 = Field(Vector[la[:,i] for i in slave_element_nodes])
x1 = X1 + u1
# 1. update nodal normals for this element. average nodes from adjacent elements
adjacent_elements = find_elements(get_elements(problem), slave_element_nodes)
adjacent_nodes = get_nodes(adjacent_elements) # including also nodes from adjacent elements
Q = [0.0 -1.0; 1.0 0.0]
normals = zeros(u)
for element in adjacent_elements
conn = get_connectivity(element)
gdofs = get_gdofs(element, field_dim)
X_el = element("geometry", time)
u_el = Field(Vector[u[:, i] for i in conn])
x_el = X_el + u_el
for ip in get_integration_points(element, Val{3})
dN = get_dbasis(element, ip)
N = element(ip, time)
t = sum([kron(dN[:,i], x_el[i]') for i=1:length(x_el)])
normals[:, conn] += ip.weight*Q*t'*N
end
end
# --> slave side normals in deformed state
n1 = Field(Vector[ForwardDiff.get_value(normals[:,i]/norm(normals[:,i])) for i in slave_element_nodes])
nnodes = size(slave_element, 2)
lan_tot = zeros(nnodes) # normal pressure
gap_tot = zeros(nnodes) # weighted normal gap
for master_element in slave_element["master elements"]
master_element_nodes = get_connectivity(master_element)
X2 = master_element("geometry", time)
u2 = Field(Vector[u[:,i] for i in master_element_nodes])
x2 = X2 + u2
# calculate segmentation: we care only about endpoints
# note: these are quadratic/cubic functions, analytical solution possible
xi1a = project_from_master_to_slave(slave_element, x1, n1, x2[1])
xi1b = project_from_master_to_slave(slave_element, x1, n1, x2[end])
xi1 = clamp([xi1a; xi1b], -1.0, 1.0)
l = 1/2*abs(xi1[2]-xi1[1])
isapprox(l, 0.0) && continue # no contribution in this master element
De = zeros(nnodes, nnodes)
Me = zeros(nnodes, nnodes)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = get_basis(slave_element, xi_s)
De += w*diagm(vec(N1))
Me += w*N1'*N1
end
Ae = De*inv(Me)
slave_dofs = get_gdofs(slave_element, field_dim)
master_dofs = get_gdofs(master_element, field_dim)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
# project gauss point from slave element to master element
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = vec(get_basis(slave_element, xi_s))
x_s = N1*x1 # coordinate in gauss point
n_s = N1*n1 # normal direction in gauss point
t_s = Q'*n_s # tangent direction in gauss point
R_s = [n_s t_s]
xi_m = project_from_slave_to_master(master_element, x_s, n_s, x2)
N2 = vec(get_basis(master_element, xi_m))
x_m = N2*x2
Phi = Ae*N1
u_s = N1*u1
u_m = N2*u2
la_s = Phi*la1 # traction force in gauss point
la_nt = R_s*la_s
gn = -dot(n_s, x_s - x_m) # normal gap
fc[:,slave_element_nodes] += w*la_s*N1'
fc[:,master_element_nodes] -= w*la_s*N2'
#C[1,slave_element_nodes] += w*gn*Phi'
lan_tot += w*la_nt[1]*Phi
gap_tot += w*gn*Phi
end
end # master elements done
# ncf = lan_tot - max(0, lan_tot - gap_tot)
# info("pressure in nodes: $(ForwardDiff.get_value(lan_tot))")
# info("weighted gap in nodes: $(ForwardDiff.get_value(gap_tot))")
# info("ncf: $(ForwardDiff.get_value(ncf))")
# cond = +lan_tot + gap_tot
# cond = +lan_tot - gap_tot # singular
# cond = -lan_tot + gap_tot
# cond = -lan_tot - gap_tot
for (i, j) in enumerate(slave_element_nodes)
n = n1[i]
t = Q'*n
R = [n t]
la_nt = R*la[:,j]
info("node $j, n=$(ForwardDiff.get_value(n)) lan = $(ForwardDiff.get_value(la_nt[1])) gap = $(ForwardDiff.get_value(gap_tot[i]))")
# if -lan_tot[i] + gap_tot[i] < 0
if -la_nt[1] + gap_tot[i] < 0
#if j in [31, 32, 33, 34, 35, 36]
info("set node $j active")
C[1,j] -= gap_tot[i]
# C[1,j] += la_nt[1] - max(0, la_nt[1] - gap_tot[i])
C[2,j] += la_nt[2]
else
info("set node $j inactive")
C[1,j] += la1[i][1]
C[2,j] += la1[i][2]
end
end
return vec([fc C])
end
# x doesn't mean deformed configuration here
x = [problem.assembly.u; problem.assembly.la]
ndofs = round(Int, length(x)/2)
A, allresults = ForwardDiff.jacobian(calculate_interface, x, ForwardDiff.AllResults)
b = -ForwardDiff.value(allresults)
#b = -calculate_interface(x)
#info("PE = $(ForwardDiff.value(allresults))")
A = sparse(A)
b = sparse(b)
SparseMatrix.droptol!(A, 1.0e-12)
SparseMatrix.droptol!(b, 1.0e-12)
#println(A)
K = A[1:ndofs,1:ndofs]
C1 = transpose(A[1:ndofs,ndofs+1:end])
C2 = A[ndofs+1:end,1:ndofs]
D = A[ndofs+1:end,ndofs+1:end]
f = b[1:ndofs]
g = b[ndofs+1:end]
add!(assembly.K, K)
add!(assembly.C1, C1)
add!(assembly.C2, C2)
add!(assembly.D, D)
add!(assembly.f, f)
add!(assembly.g, g)
return
end
+643
View File
@@ -0,0 +1,643 @@
# 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_point_to_auxiliary_plane(p::Vector, x0::Vector, Q::Matrix)
n = Q[:,1]
ph = p - dot(p-x0, n)*n
qproj = Q'*(ph-x0)
if !isapprox(qproj[1], 0.0; atol=1.0e-12)
info("project_point_to_auxiliary_plane(): point not projected correctly.")
info("p: $p")
info("x0: $x0")
info("Q: \n$Q")
info("qproj: $qproj")
error("Failed to project point to auxiliary plane.")
end
return qproj[2:3]
end
"""
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(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{Float64}[P[:,i] for i=1:size(P,dim)]
new_items = Vector{Float64}[]
for item in items
has_found = false
for new_item in new_items
if isapprox(item, 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 Float64[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)
basis(xi) = get_basis(E, xi)
dbasis(xi) = get_dbasis(E, xi)
x = element("geometry", time)
ph = Q*[0; p] + x0
theta = Float64[0.0, 0.0, 0.0]
n = Q[:,1]
for i=1:max_iterations
b = ph + theta[1]*n - basis(theta[2:3])*x
J = [n -dbasis(theta[2:3])*x]
dtheta = J \ -b
theta += dtheta
if norm(dtheta) < iter_tol
return theta
end
end
begin
info("projecting point from auxiliary plane back to surface didn't go very well.")
info("element type: $E")
info("element connectivity: $(get_connectivity(element))")
info("auxiliary plane: x0 = $x0, Q = $Q")
info("point coordinates on plane: $p")
info("element geometry: $x")
info("ph: $ph")
info("normal direction: $n")
info("parameter vector before giving up: $theta")
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
+446
View File
@@ -0,0 +1,446 @@
using JuliaFEM.Core: MortarElements2D, DVTI, Assembly
import JuliaFEM.Core: project_from_master_to_slave, project_from_slave_to_master, assemble!,
get_unknown_field_dimension, get_parent_field_name, get_gdofs, find_elements, get_nodes, Field,
get_integration_points, get_basis, get_dbasis, add!
""" Find segment from slave element corresponding to master element nodes.
x1_, n1_
slave element geometry and normal direction
x2_ master element nodes to project onto slave
"""
function project_from_master_to_slave{E<:MortarElements2D}(
slave_element::Element{E}, x1_::DVTI, n1_::DVTI, x2::Vector)
function x1(xi1)
N = get_basis(E, xi1)
return vec(N)*x1_
end
function dx1(xi1)
dN = get_dbasis(E, xi1)
return vec(dN)*x1_
end
function n1(xi1)
N = get_basis(E, xi1)
return vec(N)*n1_
end
function dn1(xi1)
dN = get_dbasis(E, xi1)
return vec(dN)*n1_
end
cross2(a, b) = cross([a; 0], [b; 0])[3]
R(xi1) = cross2(x1(xi1)-x2, n1(xi1))
dR(xi1) = cross2(dx1(xi1), n1(xi1)) + cross2(x1(xi1)-x2, dn1(xi1))
xi1 = 0.0
for i=1:5
dxi1 = -R(xi1)/dR(xi1)
xi1 += dxi1
if norm(dxi1) < 1.0e-10
return xi1
end
end
error("find projection from master to slave: did not converge")
end
function project_from_slave_to_master{E<:MortarElements2D}(
master_element::Element{E}, x1::Vector, n1::Vector, x2_::DVTI)
function x2(xi2)
N = get_basis(E, xi2)
return vec(N)*x2_
end
function dx2(xi2)
dN = get_dbasis(E, xi2)
return vec(dN)*x2_
end
cross2(a, b) = cross([a; 0], [b; 0])[3]
R(xi2) = cross2(x2(xi2)-x1, n1)
dR(xi2) = cross2(dx2(xi2), n1)
xi2 = 0.0
dxi2 = 0.0
for i=1:5
dxi2 = -R(xi2) / dR(xi2)
xi2 += dxi2
if norm(dxi2) < 1.0e-10
return xi2
end
end
error("find projection from slave to master: did not converge, last val: $xi2 and $dxi2")
end
function assemble!{E<:MortarElements2D}(assembly::Assembly,
problem::Problem{Mortar}, slave_element::Element{E},
time::Real, ::Type{Val{:forwarddiff}})
haskey(slave_element, "master elements") || return
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
function calculate_interface(u::Matrix, la::Matrix)
X1 = slave_element("geometry", time)
slave_element_nodes = get_connectivity(slave_element)
u1 = Field(Vector[u[:,i] for i in slave_element_nodes])
la1 = Field(Vector[la[:,i] for i in slave_element_nodes])
x1 = X1 + u1
adjacent_elements = find_elements(get_elements(problem), slave_element_nodes)
adjacent_nodes = get_nodes(adjacent_elements) # including also nodes from adjacent elements
Q = [0.0 -1.0; 1.0 0.0]
# 1. update nodal normals for this element
normals = zeros(u)
for element in adjacent_elements
conn = get_connectivity(element)
gdofs = get_gdofs(element, field_dim)
X_el = element("geometry", time)
u_el = Field(Vector[u[:, i] for i in conn])
x_el = X_el + u_el
for ip in get_integration_points(element, Val{3})
dN = get_dbasis(element, ip)
N = element(ip, time)
t = sum([kron(dN[:,i], x_el[i]') for i=1:length(x_el)])
normals[:, conn] += ip.weight*Q*t'*N
end
end
# --> slave side normals in deformed state
n1 = Field(Vector[normals[:,i]/norm(normals[:,i]) for i in slave_element_nodes])
fc = SparseMatrixCOO{Real}([], [], []) # interface virtual work
C = SparseMatrixCOO{Real}([], [], []) # constraints
B = SparseMatrixCOO{Real}([], [], [])
#info("u1.data = ", ForwardDiff.get_value(u1.data))
info("normal calculations done. looping master elements.")
for master_element in slave_element["master elements"]
X2 = master_element("geometry", time)
master_element_nodes = get_connectivity(master_element)
u2 = Field(Vector[u[:,i] for i in master_element_nodes])
x2 = X2 + u2
info("master element ready.")
# calculate segmentation: we care only about endpoints
# note: these are quadratic/cubic functions, analytical solution possible
info("calculating segmentation.")
xi1a = project_from_master_to_slave(slave_element, x1, n1, x2[1])
xi1b = project_from_master_to_slave(slave_element, x1, n1, x2[end])
xi1 = clamp([xi1a; xi1b], -1.0, 1.0)
l = 1/2*abs(xi1[2]-xi1[1])
isapprox(l, 0.0) && continue # no contribution
info("xi1 = $xi1")
info("create bi-orthogonal basis")
nnodes = size(slave_element, 2)
De = zeros(nnodes, nnodes)
Me = zeros(nnodes, nnodes)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = get_basis(slave_element, xi_s)
De += w*diagm(vec(N1))
Me += w*N1'*N1
end
Ae = De*inv(Me)
info("bi-orthogonal basis done. integrating fc.")
slave_dofs = get_gdofs(slave_element, field_dim)
master_dofs = get_gdofs(master_element, field_dim)
info("integrate fc")
D = zeros(nnodes, nnodes)
M = zeros(nnodes, nnodes)
gn = zeros(nnodes)
lan = zeros(nnodes)
lat = zeros(nnodes)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = get_basis(slave_element, xi_s)
# project gauss point to master element to evaluate shape function there
x_s = vec(N1)*x1 # coordinate in gauss point
n_s = vec(N1)*n1 # normal direction in gauss point
xi_m = project_from_slave_to_master(master_element, x_s, n_s, x2)
N2 = get_basis(master_element, xi_m)
x_m = vec(N2)*x2
Phi = vec(Ae*N1')
la_s = Phi*la1 # traction force in gauss point
u_s = vec(N1)*u1
u_m = vec(N2)*u2
#info("la_s = $(ForwardDiff.get_value(la_s))")
SM = [slave_dofs; master_dofs]
#N1N2 = [N1 -N2]
#info("all_dofs = $(SM)")
#info("shape functions = $(ForwardDiff.get_value(N1N2))")
#for i=1:field_dim
# #info("add to slave dofs $(slave_dofs[i:field_dim:end])")
# #info("add to master dofs $(master_dofs[i:field_dim:end])")
# add!(fc, slave_dofs[i:field_dim:end], [1, 1], -w*la_s[i]*N1)
# add!(fc, master_dofs[i:field_dim:end], [1, 1], +w*la_s[i]*N2)
#add!(fc, slave_dofs[i:field_dim:end], [1, 1], w*la_s'*u_s[i])
#add!(fc, master_dofs[i:field_dim:end], [1, 1], -w*la_s'*u_m[i])
# add!(fc, master_dofs[i:field_dim:end], [1, 1], -w*la_s[i]*u_m)
#end
#add!(fc, [slave_dofs; master_dofs], [1, 1, 1, 1, 1, 1, 1, 1], w*la_s*[u_s' -u_m'])
D += w*kron(Ae*N1', N1)
M += w*kron(Ae*N1', N2)
gn += -w*dot(n_s, x_s-x_m)*Phi
lan += w*dot(n_s, la_s)*Phi
t_s = Q'*n_s
lat += w*dot(t_s, la_s)*Phi
end
#D2 = zeros(2*nnodes, 2*nnodes)
#M2 = zeros(2*nnodes, 2*nnodes)
#for i=1:field_dim
# D2[i:field_dim:end, i:field_dim:end] += D
# M2[i:field_dim:end, i:field_dim:end] += M
#end
#info("size of D2 = $(size(D2))")
#fco = [D2 -M2]*vec(la1)
#fco = [D -M]*la[:,slave_element_nodes]
#info("fco = $(ForwardDiff.get_value(fco))")
#add!(fc, [slave_dofs; master_dofs], [1, 1, 1, 1], fco)
for i=1:field_dim
add!(B, slave_dofs[i:field_dim:end], slave_dofs[i:field_dim:end], D)
add!(B, slave_dofs[i:field_dim:end], master_dofs[i:field_dim:end], -M)
end
info("gn = $gn")
#Cj = lan - max(0, lan - gn) + lat
add!(C, slave_dofs[1:field_dim:end], [1, 1], gn')
end # master elements done
ndofs = prod(size(la))
N = SparseMatrixCOO{Real}([], [], [])
T = SparseMatrixCOO{Real}([], [], [])
for (i, j) in enumerate(slave_element_nodes)
dofs = [2*(j-1)+1, 2*(j-1)+2]
add!(N, [dofs[1]], dofs, reshape(n1[i], 1, 2))
add!(T, [dofs[2]], dofs, reshape(Q'*n1[i], 1, 2))
end
N = sparse(N, ndofs, ndofs)
T = sparse(T, ndofs, ndofs)
B = sparse(B, ndofs, ndofs)
fc = B'*vec(la)
#println(sparse(fc))
#fc = sparse(fc, ndofs, 1)
#println(fc)
#dump(full(fc))
#C = sparse(C, ndofs, 1)
C = N*B*vec(u) + T*vec(la)
return fc, C
end
function calculate_interface_PE(x::Vector)
ndofs = round(Int, length(x)/2)
nnodes = round(Int, ndofs/field_dim)
u = reshape(x[1:ndofs], field_dim, nnodes)
la = reshape(x[ndofs+1:end], field_dim, nnodes)
#fixed_la = ForwardDiff.get_value(la)
#fixed_u = ForwardDiff.get_value(u)
#u = ForwardDiff.get_value(u)
X1 = slave_element("geometry", time)
slave_element_nodes = get_connectivity(slave_element)
u1 = Field(Vector[u[:,i] for i in slave_element_nodes])
la1 = Field(Vector[la[:,i] for i in slave_element_nodes])
#fixed_la1 = Field(Vector[fixed_la[:,i] for i in slave_element_nodes])
x1 = X1 + u1
# 1. update nodal normals for this element
adjacent_elements = find_elements(get_elements(problem), slave_element_nodes)
adjacent_nodes = get_nodes(adjacent_elements) # including also nodes from adjacent elements
Q = [0.0 -1.0; 1.0 0.0]
normals = zeros(u)
for element in adjacent_elements
conn = get_connectivity(element)
gdofs = get_gdofs(element, field_dim)
X_el = element("geometry", time)
u_el = Field(Vector[u[:, i] for i in conn])
x_el = X_el + u_el
for ip in get_integration_points(element, Val{3})
dN = get_dbasis(element, ip)
N = element(ip, time)
t = sum([kron(dN[:,i], x_el[i]') for i=1:length(x_el)])
normals[:, conn] += ip.weight*Q*t'*N
end
end
# --> slave side normals in deformed state
n1 = Field(Vector[normals[:,i]/norm(normals[:,i]) for i in slave_element_nodes])
Wco = 0.0
Wla = 0.0
for master_element in slave_element["master elements"]
X2 = master_element("geometry", time)
master_element_nodes = get_connectivity(master_element)
u2 = Field(Vector[u[:,i] for i in master_element_nodes])
x2 = X2 + u2
# calculate segmentation: we care only about endpoints
# note: these are quadratic/cubic functions, analytical solution possible
xi1a = project_from_master_to_slave(slave_element, x1, n1, x2[1])
xi1b = project_from_master_to_slave(slave_element, x1, n1, x2[end])
xi1 = clamp([xi1a; xi1b], -1.0, 1.0)
l = 1/2*abs(xi1[2]-xi1[1])
isapprox(l, 0.0) && continue # no contribution
nnodes = size(slave_element, 2)
De = zeros(nnodes, nnodes)
Me = zeros(nnodes, nnodes)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = get_basis(slave_element, xi_s)
De += w*diagm(vec(N1))
Me += w*N1'*N1
end
Ae = De*inv(Me)
for ip in get_integration_points(slave_element, Val{5})
# jacobian of slave element in deformed state
dN = get_dbasis(slave_element, ip)
j = sum([kron(dN[:,i], x1[i]') for i=1:length(x1)])
w = ip.weight*norm(j)*l
xi_s = dot([1/2*(1-ip.xi); 1/2*(1+ip.xi)], xi1)
N1 = vec(get_basis(slave_element, xi_s))
# project gauss point to master element to evaluate shape function there
x_s = N1*x1 # coordinate in gauss point
n_s = N1*n1 # normal direction in gauss point
t_s = Q'*n_s
xi_m = project_from_slave_to_master(master_element, x_s, n_s, x2)
N2 = vec(get_basis(master_element, xi_m))
x_m = N2*x2
Phi = Ae*N1
gn = -dot(n_s, x_s - x_m)
gt = dot(t_s, x_s - x_m)
lan = dot(n_s, Phi*la1)
lat = dot(t_s, Phi*la1)
u_s = N1*u1
u_m = N2*u2
gu = dot(n_s, u_s - u_m)
Wco += w*dot(Phi*la1, N1*u1 - N2*u2)
#Wla += w*(lan*gn + lat*gt)
#gn = min(0, gn)
Wla += 1/2*w*1e6*gn*gn
#info("gn = $(ForwardDiff.get_value(gn))")
#Wla += w*dot(dot(n_s, Phi*la1), dot(n_s, N1*u1 - N2*u2))
end
end
return Wco, Wla
end
function calculate_contact_rhs(x::Vector)
ndofs = round(Int, length(x)/2)
nnodes = round(Int, ndofs/field_dim)
u = reshape(x[1:ndofs], field_dim, nnodes)
la = reshape(x[ndofs+1:end], field_dim, nnodes)
fc, C = calculate_interface(u, la)
info("interface vector calculated.")
return vec(full([fc; C]))
end
# x doesn't mean deformed configuration here
x = [problem.assembly.u; problem.assembly.la]
ndofs = round(Int, length(x)/2)
if ndofs == 0
info("INITIALIZING THINGS")
problem.assembly.u = zeros(16)
problem.assembly.la = zeros(16)
x = [problem.assembly.u; problem.assembly.la]
ndofs = round(Int, length(x)/2)
end
function add_fco!()
get_PI(x::Vector) = calculate_interface_PE(x)[1]
A, allresults = ForwardDiff.hessian(get_PI, x, ForwardDiff.AllResults)
b = -ForwardDiff.gradient(allresults)
info("PE = $(ForwardDiff.value(allresults))")
A = sparse(A)
b = sparse(b)
SparseMatrix.droptol!(A, 1.0e-12)
SparseMatrix.droptol!(b, 1.0e-12)
K = A[1:ndofs,1:ndofs]
C1 = transpose(A[1:ndofs,ndofs+1:end])
C2 = A[ndofs+1:end,1:ndofs]
D = A[ndofs+1:end,ndofs+1:end]
f = b[1:ndofs]
g = b[ndofs+1:end]
add!(assembly.K, K)
add!(assembly.C1, C1)
add!(assembly.C2, C2)
add!(assembly.D, D)
add!(assembly.f, f)
add!(assembly.g, g)
end
#add_fco!()
function add_wla!()
get_PI(x::Vector) = calculate_interface_PE(x)[2]
A, allresults = ForwardDiff.hessian(get_PI, x, ForwardDiff.AllResults)
b = -ForwardDiff.gradient(allresults)
info("PE = $(ForwardDiff.value(allresults))")
A = sparse(A)
b = sparse(b)
SparseMatrix.droptol!(A, 1.0e-12)
SparseMatrix.droptol!(b, 1.0e-12)
#info("A")
#println(full(A))
K = A[1:ndofs,1:ndofs]
C1 = transpose(A[1:ndofs,ndofs+1:end])
C2 = A[ndofs+1:end,1:ndofs]
D = A[ndofs+1:end,ndofs+1:end]
f = b[1:ndofs]
g = b[ndofs+1:end]
add!(assembly.K, K)
add!(assembly.C1, C1)
add!(assembly.C2, C2)
add!(assembly.D, D)
add!(assembly.f, f)
add!(assembly.g, g)
end
add_wla!()
return
end
mesh, body1, body2, bc_top, bc_bottom, contact = divided_block_problem()
bc_top.properties.formulation = :incremental
bc_bottom.properties.formulation = :incremental
contact.properties.formulation = :forwarddiff
contact.assembly.u = zeros(16)
contact.assembly.la = zeros(16)
assemble!(contact.assembly, contact, contact.elements[1], 0.0, Val{:forwarddiff})
+13 -7
View File
@@ -166,16 +166,22 @@ function update_assembly!(problem, u, la)
# copy current solutions to previous ones and add/replace new solution
assembly.u_prev = copy(assembly.u)
assembly.la_prev = copy(assembly.la)
if get_formulation_type(problem) == :incremental
info("$(problem.name): incremental formulation, adding increment to solution vector")
#info("solution vector:")
#dump(round(u, 3)')
assembly.u += u
else
if get_formulation_type(problem) == :total
info("$(problem.name): total formulation, replacing solution vector with new values")
assembly.u = u
assembly.la = la
elseif get_formulation_type(problem) == :incremental
info("$(problem.name): incremental formulation, adding increment to solution vector")
assembly.u += u
assembly.la = la
elseif get_formulation_type(problem) == :forwarddiff
info("$(problem.name): forwarddiff formulation, adding increment to solution vector")
assembly.u += u
assembly.la += la
else
info("$(problem.name): unknown formulation type, don't know what to do with results")
error("serious failure with problem formulation: $(get_formulation_type(problem))")
end
assembly.la = la
# calculate change of norm
assembly.u_norm_change = norm(assembly.u - assembly.u_prev)
+17 -4
View File
@@ -141,9 +141,11 @@ type Solver
name :: ASCIIString # some descriptive name for problem
time :: Real # current time
iteration :: Int # iteration counter
norms :: Vector{Tuple} # solution norms for convergence studies
ndofs :: Int # total dimension of global stiffness matrix, i.e., dim*nnodes
problems :: Vector{Problem}
is_linear_system :: Bool # setting this to true makes assumption of one step convergence
nonlinear_system_min_iterations :: Int64
nonlinear_system_max_iterations :: Int64
nonlinear_system_convergence_tolerance :: Float64
nonlinear_system_error_if_no_convergence :: Bool
@@ -155,9 +157,11 @@ function Solver(name::ASCIIString="default solver", time::Real=0.0)
name,
time,
0, # iteration #
[], # solution norms in (norm(u), norm(la)) tuples
0, # ndofs
[], # array of problems
false, # is_linear_system
1, # min nonlinear iterations
10, # max nonlinear iterations
5.0e-5, # nonlinear iteration convergence tolerance
true, # throw error if no convergence
@@ -277,12 +281,14 @@ crosspoints.
function get_boundary_assembly(solver::Solver)
ndofs = solver.ndofs
@assert ndofs != 0
Kc = spzeros(ndofs, ndofs)
C1 = spzeros(ndofs, ndofs)
C2 = spzeros(ndofs, ndofs)
D = spzeros(ndofs, ndofs)
g = spzeros(ndofs, 1)
for problem in get_boundary_problems(solver)
assembly = problem.assembly
Kc_ = sparse(assembly.K, ndofs, ndofs)
C1_ = sparse(assembly.C1, ndofs, ndofs)
C2_ = sparse(assembly.C2, ndofs, ndofs)
D_ = sparse(assembly.D, ndofs, ndofs)
@@ -297,12 +303,13 @@ function get_boundary_assembly(solver::Solver)
handle_overconstraint_error!(problem, overconstrained_nodes,
overconstrained_dofs, C1, C1_, C2, C2_, D, D_, g, g_)
end
Kc += Kc_
C1 += C1_
C2 += C2_
D += D_
g += g_
end
return C1, C2, D, g
return Kc, C1, C2, D, g
end
@@ -316,10 +323,10 @@ function solve_linear_system(solver::Solver, ::Type{Val{:DirectLinearSolver}})
K, f = get_field_assembly(solver)
# assemble boundary problems
C1, C2, D, g = get_boundary_assembly(solver)
Kc, C1, C2, D, g = get_boundary_assembly(solver)
# construct global system Ax=b and solve using lu factorization
A = [K C1'; C2 D]
A = [K+Kc C1'; C2 D]
b = [f; g]
nz = get_nonzero_rows(A)
@@ -379,6 +386,7 @@ end
""" Main solver loop.
"""
function call(solver::Solver)
# 1. initialize each problem so that we can start nonlinear iterations
for problem in solver.problems
initialize!(problem, solver.time)
@@ -394,6 +402,7 @@ function call(solver::Solver)
# 2.2 call solver for linearized system (default: direct lu factorization)
u, la = solve_linear_system(solver, Val{solver.linear_system_solver})
push!(solver.norms, (norm(u), norm(la)))
# 2.3 update solution back to elements
for problem in solver.problems
@@ -404,7 +413,11 @@ function call(solver::Solver)
# 2.4 check convergence
if has_converged(solver)
info("Converged in $(solver.iteration) iterations.")
return true
if solver.iteration < solver.nonlinear_system_min_iterations
info("Converged but continuing")
else
return true
end
end
end
+17 -8
View File
@@ -4,18 +4,22 @@
# Sparse utils to make assembly of local and global matrices easier.
# Unoptimized but should do all necessary stuff for at start.
type SparseMatrixCOO
type SparseMatrixCOO{T<:Real}
I :: Vector{Int}
J :: Vector{Int}
V :: Vector{Float64}
V :: Vector{T}
end
typealias SparseMatrixIJV SparseMatrixCOO
function SparseMatrixCOO()
SparseMatrixCOO([], [], [])
SparseMatrixCOO{Float64}([], [], [])
end
#function SparseMatrixCOO{T}()
# SparseMatrixCOO{T}([], [], [])
#end
function Base.convert(::Type{SparseMatrixCOO}, A::SparseMatrixCSC)
return SparseMatrixCOO(findnz(A)...)
end
@@ -80,7 +84,7 @@ function Base.(:+)(A::SparseMatrixIJV, B::SparseMatrixIJV)
return C
end
function Base.full(A::SparseMatrixIJV, args...)
function Base.full(A::SparseMatrixCOO, args...)
return full(sparse(A.I, A.J, A.V, args...))
end
@@ -94,7 +98,7 @@ Example
>>> S = [3, 4]
>>> M = [6, 7, 8]
>>> data = Float64[5 6 7; 8 9 10]
>>> A = SparseMatrixIJV()
>>> A = SparseMatrixCOO()
>>> add!(A, S, M, data)
>>> full(A)
4x8 Array{Float64,2}:
@@ -104,7 +108,7 @@ Example
0.0 0.0 0.0 0.0 0.0 8.0 9.0 10.0
"""
function add!(A::SparseMatrixIJV, dofs1::Vector{Int}, dofs2::Vector{Int}, data::Matrix{Float64})
function add!(A::SparseMatrixCOO, dofs1::Vector{Int}, dofs2::Vector{Int}, data::Matrix)
n, m = size(data)
for j=1:m
for i=1:n
@@ -112,11 +116,16 @@ function add!(A::SparseMatrixIJV, dofs1::Vector{Int}, dofs2::Vector{Int}, data::
push!(A.J, dofs2[j])
end
end
# append!(A.I, repeat(dofs1, outer=[m]))
# append!(A.J, repeat(dofs2, inner=[n]))
append!(A.V, vec(data))
end
""" Add sparse matrix of CSC to COO. """
function add!(A::SparseMatrixCOO, B::SparseMatrixCSC)
I, J, V = findnz(B)
C = SparseMatrixCOO(I, J, V)
append!(A, C)
end
""" Add new data to COO Sparse vector. """
function add!(A::SparseMatrixCOO, dofs::Vector{Int}, data::Array{Float64}, dim::Int=1)
if length(dofs) != length(data)
+2 -2
View File
@@ -73,11 +73,11 @@ function xdmf_new_grid(temporal_collection; time=0)
return grid
end
function xdmf_new_mesh!(grid, nodes, elements)
function xdmf_new_mesh!(grid, nodes, elements; datatype="XYZ")
# 1. write nodes
geometry = new_child(grid, "Geometry")
set_attribute(geometry, "Type", "XYZ")
set_attribute(geometry, "Type", datatype)
dataitem = new_child(geometry, "DataItem")
set_attribute(dataitem, "DataType", "Float")
ndim = sum([length(node) for node in nodes])