2d mortar

This commit is contained in:
Jukka Aho
2015-11-18 01:19:04 +02:00
parent 95bee76438
commit 9e9bceecff
15 changed files with 428 additions and 233 deletions
+3
View File
@@ -34,6 +34,7 @@ export @debug, set_debug_on!, set_debug_off!
using ForwardDiff
autodiffcache = ForwardDiffCache()
export derivative, jacobian, hessian
""" Simple linspace extension to arrays.
@@ -60,6 +61,7 @@ include("types.jl") # type definitions
include("elements.jl")
include("lagrange.jl") # Lagrange elements
#include("hierarchical.jl") # P-elements
include("mortar_elements.jl") # Mortar elements
### EQUATIONS ###
include("integrate.jl") # default integration points for elements
@@ -69,6 +71,7 @@ include("problems.jl")
### FORMULATIION ###
include("dirichlet.jl")
include("mortar.jl") # mortar projection
include("heat.jl")
include("elasticity.jl")
+28 -20
View File
@@ -15,7 +15,7 @@ type DirichletProblem <: BoundaryProblem
unknown_field_name :: ASCIIString
unknown_field_dimension :: Int
equations :: Vector{DirichletEquation}
element_mapping :: Dict{Element, Equation}
# element_mapping :: Dict{DataType, DataType}
field_value :: Function
end
@@ -36,14 +36,15 @@ Create u(X) = 0.0 boundary condition for three-dimensional elasticity problem:
>>> u(X) = [0.0, 0.0, 0.0]
>>> bc = DirichletProblem(3, u)
"""
function DirichletProblem(dimension::Int=1, field_value::Function=(X)->[0.0,0.0,0.0])
element_mapping = nothing
if dimension == 1
element_mapping = Dict(
Seg2 => DBC2D2
)
end
DirichletProblem("reaction force", dimension, [], element_mapping, field_value)
function DirichletProblem(dimension::Int=1, field_value::Function=(X)->[0.0,0.0,0.0], equations=[])
# element_mapping = nothing
# if dimension == 1
# element_mapping = Dict(
# Seg2 => DBC2D2
# )
# end
DirichletProblem("reaction force", dimension, equations, field_value)
# DirichletProblem("reaction force", dimension, [], element_mapping, field_value)
end
""" Dirichlet boundary condition element for 2 node line segment """
@@ -51,17 +52,22 @@ type DBC2D2 <: DirichletEquation
element :: Seg2
integration_points :: Vector{IntegrationPoint}
end
function DBC2D2(element::Seg2)
integration_points = default_integration_points(element)
if !haskey(element, "reaction force")
element["reaction force"] = zeros(1, 2)
end
function Base.size(equation::DBC2D2)
return (1, 2)
end
#function DBC2D2(element::Seg2)
function Base.convert(::Type{DirichletEquation}, element::Seg2)
integration_points = line3()
haskey(element, "reaction force") || (element["reaction force"] = zeros(1, 2))
DBC2D2(element, integration_points)
end
Base.size(equation::DBC2D2) = (1, 2)
function assemble!(assembly::Assembly, equation::DirichletEquation, time::Number=0.0, problem=nothing)
gdofs = get_gdofs(equation)
# info("gdofs = $gdofs")
element = get_element(equation)
basis = get_basis(element)
detJ = det(basis)
@@ -69,11 +75,13 @@ function assemble!(assembly::Assembly, equation::DirichletEquation, time::Number
w = ip.weight * detJ(ip)
N = basis(ip, time)
add!(assembly.stiffness_matrix, gdofs, gdofs, w*N'*N)
if !isa(problem, Void)
X = basis("geometry", ip, time)
u = problem.field_value(X)
add!(assembly.force_vector, gdofs, w*N'*u)
end
# info("added $(w*N'*N)")
# if !isa(problem, Void)
# X = basis("geometry", ip, time)
# u = problem.field_value(X)[1:length(gdofs)]
# add!(assembly.force_vector, gdofs, w*N'*u)
# end
end
# info("assembly done")
end
+50 -20
View File
@@ -19,8 +19,7 @@ Saint Venant-Kirchhoff material model, which is simply
S(E) = λtr(E) + 2μE
"""
function get_internal_energy(equation::Equation, ip::IntegrationPoint,
time::Number, F::Matrix)
function get_internal_energy(equation::ElasticityEquation, ip::IntegrationPoint, time::Number, F::Matrix)
element = get_element(equation)
basis = get_basis(element)
dbasis = grad(basis)
@@ -72,8 +71,7 @@ https://en.wikipedia.org/wiki/Plane_stress
https://en.wikipedia.org/wiki/Hooke's_law
"""
function get_residual_vector(equation::ElasticityEquation, ip::IntegrationPoint,
time::Number; variation=nothing)
function get_residual_vector(equation::ElasticityEquation, ip::IntegrationPoint, time::Number; variation=nothing)
element = get_element(equation)
basis = get_basis(element)
@@ -82,9 +80,10 @@ function get_residual_vector(equation::ElasticityEquation, ip::IntegrationPoint,
u = basis("displacement", ip, time, variation)
gradu = dbasis("displacement", ip, time, variation)
F = I + gradu # deformation gradient
#info("Deformation gradient: $F")
# residual vector - internal energy
r = get_internal_energy(equation, ip, time, F)
#info("boundary element")
# external forces - volume load
if haskey(element, "displacement load")
@@ -94,40 +93,71 @@ function get_residual_vector(equation::ElasticityEquation, ip::IntegrationPoint,
return vec(r)
end
has_residual_vector(equation::ElasticityEquation) = true
### Problem 1 - plane elasticity ###
### Plane stress elasticity ###
abstract PlaneElasticityProblem <: ElasticityProblem
abstract PlaneStressElasticityEquation <: ElasticityEquation
type PlaneStressElasticityProblem <: PlaneElasticityProblem
unknown_field_name :: ASCIIString
unknown_field_dimension :: Int
equations :: Array{ElasticityEquation, 1}
element_mapping :: Dict{DataType, DataType}
equations :: Vector{PlaneStressElasticityEquation}
end
function PlaneStressElasticityProblem(equations=[])
element_mapping = Dict(
Quad4 => CPS4)
return PlaneStressElasticityProblem("displacement", 2, equations, element_mapping)
return PlaneStressElasticityProblem("displacement", 2, equations)
end
### Equations ###
abstract PlaneElasticityEquation <: ElasticityEquation
abstract PlaneStressElasticityEquation <: PlaneElasticityEquation
""" 4-node plane stress element. """
type CPS4 <: PlaneStressElasticityEquation
element :: Quad4
integration_points :: Array{IntegrationPoint, 1}
end
function CPS4(element::Quad4)
function Base.size(equation::CPS4)
return (2, 4)
end
function Base.convert(::Type{PlaneStressElasticityEquation}, element::Quad4)
integration_points = get_default_integration_points(element)
if !haskey(element, "displacement")
element["displacement"] = zeros(2, 4)
end
haskey(element, "displacement") || (element["displacement"] = zeros(2, 4))
CPS4(element, integration_points)
end
Base.size(equation::CPS4) = (2, 4)
""" Boundary element for plane stress problem for surface loads. """
type CPS2 <: PlaneStressElasticityEquation
element :: Seg2
integration_points :: Vector{IntegrationPoint}
end
function Base.size(equation::CPS2)
return (2, 2)
end
function Base.convert(::Type{PlaneStressElasticityEquation}, element::Seg2)
integration_points = get_default_integration_points(element)
haskey(element, "displacement") || (element["displacement"] = zeros(2, 2))
CPS2(element, integration_points)
end
function get_residual_vector(equation::CPS2, ip::IntegrationPoint, time::Number; variation=nothing)
element = get_element(equation)
basis = get_basis(element)
u = basis("displacement", ip, time, variation)
r = zeros(size(equation))
if haskey(element, "displacement traction force")
T = basis("displacement traction force", ip, time)
# info("traction force = $T")
# info("basis = $(basis(ip, time))")
r -= T*basis(ip, time)
end
return vec(r)
end
+6
View File
@@ -97,6 +97,12 @@ function get_gdofs(equation::Equation)
return gdofs
end
function get_gdofs(element::Element, dim::Int)
conn = get_connectivity(element)
gdofs = vec(vcat([dim*conn'-i for i=dim-1:-1:0]...))
return gdofs
end
""" Assemble element. """
function assemble!(assembly::Assembly, equation::Equation, time::Number=0.0, problem=nothing)
+22 -22
View File
@@ -72,46 +72,46 @@ end
""" Diffusive heat transfer for 4-node bilinear element. """
type DC2D4 <: HeatEquation
element :: Quad4
integration_points :: Array{IntegrationPoint, 1}
integration_points :: Vector{IntegrationPoint}
end
function DC2D4(element::Quad4)
integration_points = get_default_integration_points(element)
if !haskey(element, "temperature")
element["temperature"] = zeros(4)
end
DC2D4(element, integration_points)
function Base.size(equation::DC2D4)
return (1, 4)
end
Base.size(equation::DC2D4) = (1, 4)
""" Diffusive heat transfer for 2-node linear segment. """
type DC2D2 <: HeatEquation
element :: Seg2
integration_points :: Vector{IntegrationPoint}
end
function DC2D2(element::Seg2)
function Base.size(equation::DC2D2)
return (1, 2)
end
# Conversions element -> equation
function Base.convert(::Type{HeatEquation}, element::Quad4)
integration_points = get_default_integration_points(element)
if !haskey(element, "temperature")
element["temperature"] = zeros(2)
end
haskey(element, "temperature") || (element["temperature"] = zeros(4))
DC2D4(element, integration_points)
end
function Base.convert(::Type{HeatEquation}, element::Seg2)
integration_points = get_default_integration_points(element)
haskey(element, "temperature") || (element["temperature"] = zeros(2))
DC2D2(element, integration_points)
end
Base.size(equation::DC2D2) = (1, 2)
### Problems ###
type PlaneHeatProblem <: HeatProblem
unknown_field_name :: ASCIIString
unknown_field_dimension :: Int
equations :: Vector{Equation}
#element_mapping :: Dict{Element, Equation}
# FIXME: Why is not working ^
element_mapping :: Dict{Any, Any}
equations :: Vector{HeatEquation}
end
""" Default constructor for problem takes no arguments. """
function PlaneHeatProblem()
element_mapping = Dict(
Quad4 => DC2D4,
Seg2 => DC2D2)
return PlaneHeatProblem("temperature", 1, [], element_mapping)
function PlaneHeatProblem(equations=[])
return PlaneHeatProblem("temperature", 1, equations)
end
+24 -26
View File
@@ -11,12 +11,20 @@ function get_default_integration_points(element::Quad4)
]
end
function get_default_integration_points(element::Seg2)
function line1()
[
IntegrationPoint([0.0], 2.0)
]
end
function line2()
[
IntegrationPoint([-sqrt(1/3)], 1)
IntegrationPoint([+sqrt(1/3)], 1)
]
end
function line3()
[
IntegrationPoint([0.0], 8/9),
@@ -25,6 +33,15 @@ function line3()
]
end
function line4()
[
IntegrationPoint([+sqrt(3/7 - 2/7*sqrt(6/5))], (18+sqrt(30))/36)
IntegrationPoint([-sqrt(3/7 - 2/7*sqrt(6/5))], (18+sqrt(30))/36)
IntegrationPoint([+sqrt(3/7 + 2/7*sqrt(6/5))], (18-sqrt(30))/36)
IntegrationPoint([-sqrt(3/7 + 2/7*sqrt(6/5))], (18-sqrt(30))/36)
]
end
function line5()
[
IntegrationPoint([-1/3*sqrt(5 + 2*sqrt(10/7))], (322-13*sqrt(70))/900),
@@ -35,29 +52,10 @@ function line5()
]
end
#integration_points = [
# IntegrationPoint([ 0.0000000000000000], 0.5688888888888889),
# IntegrationPoint([-0.5384693101056831], 0.4786286704993665),
# IntegrationPoint([ 0.5384693101056831], 0.4786286704993665),
# IntegrationPoint([-0.9061798459386640], 0.2369268850561891),
# IntegrationPoint([ 0.9061798459386640], 0.2369268850561891)
#]
#integration_points = [
# IntegrationPoint([+sqrt(3/7 - 2/7*sqrt(6/5))], (18+sqrt(30))/36)
# IntegrationPoint([-sqrt(3/7 - 2/7*sqrt(6/5))], (18+sqrt(30))/36)
# IntegrationPoint([+sqrt(3/7 + 2/7*sqrt(6/5))], (18-sqrt(30))/36)
# IntegrationPoint([-sqrt(3/7 + 2/7*sqrt(6/5))], (18-sqrt(30))/36)
#]
#integration_points = [
# IntegrationPoint([0.0], 8/9),
# IntegrationPoint([-sqrt(3/5)], 5/9),
# IntegrationPoint([+sqrt(3/5)], 5/9)
#]
#integration_points = [
# IntegrationPoint([-sqrt(1/3)], 1)
# IntegrationPoint([+sqrt(1/3)], 1)
#]
#integration_points = [
# IntegrationPoint([0.0], 2)
#]
function get_default_integration_points(element::Seg2)
return line1()
end
function get_default_integration_points(element::MSeg2)
return line3()
end
+58 -50
View File
@@ -1,78 +1,86 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
# Mortar projection integration
# Mortar equations
abstract MortarEquation <: Equation
function get_unknown_field_name(equation::MortarEquation)
return "reaction force"
end
""" Mortar boundary condition element for 2-dimensional problem, 2 node line segment. """
type MBC2D2 <: MortarEquation
element :: MSeg2
integration_points :: Vector{IntegrationPoint}
end
function Base.size(equation::MBC2D2)
return (1, 2)
end
function Base.convert(::Type{MortarEquation}, element::MSeg2)
return MBC2D2(element, get_default_integration_points(element))
end
# Mortar problem
"""
Parameters
----------
node_csys
coordinate system in node, normal + tangent + "binormal"
element_pairs
m x s matrix of boolean values, indicating elements sharing
common surface. s is number of slave elements and m is number
of master elements.
in 3d 3x3 matrix, in 2d 2x2 matrix, respectively
"""
type MortarProblem <: BoundaryProblem
unknown_field_name :: ASCIIString
unknown_field_dimension :: Int
equations :: Vector{MortarEquation}
element_mapping :: Dict{Element, MortarEquation}
master_elements :: Vector{Element} # mortar surface
node_csys :: Dict{Int, Matrix{Float64}}
element_pairs :: Matrix{Bool}
end
function MortarProblem(dimension::Int=1, equations=[], master_elements=[])
element_mapping = Dict(
Seg2 => MBC2D2,
)
MortarProblem("reaction force", dimension, equations, element_mapping, master_elements, Dict(), zeros(0,0))
function MortarProblem(dimension::Int=1, equations=[])
MortarProblem("reaction force", dimension, equations)
end
""" Mortar boundary condition element for 2-dimensional problem, 2 node line segment. """
type MBC2D2 <: MortarEquation
element :: Seg2 # == non-mortar surface element
integration_points :: Vector{IntegrationPoint}
end
function MBC2D2(element::Seg2)
integration_points = default_integration_points(element)
if !haskey(element, "reaction force")
element["reaction force"] = zeros(1, 2)
end
MBC2D2(element, integration_points)
end
Base.size(equation::MBC2D2) = (1, 2)
# Mortar projection calculation
function find_master_elements(slave_element, problem)
# find slave element "position" in element pairs matrix
all_elements = map((equation) -> get_element(equation), problem.equations)
seid = findfirst(slave_element, all_elements)
info("slave element id = $seid")
# find master element "positions" in element pairs matrix
meids = find(problem.element_pairs[:, seid])
info("master element ids = $meids")
# master elements
master_elements = problem.master_elements[meids]
return master_elements
""" Find master or "mortar" elements for this slave element. """
function get_master_elements(element::MortarElement)
return element.master_elements
end
function calculate_local_assembly!(assembly::LocalAssembly, equation::MortarEquation, unknown_field_name::ASCIIString, time::Number=0.0, problem=nothing)
# slave element = non-mortar element where integration happens
# master element = mortar element projected to non-mortar side
isa(problem, Void) && error("Cannot create projection without problem")
initialize_local_assembly!(assembly, equation)
function assemble!(assembly::Assembly, equation::MortarEquation, time::Number=0.0, problem=nothing)
slave_element = get_element(equation)
basis = get_basis(slave_element)
detJ = det(basis)
master_elements = find_master_elements(equation, problem)
master_elements = get_master_elements(slave_element)
slave_basis = get_basis(slave_element)
detJ = det(slave_basis)
dim = size(equation, 1) # number of nodes
slave_dofs = get_gdofs(slave_element, dim)
for master_element in master_elements
for ip in get_integration_points(slave_element)
mortar_basis = 0 # ...
assembly.stiffness_matrix += w*basis'*basis
assembly.force_vector += w*N'*gn
master_dofs = get_gdofs(master_element, dim)
xi1a = project_from_master_to_slave(slave_element, master_element, [-1.0])
xi1b = project_from_master_to_slave(slave_element, master_element, [ 1.0])
xi1 = clamp([xi1a xi1b], -1.0, 1.0)
l = 1/2*(xi1[2]-xi1[1])
if abs(l) < 1.0e-6
warn("No contribution")
continue # no contribution
end
master_basis = get_basis(master_element)
for ip in get_integration_points(equation)
w = ip.weight*detJ(ip)*l
# integration point on slave side segment
xi_gauss = 1/2*(1-ip.xi)*xi1[1] + 1/2*(1+ip.xi)*xi1[2]
# projected integration point
xi_projected = project_from_slave_to_master(slave_element, master_element, xi_gauss)
# add contribution to left hand side
N1 = slave_basis(xi_gauss, time)
N2 = master_basis(xi_projected, time)
add!(assembly.lhs, slave_dofs, slave_dofs, w*N1'*N1)
add!(assembly.lhs, slave_dofs, master_dofs, -w*N1'*N2)
end
end
end
+84
View File
@@ -0,0 +1,84 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
# Mortar elements
# slave element = non-mortar element where integration happens
# master element = mortar element projected to non-mortar side
abstract MortarElement <: Element
type MSeg2 <: MortarElement
connectivity :: Vector{Int}
basis :: Basis
fields :: FieldSet
master_elements :: Vector{MortarElement}
end
function MSeg2(connectivity, master_elements=[], biorthogonal=false)
basis(xi) = [(1-xi[1])/2 (1+xi[1])/2]
dbasisdxi(xi) = [-1/2 1/2]
return MSeg2(connectivity, Basis(basis, dbasisdxi), FieldSet(), master_elements)
end
""" Find projection from slave nodes to master element, i.e. find xi2 from
master element corresponding to the xi1.
"""
function project_from_slave_to_master(slave::MortarElement, master::MortarElement, xi1::Vector, time::Float64=0.0; max_iterations=5, tol=1.0e-9)
slave_basis = get_basis(slave)
master_basis = get_basis(master)
# slave side geometry and normal direction at xi1
X1 = slave_basis("geometry", xi1, time)
N1 = slave_basis("nodal ntsys", xi1, time)[:,1]
# master side geometry at xi2
X2(xi2) = master_basis("geometry", [xi2], time)
# dX2(xi2) = dmaster_basis("geometry", xi2, time)
# equation to solve
R(xi2) = det([X2(xi2)-X1 N1]')
# dR(xi2) = det([dX2(xi2) N1]')
dR = ForwardDiff.derivative(R)
# go!
xi2 = 0.0
for i=1:max_iterations
dxi2 = -R(xi2) / dR(xi2)
xi2 += dxi2
if norm(dxi2) < tol
return Float64[xi2]
end
end
error("find projection from slave to master: did not converge")
end
""" Find projection from master surface to slave point, i.e. find xi1 from slave element corresponding to the xi2. """
function project_from_master_to_slave(slave::MortarElement, master::MortarElement, xi2::Vector, time::Float64=0.0; max_iterations=5, tol=1.0e-9)
slave_basis = get_basis(slave)
master_basis = get_basis(master)
# slave side geometry and normal direction at xi1
X1(xi1) = slave_basis("geometry", [xi1], time)
N1(xi1) = slave_basis("nodal ntsys", [xi1], time)[:,1]
# master side geometry at xi2
X2 = master_basis("geometry", xi2, time)
# equation to solve
R(xi1) = det([X1(xi1)-X2 N1(xi1)]')
# dR(xi1) = det([dX1(xi1) N1(xi1)]') + det([X1(xi1)-X2 dN1(xi1)]')
dR = ForwardDiff.derivative(R)
# go!
xi1 = 0.0
for i=1:max_iterations
dxi1 = -R(xi1) / dR(xi1)
xi1 += dxi1
if norm(dxi1) < tol
return Float64[xi1]
end
end
error("find projection from master to slave: did not converge")
end
+5 -4
View File
@@ -33,10 +33,11 @@ Notes
Equation is automatically created during process based on problem
element -> equation mapping and element type.
"""
function Base.push!(problem::Problem, element::Element)
element_type = typeof(element)
equation_type = problem.element_mapping[element_type]
push!(problem.equations, equation_type(element))
function Base.push!(problem::Problem, element::Element, args...)
# element_type = typeof(element)
# equation_type = problem.element_mapping[element_type]
# push!(problem.equations, equation_type(element, args...))
push!(problem.equations, element)
end
"""
+22 -21
View File
@@ -111,25 +111,26 @@ common situation, i.e., some main field problem and it's Dirichlet boundary.
Cu = g
"""
function call(solver::SimpleSolver, time::Number=Inf)
p1, p2 = get_problems(solver)
function call(solver::SimpleSolver, time::Number=0.0)
problem1, problem2 = get_problems(solver)
ga1 = initialize_global_assembly(p1)
calculate_global_assembly!(ga1, p1)
ga2 = initialize_global_assembly(p2)
calculate_global_assembly!(ga2, p2)
assembly1 = Assembly()
assemble!(assembly1, problem1, time)
assembly2 = Assembly()
assemble!(assembly2, problem2, time)
A1 = ga1.stiffness_matrix
b1 = ga1.force_vector
A2 = ga2.stiffness_matrix
b2 = ga2.force_vector
# info("Creating sparse matrices")
A1 = sparse(assembly1.stiffness_matrix)
b1 = sparse(assembly1.force_vector, size(A1, 1), 1)
A2 = sparse(assembly2.stiffness_matrix)
b2 = sparse(assembly2.force_vector, size(A2, 1), 1)
# create a saddle point problem
A = [A1 A2; A2' zeros(A2)]
b = [b1; b2]
# solve problem
nz = unique(rowvals(A)) # here we remove any zero rows
nz = unique(rowvals(A)) # take only non-zero rows
x = zeros(b)
x[nz] = lufact(A[nz,nz]) \ full(b[nz])
@@ -138,23 +139,23 @@ function call(solver::SimpleSolver, time::Number=Inf)
x2 = x[length(b1)+1:end]
# update field for elements in problem 1
for equation in get_equations(p1)
for equation in get_equations(problem1)
element = get_element(equation)
field_name = get_unknown_field_name(p1)
gdofs = get_gdofs(p1, equation)
field_name = get_unknown_field_name(problem1)
gdofs = get_gdofs(problem1, equation)
element_solution = full(x1[gdofs])
field = Field(time, element_solution)
push!(element[field_name], field)
field = Increment(element_solution)
push!(element[field_name], TimeStep(time, field))
end
# update field for elements in problem 2 (Dirichlet boundary)
for equation in get_equations(p2)
for equation in get_equations(problem2)
element = get_element(equation)
field_name = get_unknown_field_name(p2)
gdofs = get_gdofs(p2, equation)
field_name = get_unknown_field_name(problem2)
gdofs = get_gdofs(problem2, equation)
element_solution = full(x2[gdofs])
field = Field(time, element_solution)
push!(element[field_name], field)
field = Increment(element_solution)
push!(element[field_name], TimeStep(time, field))
end
end
+3 -3
View File
@@ -14,8 +14,8 @@ function SparseMatrixIJV()
SparseMatrixIJV([], [], [])
end
function Base.sparse(A::SparseMatrixIJV)
return sparse(A.I, A.J, A.V)
function Base.sparse(A::SparseMatrixIJV, args...)
return sparse(A.I, A.J, A.V, args...)
end
function Base.push!(A::SparseMatrixIJV, I::Int, J::Int, V::Float64)
@@ -37,7 +37,7 @@ function Base.append!(A::SparseMatrixIJV, I::Vector{Int}, J::Vector{Int}, V::Vec
end
function Base.full(A::SparseMatrixIJV, args...)
return full(sparse(A.I, A.J, A.V), args...)
return full(sparse(A.I, A.J, A.V, args...))
end
""" Add local element matrix to sparse matrix. This basically does: