Bug/mortar discretization (#88)

* new mortar segmentation tests which are failing

* test_problems_mortar_3d.jl: first test (Tet4) pass

* solvers.jl: diagonal of A is now properly filled, if that option is used. Another option is to remove zero rows from matrix system, which is on by default

* problems_mortar.jl: added new function diagnose_interface to calculate quantities from interface hopefully revealing bugs in calculation

* problems_mortar_3d.jl: added docstring for check_orientation! and removed flooding debug messages not helping to debug anything

* solvers.jl: Another way to solve Ax = b

* Refactored code to make implementation of Tri6 assemble! easier

* Patch test with linear Tet4 elements and quadratic Tet10 elements pass

When using quadratic elements, in polygon clipping algorithm element is divided to linear sub-elements as proposed in [Puso2008]. Interpolation of Lagrange multiplier space is done using quadratic shape functions.

References
----------

[Puso2008] Puso, Michael A., T. A. Laursen, and Jerome Solberg. "A segment-to-segment mortar contact method for quadratic elements and large deformations." Computer Methods in Applied Mechanics and Engineering 197.6 (2008): 555-566.

* increased coverage by adding diagnose_interface

* test using dual basis, failing for unknown reason

* Fixed dual basis construction for Mortar/Tet4

The coefficient matrix Ae for one particular slave element e is the result performing numerical integration on *all* integration cells associated with this element [Popp2013]. Ae cannot be calculated "cell-wise" like it was done before. Now patch test will pass also using `interface.properties.dual_basis = true` option. Partially integrated slave elements are supported as well.

References
----------

[Popp2013] Popp, Alexander, et al. "Improved robustness and consistency of 3D contact algorithms based on a dual mortar approach." Computer Methods in Applied Mechanics and Engineering 264 (2013): 67-80.

* Minor modifications to preprocess.jl

- removed two functions which are unimplemented (but maybe planned in future)
- added function create_node_set_from_element_set!, which can be used, like name suggests, to create a node set from nodes belonging to some set of elements.

* solvers.jl: now prints a list of overconstrained nodes which can be easily copy-pasted to problem.assembly.removed_dofs list to solver overconstrained situation manually

* Increase code coverage

Added a new test which tests dual basis 3d mortar + adjust option when using Tet4 in elasticity problem.

* Tet10 + Dual basis still failing, others are working

* mortar 3d low level tests

* linear surface element projection tests pass

* Introduced basis transform constant alpha

Tet10 + dual basis patch test still failing, but single element low level routine tests gives expected results with alpha=0.2

* added new integration rule FPG12 for triangular elements

* added drop_tolerance option to remove very small values from constraint matrices

* Introduced a basis transform matrix T

Constructing bi-orthogonal basis for quadratic surfaces is ill-conditioned. By doing a basis transform N' = N*T for slave side displacement vector it's possible to construct a bi-orthogonal basis in a same way than with linear elements. Setting alpha=0.2 ensures that quadratic basis functions are strictly positive in practical cases.

* fix 3d clipping test routine, accepts only 3d vertices

* dropped number of integration poitns from 12 to 7 in quadratic mortar surfaces intrestingly gives more accurate results, maybe something numerical error in FPG12 integration rule..?

* added two displacement patch tests + output writing for all cases

* %s/Int64/Int/g

* Changed test data location

* Fine tuning of logging levels
This commit is contained in:
Jukka Aho
2017-02-25 18:40:14 +02:00
committed by Tero Frondelius
parent a5c093c1d6
commit f275ce3767
10 changed files with 2026 additions and 264 deletions
+194 -3
View File
@@ -42,12 +42,14 @@ type Mortar <: BoundaryProblem
linear_surface_elements :: Bool
split_quadratic_slave_elements :: Bool
split_quadratic_master_elements :: Bool
alpha :: Float64
drop_tolerance :: Float64
store_fields :: Vector{Symbol}
end
function Mortar()
default_fields = []
return Mortar(-1, false, false, false, false, Inf, true, true, true, default_fields)
return Mortar(-1, false, false, false, false, Inf, true, true, true, 0.0, 1.0e-9, default_fields)
end
function get_unknown_field_name(problem::Problem{Mortar})
@@ -63,13 +65,202 @@ function get_formulation_type(problem::Problem{Mortar})
end
function assemble!(problem::Problem{Mortar}, time::Float64)
if length(problem.elements) == 0
warn("No elements defined in interface $(problem.name), this will result empty assembly!")
return
end
if problem.properties.dimension == -1
problem.properties.dimension = dim = size(first(problem.elements), 1)
info("assuming dimension of mesh tie surface is $dim")
info("if this is wrong set is manually using problem.properties.dimension")
info("Assuming dimension of mesh tie surface is $dim. If this is wrong set is manually using problem.properties.dimension")
end
dimension = Val{problem.properties.dimension}
use_forwarddiff = Val{problem.properties.use_forwarddiff}
assemble!(problem, time, dimension, use_forwarddiff)
end
""" Given a CCW ordered set of vertices, calculate area of polygon.
Examples
--------
julia> P = Vector[[1/3, 5/12, 1/2], [1/3, 1/2, 1/2], [1/2, 1/2, 1/2], [1/2, 1/3, 1/2], [5/12, 1/3, 1/2]]
5-element Array{Array{T,1},1}:
[0.333333,0.416667,0.5]
[0.333333,0.5,0.5]
[0.5,0.5,0.5]
[0.5,0.333333,0.5]
[0.416667,0.333333,0.5]
julia> A = calculate_polygon_area(P)
0.02430555555555556
julia> isapprox(A, 7/288)
true
"""
function calculate_polygon_area(P)
N_P = length(P)
A = sum([norm(1/2*cross(P[i]-P[1], P[mod(i,N_P)+1]-P[1])) for i=2:N_P])
return A
end
""" Function to print useful debug information from interface to find bugs. """
function diagnose_interface(problem::Problem{Mortar}, time::Float64)
info("Diagnosing Mortar interface...")
props = problem.properties
field_dim = get_unknown_field_dimension(problem)
field_name = get_parent_field_name(problem)
slave_elements = get_slave_elements(problem)
I_area = 0.0
if props.split_quadratic_slave_elements
info("props.split_quadratic_slave_elements = true")
if !props.linear_surface_elements
warn("Mortar3D: split_quadratic_surfaces = true and linear_surface_elements = false maybe have unexpected behavior")
end
slave_elements = split_quadratic_elements(slave_elements, time)
end
info("Number of slave elements in interface: $(length(slave_elements))")
# 1. calculate nodal normals and tangents for slave element nodes j ∈ S
normals = calculate_normals(slave_elements, time, Val{2};
rotate_normals=props.rotate_normals)
update!(slave_elements, "normal", time => normals)
S_areas = []
C_areas = []
P_areas = []
for slave_element in slave_elements
info(repeat("-", 80))
info("Processing slave element $(slave_element.id), type = $(get_element_type(slave_element))")
info(repeat("-", 80))
S_area = 0.0
S_area_in_contact = 0.0
for ip in get_integration_points(slave_element)
S_area += ip.weight*slave_element(ip, time, Val{:detJ})
end
info("Total area of slave element = $S_area")
if props.linear_surface_elements
info("Converting slave element to linear surface element")
slave_element = convert_to_linear_element(slave_element)
end
slave_element_nodes = get_connectivity(slave_element)
info("Slave element connectivity = $slave_element_nodes")
nsl = length(slave_element)
X1 = slave_element("geometry", time)
n1 = Field([normals[j] for j in slave_element_nodes])
# project slave nodes to auxiliary plane (x0, Q)
xi = mean(get_reference_coordinates(slave_element))
N = vec(get_basis(slave_element, xi, time))
x0 = N*X1
n0 = N*n1
info("Auxiliary plane x0 = $x0, n0 = $n0")
S = Vector[project_vertex_to_auxiliary_plane(X1[i], x0, n0) for i=1:nsl]
check_orientation!(S, n0)
info("Slave element $(slave_element.id) vertices in auxiliary plane: $S")
# 3. loop all master elements
master_elements = slave_element("master elements", time)
if props.split_quadratic_master_elements
master_elements = split_quadratic_elements(master_elements, time)
end
for master_element in master_elements
if props.linear_surface_elements
master_element = convert_to_linear_element(master_element)
end
master_element_nodes = get_connectivity(master_element)
nm = length(master_element)
X2 = master_element("geometry", time)
if norm(mean(X1) - mean(X2)) > problem.properties.distval
# elements are "far enough"
continue
end
# 3.1 project master nodes to auxiliary plane and create polygon clipping
M = Vector[project_vertex_to_auxiliary_plane(X2[i], x0, n0) for i=1:nm]
check_orientation!(M, n0)
P = get_polygon_clip(S, M, n0)
if length(P) < 3
if length(P) == 0
continue
end
if length(P) == 1
info("length(P) == 1, shared vertex")
end
if length(P) == 2
info("length(P) == 2, shared edge")
end
continue
end
info("Master element $(master_element.id) vertices in auxiliary plane = $M")
check_orientation!(P, n0)
P_area_ = calculate_polygon_area(P)
info("Polygon clip found, P=$P, N_P = $(length(P)), area of polygon = $P_area_")
if isapprox(P_area_, 0.0)
error("Polygon P has zero area: $P_area_")
end
P_area = 0.0
C0 = calculate_centroid(P)
info("Centroid of polygon = $C0")
# 4. loop integration cells
all_cells = get_cells(P, C0)
info("Polygon is splitted to $(length(all_cells)) integration cells.")
for (cell_id, cell) in enumerate(all_cells)
C_area = 0.0
virtual_element = Element(Tri3, Int[])
update!(virtual_element, "geometry", cell)
# 5. loop integration point of integration cell
for ip in get_integration_points(virtual_element, 3)
N = vec(get_basis(virtual_element, ip, time))
detJ = virtual_element(ip, time, Val{:detJ})
w = ip.weight*detJ
# project gauss point from auxiliary plane to master and slave element
x_gauss = virtual_element("geometry", ip, time)
xi_s, alpha = project_vertex_to_surface(x_gauss, x0, n0, slave_element, X1, time)
xi_m, alpha = project_vertex_to_surface(x_gauss, x0, n0, master_element, X2, time)
C_area += w
end # integration points done
info("Cell $cell_id has area of $C_area")
P_area += C_area
push!(C_areas, C_area)
end # integration cells done
if !isapprox(P_area, P_area_)
error("P_area = $P_area, should be $P_area_")
end
S_area_in_contact += P_area
push!(P_areas, P_area)
end # master elements done
S_perc = S_area_in_contact / S_area * 100.0
push!(S_areas, S_area_in_contact)
info("Area of slave element in contact: $S_area_in_contact, it's $S_perc % of total element area")
I_area += S_area_in_contact
end # slave elements done, contact virtual work ready
info("Area of interface: $I_area")
info("Smallest cell area: $(minimum(C_areas))")
info("Smallest polygon area: $(minimum(P_areas))")
info("Smallest slave element area in contact: $(minimum(S_areas))")
end