spectralDNS / shenfun

High performance computational platform in Python for the spectral Galerkin method

Home Page:http://shenfun.readthedocs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Convergence troubles

thoeschler opened this issue · comments

Hi Mikael,

I have been contacting you about half a year ago regarding the use of shenfun for solving problems in linear elasticity and linear gradient elasticity. I am facing some troubles while performing a convergence test with a manufactured solution. To keep it simple, I am only describing the case of standard linear elasticity. I would greatly appreciate it if you could take a look at the code for the problem described below and give us your opinion.

I have derived two different variational formulations for the spectral Galerkin method. The first one is based on the principle of virtual work which reads


Inserting Hooke's Law


yields the variational form

My manufactured solution only contains Dirichlet boundary conditions. Therefore, the test functions vanish on entire boundary and so the boundary integral.

The second variational formulation is based on the Lamé-Navier equations which reads


Through the integration by part the weak form is obtained as


For the manufactured solution (pure Dirichlet problem) the boundary terms again vanishes.

I think that both variational formulations are equivalent for a pure Dirichlet problem. In order to check that both formulations are working in shenfun, I performed a convergence test. I computed the integral error of the numerical w.r.t. the manufactured one. I found that the error goes down to 1e-15 for N=30 basis functions in case of the second version. For the principle of virtual power (first version), however, the error is still around 1e-3.
is working where as the first one does not. Maybe you could take a look at the attached code file and give me your opinion what goes wrong and how it could possibly be fixed.

The reason for considering the first version at all is that I want to study another problem which is not a pure Dirichlet problem. Instead it contains one homogeneous traction boundary condition. In this problem, the boundary integral above also vanishes but the second variational formulation is not correct since the boundary integral does not contain the (physically correct) traction.

The code below should explain the problem. Both variational forms are inplemented in the code.

from sympy import symbols, cos, sin, pi
from math import sqrt
from shenfun import comm, TensorProductSpace, VectorSpace, FunctionSpace, Function, inner, Array, TrialFunction, \
    TestFunction, grad, div, Dx, BlockMatrix, extract_bc_matrices, project

# computational domain
l = 1.
h = l/2
domain_x = (0, l)
domain_y = (0, h)
domain = (domain_x, domain_y)
dim = len(domain)
# size of discretization
N = 30

# elastic constants
E = 400. # Young's modulus
nu = 0.4 # Poisson's ratio
# Lame parameters
lambd = E * nu / ( (1.0 + nu) * (1.0 - 2.0*nu) )
mu = E / (2.0 * (1.0 + nu) )

# displacement value
u0 = 1.

# sympy variables
x, y = symbols("x,y")
coord = [x, y]

# manufactured solution (sympy expression)
ua_sp = (u0*((1+x/l)*(y/h)**2*(1-y/h)**2*sin(2*pi*x/l)*cos(3*pi*y/h) + x/l*4*y/h*(1-y/h)), u0*x/l*(1-x/l)*sin(2*pi*y/h))

# Section 1) Setup  boundary conditions, shenfun function spaces etc.
# 1.1) Specify boundary conditions for the displacement
bcs = (
       # first component
       (
        # (left, right)
        (0., u0*4.0 * y / h * (1.0 - y/h) ),
        # (bottom, top)
        (0., 0.)
       ),
       # second component
       (
        # (left, right)
        (0., 0.),
        # (bottom, top)
        (0., 0.)
       )
      )

# 1.2) Create a VectorSpace for the displacement
vector_tensor_spaces = []
for i in range(dim): # nb of displacement components
    tensor_spaces = []
    for j in range(dim): # nb of FunctionSpaces for each component
        basis = FunctionSpace(N, family='legendre', bc=bcs[i][j], domain=domain[j])
        tensor_spaces.append(basis)
    vector_tensor_spaces.append(TensorProductSpace(comm, tuple(tensor_spaces)))
V = VectorSpace(vector_tensor_spaces)

# 1.3) Test and trial functions
u = TrialFunction(V)
v = TestFunction(V)

# 1.4) Compute the body force
# create a VectorSpace without boundary conditions
V_none = V.get_orthogonal()

# compute analytical solution as shenfun Function
ua = Array(V_none, buffer=ua_sp).forward()

# compute symbolic body force (as sympy function)
sym_body_force = (-(lambd+mu)*grad(div(ua))-mu*div(grad(ua))).tosympy(basis=ua_sp, psi=(x, y))

# compute body forces at quadrature points
body_force = Array(V_none, buffer=tuple(sym_body_force))

# Section 2) Solve the problem

# 2.1) Define weak form and matrices
matrices = []

#------------------------------------------------------------------------------#
# 1st version: principle of virtual power
#------------------------------------------------------------------------------#
A = inner(mu*grad(u), grad(v))

# inner(grad(u)^T, grad(v))
B = []
for i in range(dim):
    for j in range(dim):
            temp = inner(mu*Dx(u[i], j), Dx(v[j], i))
            if isinstance(temp, list):
                B += temp
            else:
                B += [temp]

C = inner(lambd*div(u), div(v))

matrices = A + B + C

#------------------------------------------------------------------------------#
# 2nd version: Lame-Navier equations
#------------------------------------------------------------------------------#
#A = inner(mu*grad(u), grad(v))
#
#B = inner((lambd + mu)*div(u), div(v))
#
#matrices = A + B

# 2.2) Right-hand side from body forces
b = inner(v, body_force)

# solution
u_hat = Function(V)

# 2.3) Setup the linear system 
# get boundary matrices
bc_mats = extract_bc_matrices([matrices])

# BlockMatrix for homogeneous part
M = BlockMatrix(matrices)

# BlockMatrix for inhomogeneous part
BM = BlockMatrix(bc_mats)

# inhomogeneous part of solution
uh_hat = Function(V).set_boundary_dofs()        
# additional part to be passed to the right hand side
b_add = Function(V)
b_add = BM.matvec(-uh_hat, b_add) # negative because added to right hand side

# 2.4) Solve the linear system 
# solve homogeneous part of solution
u_hat = M.solve(b + b_add)

# solution
u_hat += uh_hat

# Section 3) Compute error

# compute integral error
error_array = Array(V, buffer=ua_sp)
error_array -= project(u_hat, V).backward()
error_ua = sqrt(inner((1, 1), error_array**2))

print('error (analytical solution):', error_ua)

Best regards,

Thilo

Hi Thilo,
Thanks for reporting this bug. I did some checking and found a rather deep error that had to do with the inhomogeneous boundary conditions and the non-standard domain sizes you are using. I will push a fix as soon as possible. If you're using the shenfun source code, then you can fix it by replacing this line with scb=sc.

I just realised that fixing the bug did not actually fix your code. It just fixed all other problems that used inhomogeneous boundary conditions and non-standard domain. Which may have been a recently introduced bug if you did managed to solve your problem for one of the cases above. I guess I will have to look some more.

I don't think your variational forms are identical, so I don't understand how you can expect both methods to work?

For the pure Dirichlet problem both variational forms should be valid because in both cases the boundary integral vanishes as the test function vanishes on the entire boundary.

But I should be able to derive one from the other, and I can't.

By use of the divergence theorem I was able to derive one form from the other.

Hi Mikael,
in the following image I derived the equivalence of the two weak forms. Maybe you could take a look at it. The notation is a little different, I hope that is not a problem.
grafik

Hi Mikael,
do you think that the two forms are equivalent? If you find some time to look into it, please let me know.

Hi Thilo,
Sorry, forgot about it. There are some issues with your note that I don't believe are correct and I don't really have time to look closely into it. Do you by any chance have a supervisor or someone to discuss these derivations with? For one, $\boldsymbol{u} \cdot \nabla$ is not the same as $\nabla \cdot \boldsymbol{u}$.

Hi Mikael,
I discussed the equivalence of the two variational forms with my supervisor and we both agreed that the two forms seem to be equivalent. I have changed the derivation in my comment above to index notation so it is now a bit more readable. If you find some time, I would be grateful if you could take a look at it.

Hi Thilo,
I'm sorry for having been reluctant to look into this. It's very difficult and timeconsuming to find bugs when the reproducing code is 200 lines, and when it's not even obvious that it is a shenfun issue. However, I did have a look and quickly isolated the problem to the div div terms like inner(lambd*div(u), div(v)). There was an error in the precomputed matrices, and if you comment out line 1336

#((SD, 1), (SD, 0)): functools.partial(CSDSDmat, scale=-1.),

then it should work for the latest version of shenfun.
Actually, I realised this has deeper roots and will have to be fixed in inner. So great that you found and reported the error:-)

Hi Mikael,

first of all thanks for taking the time to review this.

I tried commenting out the line

((SD, 1), (SD, 0)): functools.partial(CSDSDmat, scale=-1.),

that you were referring to. However, with the latest version of shenfun I wasn't able to run the test code from my initial comment at all. As far as I can tell the error that I get when running the test code has to do with the inhomogeneous boundary conditions that I am using:

 File "/home/student01/Dokumente/Projects/convergence_problem.py", line 61, in <module>
    vector_tensor_spaces.append(TensorProductSpace(comm, tuple(tensor_spaces)))

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/tensorproductspace.py", line 322, in __init__
    base.bc.set_tensor_bcs(base, self)

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/tensorproductspace.py", line 1602, in set_tensor_bcs
    b_hat = T.forward(b).copy()

  File "/home/student01/anaconda3/lib/python3.7/site-packages/mpi4py_fft/mpifft.py", line 73, in __call__
    self._xfftn[-1](**kw)

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/spectralbase.py", line 1389, in __call__
    return self.func(input_array, output_array, **kw)

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/spectralbase.py", line 1389, in __call__
    return self.func(input_array, output_array, **kw)

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/spectralbase.py", line 262, in forward
    self.bc.add_mass_rhs(self.forward.output_array)

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/tensorproductspace.py", line 1678, in add_mass_rhs
    self.set_boundary_dofs(u)

  File "/home/student01/Dokumente/Projects/shenfun/shenfun/tensorproductspace.py", line 1718, in set_boundary_dofs
    u[self.base.si[-(M)+i]] = self.bcs[i]

  File "/home/student01/anaconda3/lib/python3.7/site-packages/sympy/core/expr.py", line 358, in __float__
    raise TypeError("can't convert expression to float")

TypeError: can't convert expression to float

If you find some time, please let me know if you can successfully run the script with the latest version of shenfun.

Well, this looks more like a regular bug:-) I'll check it out.

With my version of shenfun it runs with:

error (analytical solution): 2.5716607593388476e-16

Are you using the latest version of shenfun from github? I may have fixed the problem you see quite recently.

I just cloned the latest version and now it works :) Thanks again!