Simple 2D isotropic linear elasticity problem on the unit square with a mesh of 2 triangles

To debug a GetFEM code, I am doing “hand-made” computations on a very simple example. Before moving to the contact algorithm, I need to make sure that things work well for the following toy problem without contact.

The unit square (0,1)\times(0,1) is occupied by a linear elastic isotropic material. I apply a vertical surface load on the upper side y=1, say, \mathbf g=(0,g) (let’s forget about units), and the left side x=0 is clamped.

I discretize the problem with only two triangular elements (\mathbb P_1 elements), meaning that I have the following global node numbering:

(0,0) : 1

(1,0) : 2

(1,1) : 3

(0,1) : 4

so that the mesh is made up by the two right triangles 1-2-4 and 2-3-4.

Local indexing of the nodes is always counterclockwise, of course (starting with the right angle).

I want to compute by hand the value of the displacement field at nodes 2 and 3, which are the only free nodes (and actually the surface load only “works” on node 3).

The local stiffness matrix (independent of the element) I obtain is the following 6x6 matrix:

K_e = {1 \over 2} \begin{pmatrix} \lambda + 3\mu & \lambda + \mu & -\lambda - 2\mu & -\mu & -\mu & -\lambda \\ \lambda + \mu & \lambda + 3\mu & -\lambda & -\mu & -\mu & -\lambda - 2\mu \\ -\lambda - 2\mu & -\lambda & \lambda + 2\mu & 0 & 0 & \lambda \\ -\mu & -\mu & 0 & \mu & \mu & 0 \\ -\mu & -\mu & 0 & \mu & \mu & 0 \\ -\lambda & -\lambda - 2\mu & \lambda & 0 & 0 & \lambda + 2\mu \end{pmatrix} ,

where \lambda and \mu are the usual Lamé moduli.

Let’s denote the displacement at a node i as (u_i, v_i). Assembling the global stiffness matrix, the global right-hand side, and taking account of the homogeneous Dirichlet boundary conditions on nodes 1 and 4 (so that u_1=v_1=u_4=v_4=0), I obtain the following reduced linear system (4 scalar unknowns instead of 8):

K_{glob} U = F_{glob},

where the unknown is U = (u_2, v_2, u_3, v_3)^\top, F_{glob} = (0,0,0,g/2)^\top, and the following (reduced) global stiffness matrix:

K_{glob} = {1\over 2} \begin{pmatrix} \lambda + 3\mu & 0 & -\mu & -\mu \\ 0 & \lambda + 3\mu & -\lambda & -\lambda - 2\mu \\ -\mu & -\lambda & \lambda + 3\mu & \lambda + \mu \\ -\mu & -\lambda - 2\mu & \lambda + \mu & \lambda + 3\mu \end{pmatrix} .

I am assuming plane strains, and for the values E=20 and \nu = 0.2 of Young’s modulus and Poisson’s ratio, and g=-5 for the vertical (downwards) surface load, I get the following solution:

U = (u_2,v_2,u_3,v_3)^\top = (-0.0983193, -0.337815, 0.138655, -0.49916)^\top.

Nevertheless, using a GetFEM test script (the mesh and global node numbering are slightly different in this case), I get the following values, after properly rearranging values for comparison:

U = (u_2,v_2,u_3,v_3)^\top = (-0.0605042, -0.337815, 0.07563025, -0.39831933)^\top.

What are these differences due to? Am I missing something in the assembly procedure?

I am certain that the values obtained with GetFEM are the correct ones, but I cannot figure out what is wrong in the procedure I carried out.

ok, dof numbering you cannot control in GetFEM, it is what it is.

I assum3 that you use a GetFEM model object and define your test problem with add_linear_term calls.vThen, in order to compare with your hand written solution, print the tangent matrix and rhs of the model with:

print(md.tangent_matrix().full())
print(md.rhs())

and we can take it from there.

This is what I get (adding those commands after md.solve(), of course):

[[  1.           0.           0.           0.           0.
    0.           0.           0.        ]
 [  0.           1.           0.           0.           0.
    0.           0.           0.        ]
 [  0.           0.           1.           0.           0.
    0.           0.           0.        ]
 [  0.           0.           0.           1.           0.
    0.           0.           0.        ]
 [  0.          -6.94444444 -11.11111111   2.77777778  15.27777778
    0.          -4.16666667   4.16666667]
 [ -6.94444444   0.           4.16666667  -4.16666667   0.
   15.27777778   2.77777778 -11.11111111]
 [-11.11111111   4.16666667   0.           0.          -4.16666667
    2.77777778  15.27777778  -6.94444444]
 [  2.77777778  -4.16666667   0.           0.           4.16666667
  -11.11111111  -6.94444444  15.27777778]]
[ 0.   0.   0.   0.   0.  -2.5  0.   0. ]

This is my test script:

import numpy as np
import getfem as gf
import sys,os

os.system('rm *.vtu')
np.set_printoptions(threshold=sys.maxsize)

gf.util('trace level', 0)

md = gf.Model("real")

E =   0.2e2                    # Young's modulus [Pa]
nu =  0.2                   # Poisson's ratio [-]
# rho = 1

nbElems = 1.
meshSize = 1./nbElems
fem_order = 1
Dim = 2                            # Dimension du problème (toujours 2 ici)
GT    = 'GT_PK'                    # Type d'Eléments Finis : triangle
FEM   = 'FEM_PK'
Integ = 'IM_TRIANGLE(9)'           # Méthode d'intégration sur le triangle
#-----

# print('Mesh generation')
# m = gf.Mesh('regular simplices', np.arange(0,1+meshSize,meshSize), np.arange(0,1+meshSize,meshSize))
m = gf.Mesh('triangles grid', [0,1], [0,1])

mf   = gf.MeshFem(m, Dim)
mf.set_fem(gf.Fem(FEM+'('+str(Dim)+','+str(fem_order)+')'))

#  Integration method on each element
mim = gf.MeshIm(m, gf.Integ(Integ)) 
#-----

# Dirichlet and Neumann conditions
# Boundaries creation
DIRICHLET_BOUNDARY = 1
NEUMANN_BOUNDARY = 2
CONTACT_BOUNDARY = 3

m.set_region(DIRICHLET_BOUNDARY,
                m.outer_faces_with_direction([-1.,  0.], 0.001)) # Left side

m.set_region(NEUMANN_BOUNDARY,
                m.outer_faces_with_direction([ 0.,  1.], 0.001)) # Upper side

m.set_region(CONTACT_BOUNDARY,
                m.outer_faces_with_direction([ 0.,  -1.], 0.001)) # Lower side

md.add_fem_variable("u", mf)   

 
# Petites perturbations

md.add_initialized_data("rlambda",  E*nu/((1.+nu)*(1.-2.*nu))) # First Lame coefficient (N/cm^2)
md.add_initialized_data("rmu",  E/(2.*(1.+nu))) # Second Lame coefficient (N/cm^2)

md.add_macro("ee", "0.5*(Grad_u+(Grad_u)')")
md.add_macro("SS", "2*rmu*ee + rlambda*Trace(ee)*Id(meshdim)")

md.add_linear_term(mim, "SS:Grad_Test_u")

G = md.interpolation('[0., -5]', mf)
md.add_initialized_fem_data('g', mf, G)
md.add_linear_term(mim, "-g.Test_u", NEUMANN_BOUNDARY)

# boundary conditions
u_d = md.interpolation("[0., 0.]", mf)
md.add_initialized_fem_data("DirichletData", mf, u_d)
md.add_Dirichlet_condition_with_simplification("u", DIRICHLET_BOUNDARY, "DirichletData")

(nbit, converged) = md.solve() #('max_res', 1.E-08, 'max_iter', 100, 'noisy')

The numerical coefficients of my handwritten (reduced, as Dirichlet conditions have already been taken into account) assembled matrix are as follows (as per the numbering I chose)

\begin{pmatrix} 15.2778 & 0 & -4.16667 & -4.16667 \\ 0 & 15.2778 & -2.77778 & -11.1111 \\ -4.16667 & -2.77778 & 15.2778 & 6.94444 \\ -4.16667 & -11.1111 & 6.94444 & 15.2778 \end{pmatrix}

I am not a big fan of this method.

In your case, to get a more direct comparison with your handwritten solution, apply the BC directly by dof elimination on the FE space

mf0   = gf.MeshFem(m, Dim)
mf0.set_fem(gf.Fem(FEM+'('+str(Dim)+','+str(fem_order)+')'))

kept_dofs = list(set(range(mf0.nbdof()))
                 -set(mf0.basic_dof_on_region(DIRICHLET_BOUNDARY)))
mf = gf.MeshFem('partial', mf0, kept_dofs)

see
https://gitlab.gbar.dtu.dk/-/snippets/50

Thank you, but if I do that, the error message I get is “Interpolation on reduced fem is not allowed”.

sure, this is here I guess

you can replace mf with the unreduced mf0 in these two lines.

But if gravity is constant, then it is better to use a constant

md.add_initialized_data('g', [0,-5])

Alright, thank you. I get exactly the same values as using md.add_Dirichlet_condition_with_simplification. Hence, there must be something wrong with the assembly procedure I carried out by hand… but still, it’s hard to figure out what is wrong. The value of v_2 is the same (with GetFEM or by hand), but the others not (even though the signs and orders of magnitude are ok).

What is the analytical expression of the global stiffness matrix you obtain, using the numbering I chose?

PS g is not the gravity, but a vertical surface load applied on the boundary y=1.

please post the tangent matrix and rhs after the simplifications, again.

PS g is not the gravity, but a vertical surface load applied on the boundary y=1.

it does not matter, you can still define it as a constant

[[ 15.27777778   0.          -4.16666667   4.16666667]
 [  0.          15.27777778   2.77777778 -11.11111111]
 [ -4.16666667   2.77777778  15.27777778  -6.94444444]
 [  4.16666667 -11.11111111  -6.94444444  15.27777778]]
[ 0.  -2.5  0.   0. ]

u = [ 0.07563025 -0.39831933 -0.0605042  -0.33781513]

Hello, Kostas.

I made the hand computations over again using a slightly different mesh and now everything is fine. I still don’t know what was wrong in the procedure I used before, but now it’s ok.

You might delete this thread if you like.