Incorrect Convergence Rate 2D Burgers Equation

I am trying to solve the unsteady viscous 2D Burgers equation with periodic BCs in the x-direction and Dirichlet BCs in the y-direction in a space domain [0,pi]x[0,2] and a time domain [0,1] with a time step dt=1e-4. I use the Crank-Nicholson scheme for time integration. The solution is defined in a VectorFunctionSpace and the initial condition and forcing term are defined from a DNS dataset such that the obtained FEniCS solution represents the spanwise and wall-normal velocity components from a Turbulent Channel Flow (TCF) at a Re_tau=180.
I am using linear triangular elements and thus I am expecting to have a convergence rate of O(h^2) where h is the element’s length. I use h-refinement with a factor of 2 across all spatial resolutions ranging from 32x16 to 512x256. However, that is not the case as the presented convergence is approximately 1.3. Any idea on why this is happening? I have performed a test case similar but in 1D from the same dataset (to just obtain the wall-normal component) and the convergence rate was very close to the expected.
Below is the implemented FEM solver for the 2D case:

from dolfin import *
import numpy as np
import os

class DNSDataExpression(UserExpression):
    def __init__(self, ww, vv, **kwargs):
        self.ww = ww
        self.vv = vv
        super().__init__(**kwargs)
    
    def eval(self, values, x):
        z_index = int((x[0] / L_z) * (self.ww.shape[1] - 1))
        y_index = int((x[1] / L_y) * (self.ww.shape[0] - 1))
        
        values[0] = self.ww[y_index, z_index]
        values[1] = self.vv[y_index, z_index]

    def value_shape(self):
        return (2,)
    
class PeriodicBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return bool(x[0] < DOLFIN_EPS and x[0] > -DOLFIN_EPS and on_boundary)

    def map(self, x, y):
        y[0] = x[0] - L_z
        y[1] = x[1]

class DirichletBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return bool((x[1] < DOLFIN_EPS or x[1] > (L_y - DOLFIN_EPS)) and on_boundary)
    
def InterpolateDNSData(N_zr, N_yr, f1, f2, dim_2D = True):
    if dim_2D:

List item

f1 = f1[::N_yr, ::N_zr]
        f2 = f2[::N_yr, ::N_zr]
    else:
        f1 = f1[:, ::N_yr, ::N_zr]
        f2 = f2[:, ::N_yr, ::N_zr]

    return f1, f2

def SolverParameters(solver):
    # Parameters for non-linear solvers:
    prm = solver.parameters
    prm["nonlinear_solver"]                                         = "snes" 
    prm["snes_solver"]["line_search"]                               = "bt"   
    prm["snes_solver"]["linear_solver"]                             = "mumps"
    prm["snes_solver"]["krylov_solver"]["nonzero_initial_guess"]    = True
    prm["snes_solver"]["report"]                                    = False
    set_log_active(False)

    return solver

N_z = 32                                           # Number of elements in spanwise z-direction
N_y = N_z // 2                                      # Number of elements in wall-normal y-direction
L_z = 3.14159                                       # Spanwise length
L_y = 2                                             # Wall-normal length
z = np.linspace(0, L_z, N_z + 1)                    # Coordinates in z-direction
y = np.linspace(0, L_y, N_y + 1)                    # Coordinates in y-direction
degree = 1                                          # Degree of the finite element basis functions
Re = 180                                            # Reynolds number

# Time integration parameters
T = 1.0
dt = 1e-4
t = np.linspace(0, T, int(T / dt) + 1)
N_t = len(t)
theta = 0.5

print('\nNumber of elements in z-direction: ', N_z)
print('Number of elements in y-direction: ', N_y)

N_zz = 512
N_yy = N_zz // 2

# Load DNS data
print('\nLoading DNS data')

filename = 'data/DNS/burgers_2D_forcing.npz'
file_path = os.path.join(os.path.dirname(__file__), filename)
data = np.load(file_path)
f_zz = data['f_z']
f_yy = data['f_y']

filename = 'data/DNS/burgers_2D_IC.npz'
file_path = os.path.join(os.path.dirname(__file__), filename)
data = np.load(file_path)
ww = data['w']
vv = data['v']

# Interpolation in space to have same resolution as FEM solver
N_zr = int(N_zz / N_z)
N_yr = int(N_yy / N_y)
ww, vv      = InterpolateDNSData(N_zr, N_yr, ww, vv)
f_zz, f_yy  = InterpolateDNSData(N_zr, N_yr, f_zz, f_yy, dim_2D = False)

# Create mesh and define function space
rectangle_mesh = RectangleMesh(Point(0, 0), Point(L_z, L_y), N_z, N_y)
V = VectorFunctionSpace(rectangle_mesh, 'CG', degree, constrained_domain = PeriodicBoundary())

# Define Dirichlet BC
u_D = Constant((0, 0))
bc = DirichletBC(V, u_D, DirichletBoundary())
bcs = [bc]

# Define necessary functions
u   = Function(V)
u_0 = Function(V)
f   = Function(V)
f_0 = Function(V)
v   = TestFunction(V)

# Initial condition and forcing terms from DNS data
u_e = DNSDataExpression(ww, vv, degree = degree)
u.interpolate(u_e)
u_0.assign(u)
f_e = DNSDataExpression(f_zz[0], f_yy[0], degree = degree)
f.interpolate(f_e)
f_0.assign(f)

# Define variational problem
F_theta     = dot(dot(u, nabla_grad(u)), v) + inner(grad(u), grad(v)) / Re - dot(f, v)
F_1_theta   = dot(dot(u_0, nabla_grad(u_0)), v) + inner(grad(u_0), grad(v)) / Re - dot(f_0, v)
F           = (dot((u - u_0) / dt, v)) * dx + theta * F_theta * dx + (1 - theta) * F_1_theta * dx
dF_du       = derivative(F, u)

problem = NonlinearVariationalProblem(F, u, bcs, dF_du)
solver  = NonlinearVariationalSolver(problem)
solver  = SolverParameters(solver)

u_history = [u.copy(deepcopy=True)]

print('\nFEM solver started')
for i in range(1, N_t):
    # Update source term
    f_e = DNSDataExpression(f_zz[i], f_yy[i], degree = degree)
    f.interpolate(f_e)

    # Solve primal variational problem
    solver.solve()
    u_history.append(u.copy(deepcopy=True))
    u_0.assign(u)
    f_0.assign(f)

I’m afraid it will be quite difficult to help you, as your question is on a system level as opposed to on component level. Community members can at best try to give you some general advice. In that regard:

  • With respect to what exactly are you computing your error? Is your DNS data the solution of the same Burgers equation but on a much finer grid, and with much smaller timesteps?
  • Burger’s equation tends to develop sharp layers and shocks. These obfuscate convergence, as they cause a very large pre-asymptotic range. Only when your grid starts to be able to represent the tiny curvature induced by the 1/180 viscous term do you get to the asymptotic range, and can you hope to get optimal convergence. Did you maybe use a much finer mesh, or larger viscosity, in your 1d case?
1 Like

Thank you for your reply! I was trying to understand if something was wrong with my implementation within FEniCS. Regarding your questions:

  • I am computing my error with respect to the DNS data. This data is computed on a different mesh and then interpolated to a uniform spatial resolution of 512x256 elements and a time step of 1e-4. This interpolated data is the reference data I use to compute the error for the spatial resolutions 32x16, 64x32, 128x64, 256x128 and 512x256 at the same time step. I assume that this won’t be the same solution as the 2D Burgers equation solution in a much more refined mesh but I assumed it could be used as a reference, as I did the same in the 1D case. Nonetheless, I tried to compute the error with respect to the more refined solution I have but the slope is approximately the same as before.
  • For the 1D case, I studied the resolutions of 16, 32, 64, 128 and 256 with the same viscosity as for the 2D case, i.e. nu=1/180. Maybe due to the 2D nature of this new problem, the resolutions are still quite coarse and I am not in the asymptotic region yet. Still, here is a plot of the RMSE versus the reference element size and it looks like to be asymptotic already…

I am not entirely sure how the shock lengthscale is affected by the 1D vs 2D nature of the problem. Of course, there is an actual difference: in 1D, flow is always perpendicular to the shock (and thus to the diffusion lengthscale), in 2D, flow may also shear away from a shock. But honestly (maybe naively) I would expect the required meshsize to see convergence to be roughly equal.

Just to be clear, your reference data is a Burgers solution too right? Not a Navier-Stokes solution? I’m asking because you mention channel flow turbulence in your original post.

One test you could run, is to see if the interpolation of your reference data onto your computational grid converges at the expected rate. If it does not, then you know you’re for sure underresolved, and are operating at the pre-asymptotic range.

My reference data is a Navier-Stokes solution. However, I am forcing the Burgers’ equation with the missing terms to ‘make’ it Navier-Stokes. Thus, I assumed to be an adequate reference solution, as it also worked for the 1D case.