Wrong Pressure Field Convergence in Taylor-Green Vortex Problem

Hey there community. I’m so new at FenicsX and I’m trying to solve the Taylor-Green Vortex problem. The velocity converges really well, but the pressure converges to something different and I don’t know how to correct it.
I’m using a monolithic schema of a IMEX Galerkin method. I left the code for everyone who wants or could help me to figure it out, or already has had the same problem as me :slight_smile:.
In the picture attached I show on the left side the computed pressure field and in the right side the exact solution for the pressure field, both for the first time step (1e-5). I’m using the last fenics-dolfinx version (0.8.0) installed by conda-forge.

Thanks in advance.

#Taylor-Green Vortex
import numpy as np
import ufl
from dolfinx import fem, mesh, io
from mpi4py import MPI
from dolfinx import *
import dolfinx
from petsc4py import PETSc
from pathlib import Path
from dolfinx.mesh import create_unit_square
from basix.ufl import element, mixed_element
from dolfinx.fem.petsc import LinearProblem, assemble_matrix, assemble_vector, apply_lifting, create_vector, create_matrix,set_bc
from dolfinx.fem import Constant, Function, functionspace, assemble_scalar, dirichletbc, form, locate_dofs_geometrical
from ufl import Measure, SpatialCoordinate, TestFunction, TrialFunction, div, exp, inner,dx,rhs,lhs,inner,dot,nabla_grad, TestFunctions, TrialFunctions

# Define the time-dependent function F_t

#define the exact solution for velocity
class vel_exac:
    def __init__(self, t):
        self.t = t
    
    def eval(self, x):
        values = np.vstack((np.sin(np.pi * x[0]) * np.cos(np.pi * x[1]) * F_t(t),
                                      -np.sin(np.pi * x[1]) * np.cos(np.pi * x[0]) * F_t(t)),
                                      dtype=PETSc.ScalarType)
        return values

#define the exact solution for pressure    
class press_exac:
    def __init__(self, t):
        self.t = t

    def eval(self, x):
        values = -0.5 * ((np.sin(2 * np.pi * x[0])**2) + (np.sin(2 * np.pi * x[1])**2)) * F_t(t)**2
        return values

# Define the boundary condition
class Vel_BoundaryCondition:
    def __init__(self, t):
        self.t = t

    def eval(self, x):
        values = np.vstack((np.sin(np.pi * x[0]) * np.cos(np.pi * x[1]) * F_t(t),
                                      -np.sin(np.pi * x[1]) * np.cos(np.pi * x[0]) * F_t(t)),
                                      dtype=PETSc.ScalarType)
        return values

def F_t(t):
    return np.exp(-2 * np.pi**2 * t)  # Example time-dependent function

def comp_and_save_exact_sol(t):
    # Update the exact solution and saving
    u_e.t = t; p_e.t = t
    w_ex.sub(0).interpolate(u_e.eval) ; w_ex.sub(1).interpolate(p_e.eval)
    u_ex_t = w_ex.sub(0).collapse() ; p_ex_t = w_ex.sub(1).collapse()
    
    pressure_integral = assemble_scalar(form(p_ex_t * dx))
    p_ex_t.vector[:] -= pressure_integral
    
    u_ex.x.array[:] = u_ex_t.x.array[:] ; p_ex.x.array[:] = p_ex_t.x.array[:]
    
    uex.write(t); pex.write(t)
    return u_ex, p_ex

def all_walls(x):
    return np.logical_or(np.logical_or(np.isclose(x[0], 0), np.isclose(x[0], 1)), 
                         np.logical_or(np.isclose(x[1], 0), np.isclose(x[1], 1)))

#Def point
def point(x): 
    point = [0.0, 0.0]
    return np.logical_and(np.isclose(x[0], point[0]), np.isclose(x[1], point[1]))

# Number of elements in each direction of the mesh
n_elem = 80
msh = create_unit_square(MPI.COMM_WORLD, n_elem, n_elem)

#Approximation space polynomial degree
deg = 2
P2 = element("Lagrange", msh.basix_cell(), deg, shape=(msh.geometry.dim,))
P1 = element("Lagrange", msh.basix_cell(), deg-1)
#V, Q = functionspace(msh, P2), functionspace(msh, P1)

#defining mixed function space
W_el = mixed_element([P2, P1])
W = functionspace(msh, W_el)

# Create functions for the exact solutions
w_ex = Function(W) #; u_ex, p_ex = w_ex.split()
t = 0.0

u_e = vel_exac(t); p_e = press_exac(t)
w_ex.sub(0).interpolate(u_e.eval)
w_ex.sub(1).interpolate(p_e.eval)
u_ex = w_ex.sub(0).collapse() ; p_ex = w_ex.sub(1).collapse()
u_ex.name = "Velocity_exact"; p_ex.name = "Pressure_exact"; 

# Save the solution in BP format
output = Path("output")
output.mkdir(exist_ok=True)

#writing the exact solution in bp format
uex = io.VTXWriter(msh.comm, output / f"exact_solution_u.bp", u_ex)
pex = io.VTXWriter(msh.comm, output / f"exact_solution_p.bp", p_ex)

# integrate the pressure over the domain to subtract it to the pressure solution
pressure_integral = assemble_scalar(form(p_ex * dx))
pressure_integral_function = Constant(msh, PETSc.ScalarType(pressure_integral))
p_ex.vector[:] -= pressure_integral_function.value
uex.write(t); pex.write(t)

V,_ = W.sub(0).collapse()
Q,_ = W.sub(1).collapse()

#Facets and dofs walls
fdim = msh.topology.dim - 1

#Velocity BC on walls 
facets_walls = dolfinx.mesh.locate_entities_boundary(msh, fdim, all_walls)
dofs_walls = dolfinx.fem.locate_dofs_topological((W.sub(0), V), 1, facets_walls)
u_bc_fes = Function(V)
u_values = vel_exac(t)
u_bc_fes.interpolate(u_values.eval)
bc_u = dirichletbc(u_bc_fes, dofs_walls, W.sub(0))

P_val = 0.0 #pressure Value at the point
point_located = dolfinx.mesh.locate_entities_boundary(msh, 0, point)
point_dofs = dolfinx.fem.locate_dofs_topological((W.sub(1),Q), 0, point_located)
press_fes = Function(Q)
press_fes.vector.set(P_val)
bc_p = dirichletbc(press_fes, point_dofs, W.sub(1))
bcs_up = [bc_u, bc_p]

#Initial condition
w_n = Function(W)  
w_n.sub(0).interpolate(u_values.eval) #Exact solution at t=0
u_n, p_n = w_n.split()

# Save the solution in BP format
u_n_w = u_n.collapse(); p_n_w = p_n.collapse()
u_n_w.name = "Velocity"; p_n_w.name = "Pressure"
u_sol = io.VTXWriter(msh.comm, output / f"results_u.bp", u_n_w)
p_sol = io.VTXWriter(msh.comm, output / f"results_p.bp", p_n_w)
u_sol.write(t); p_sol.write(t)

# Save the boundary conditions in BP format
w_BC = Function(W)
w_BC.sub(1).interpolate(lambda x: x[0]*0 + 1) #Only to see the pressure BC
set_bc(w_BC.vector, bcs_up)
u_BC = w_BC.sub(0).collapse(); p_BC = w_BC.sub(1).collapse()
u_BC.name = "Velocity_BC"; p_BC.name = "Pressure_BC"
bc_u_w = io.VTXWriter(msh.comm,output / "BC_checking_u.bp", u_BC)
bc_p_w = io.VTXWriter(msh.comm,output / "BC_checking_p.bp", p_BC)
bc_u_w.write(t); bc_p_w.write(t)

#defininf Trial and Test functions
(u,p) = TrialFunctions(W)
(v,q) = TestFunctions(W)

dt = 1e-4
dx = Measure("dx",msh)
f = Constant(msh, PETSc.ScalarType((0, 0)))
nu = Constant(msh,PETSc.ScalarType(1))
tau = Constant(msh,PETSc.ScalarType(1/dt))

# Define the variational form
F1 = tau * dot(u,v)* dx  # Mass term unknown
F1 += nu * inner(nabla_grad(u), nabla_grad(v)) * dx  # Diffusive term
F1 += dot(dot(nabla_grad(u),u_n) , v) * dx  # Convective term
F1 += dot(div(u),q) * dx - dot(p,div(v)) * dx  # Continuity equation
F1 -= dot(f,v) * dx  # RHS
F1 -= tau * dot(u_n,v)* dx # Mass term known

# Define the variational form for the LHS and RHS 
a1 = form(lhs(F1)); l1 = form(rhs(F1))

#Assembling 
#A = assemble_matrix(a1, bcs=bcs_up)
A = create_matrix(a1)
b = create_vector(l1)

# Create LU solver
solver = PETSc.KSP().create(msh.comm)
solver.setOperators(A)
solver.setType("preonly")
solver.getPC().setType("lu")
solver.getPC().setFactorSolverType("superlu_dist")

w_n1 = Function(W); u_n1, p_n1 = w_n1.split()

# Time-stepping
t = 0.0; t_end = 1
cnt = 0

# Define the error
L2_error_u = form(dot(u_n1 - u_ex, u_n1 - u_ex) * dx) #L2 error for the velocity
L2_error_p = form((p_n1 - p_ex)**2 * dx) #L2 error for the pressure

L2_history_w = [];

while t <= t_end:

    t += dt; cnt += 1
    t = round(t, 6)
    print(f"t = {t:.5f}", end="\r")
    
    # Update the exact solution and saving
    u_ex, p_ex = comp_and_save_exact_sol(t)

    # Update the time-dependent boundary condition
    u_values.t = t; u_bc_fes.interpolate(u_values.eval)
    bc_u = dirichletbc(u_bc_fes, dofs_walls, W.sub(0))
    bc_p = dirichletbc(press_fes, point_dofs, W.sub(1))
    bcs_up = [bc_u, bc_p]

    # Assemble the matrix
    A.zeroEntries()
    assemble_matrix(A, a1, bcs=bcs_up)
    A.assemble()

    with b.localForm() as loc:
        loc.set(0)

    # Compute solution
    assemble_vector(b, l1)
    apply_lifting(b, [a1], [bcs_up])
    b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b, bcs_up)
    solver.solve(b, w_n1.vector)
    w_n1.x.scatter_forward()

    # Split the solution and save it
    u_n1, p_n1 = w_n1.split()

    #compute the L2 norm of the error for u and p
    error_L2_u = np.sqrt(msh.comm.allreduce(assemble_scalar(L2_error_u), op=MPI.SUM))
    error_L2_p = np.sqrt(msh.comm.allreduce(assemble_scalar(L2_error_p), op=MPI.SUM))
    print(f"Time: {t:.2e}; L2 error_u: {error_L2_u:.2e}; L2 error_p: {error_L2_p:.2e}")
    
    L2_history_w.append([t, error_L2_u, error_L2_p])

    #Update the solution
    u_n.x.array[:] = u_n1.x.array[:]
    p_n.x.array[:] = p_n1.x.array[:]

    # Save the solution in BP format
    u_temp = u_n.collapse(); p_temp = p_n.collapse()
    u_n_w.x.array[:] = u_temp.x.array[:]; p_n_w.x.array[:] = p_temp.x.array[:]
    u_sol.write(t); p_sol.write(t)

    # Save the boundary conditions in BP format
    set_bc(w_BC.vector, bcs_up)
    u_BC.x.array[:] = w_BC.sub(0).collapse().x.array[:]
    p_BC.x.array[:] = w_BC.sub(1).collapse().x.array[:]
    bc_u_w.write(t); bc_p_w.write(t)

    #only for solution comparation
    if cnt == 10:
        break

    stop()

# Save the L2 error history
with open(output / "L2_error_history.txt", "w") as f:
    f.write("Time L2_error_u L2_error_p\n")
    for L2_error in L2_history_w:
        f.write(f"{L2_error[0]} {L2_error[1]} {L2_error[2]}\n")

print('Computation completed ...')

uex.close(); pex.close()
u_sol.close(); p_sol.close()
bc_u_w.close(); bc_p_w.close()
solver.destroy()
b.destroy()

print('Solution written in output folder')

Your exact pressure is not correct.
I am not sure why you call

I am also not certain about your Taylor-green vortex solution for pressure. It should be as sketched here: