The order of convergence for solving the mixed Poisson problem

Hi, everyone.
I would like to solve a mixed poisson problem with homogeneous flux boundary conditions:

\sigma =-\grad u,
\div \sigma = -f.          in \Omaga

The boundary condition is:
\sigma \cdot n=0 on \partial \Omega.
And the exact solution of \sigma that I set already satisfies \sigma\cdot n=0 on the boundary.

This is my code:

from mpi4py import MPI # type: ignore
from petsc4py import PETSc
import numpy as np
import ufl
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc
from basix.ufl import element, mixed_element 
from dolfinx import default_real_type, fem, io, mesh 
from ufl import  TestFunctions, TrialFunctions, div, inner,dx

def Exactu(x):
    return (1/3)*x[0]**3 - (1/2)*x[0]**2 + (1/3)*x[1]**3 - (1/2)*x[1]**2

def Exact_sigma(x):
    temp1= x[0]**2 - x[0]
    temp2= x[1]**2 - x[1]
    return [temp1,temp2]


def source_term(x):
    return -2*x[0] - 2*x[1] + 2


def clamped_boundary(x):
    return np.isclose(x[0], 0.0) | np.isclose(x[0], 1.0) | np.isclose(x[1], 0.0) | np.isclose(x[1], 1.0)

hs = []
errors_sigma_L2 = []
errors_u_L2= []  

for i in range(4):
    n = 2 ** (i + 3)              
    h = 1.0 / n
    msh = mesh.create_unit_square(MPI.COMM_WORLD, n, n, mesh.CellType.triangle)

    k = 1
   #Q_el = element("BDMCF", msh.basix_cell(), k, dtype=default_real_type)   mesh.CellType.triangle
    Q_el = element("RT", msh.basix_cell(), k)
    P_el = element("DG", msh.basix_cell(), k - 1)
    V = fem.functionspace(msh, mixed_element([Q_el, P_el]))
    (sigma, u) = TrialFunctions(V)
    (tau, v) = TestFunctions(V)

    V0 = V.sub(0)    # RT元
    V1 = V.sub(1)    # P_k 元

    Q, _ = V0.collapse()   
    P, _ = V1.collapse() 

    f = fem.Function(P)
    f.interpolate(source_term)

    sigma_D = fem.Function(Q)
    sigma_D.interpolate(Exact_sigma)

    tdim = msh.topology.dim  
    fdim = tdim - 1  
    facets = mesh.locate_entities_boundary(msh,fdim,clamped_boundary)
    dofs = fem.locate_dofs_topological((V0, Q), fdim, facets)
    bc = fem.dirichletbc(sigma_D, dofs, V0)

    G1 = inner(sigma, tau) * dx +inner(u, div(tau)) * dx + inner(div(sigma), v) * dx 
    G2 = -inner(f, v) * dx

    a = fem.form(G1)
    L = fem.form(G2)


    A = assemble_matrix(a, bcs=[bc])
    A.assemble()
    b = create_vector(L)

    w_h = fem.Function(V)

    solver = PETSc.KSP().create(msh.comm)
    solver.setOperators(A)
    solver.setType(PETSc.KSP.Type.PREONLY)
    solver.getPC().setType("lu")
    solver.getPC().setFactorSolverType("mumps")

# 设置MUMPS专门选项 
    opts = PETSc.Options()
    opts["mat_mumps_icntl_14"] = 80    # 增加工作内存
    opts["mat_mumps_icntl_24"] = 1     # 支持奇异矩阵
    opts["mat_mumps_icntl_25"] = 0     # 支持奇异矩阵
    opts["ksp_error_if_not_converged"] = 1  # 错误检查

    solver.setFromOptions()


    with b.localForm() as loc_b:
      loc_b.set(0)
    assemble_vector(b, L)

   # 施加边界条件
    apply_lifting(b, [a], [[bc]])
    b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b, [bc])

# 求解线性系统
    solver.solve(b, w_h.x.petsc_vec)
    w_h.x.scatter_forward()

    sigma_h = w_h.sub(0).collapse() 
    u_h = w_h.sub(1).collapse()


# 计算误差
    V_ex = fem.functionspace(msh, ("DG", k+2))
    Vv_ex = fem.functionspace(msh, ("RT", k+2))

# u 的误差
    u_ex = fem.Function(V_ex)
    u_ex.interpolate(Exactu)

    sigma_ex = fem.Function(Vv_ex)
    sigma_ex.interpolate(Exact_sigma)

# L2 误差
    error_u_L2 = np.sqrt(msh.comm.allreduce(fem.assemble_scalar(fem.form(inner(u_h - u_ex, u_h - u_ex) * dx)), op=MPI.SUM))
    error_sigma_L2 = np.sqrt(msh.comm.allreduce(fem.assemble_scalar(fem.form(inner(sigma_h - sigma_ex, sigma_h - sigma_ex) * dx)), op=MPI.SUM))
    # 存储误差和网格尺寸
    hs.append(h)
    errors_u_L2.append(error_u_L2)  
    errors_sigma_L2.append(error_sigma_L2)
    
    if MPI.COMM_WORLD.rank == 0:
        print(f"网格尺寸: {n}x{n}, h = {h:.3f}")
        print(f"原始变量u的L2误差: {error_u_L2:.2e}")
        print(f"通量sigma的L2误差: {error_sigma_L2:.2e}")
        print("-" * 50)

# 计算并输出收敛阶
if MPI.COMM_WORLD.rank == 0 and len(hs) > 1:
    print("\n" + "="*60)
    print("收敛阶分析:")
    print("="*60)
    print(f"{'网格尺寸':<12} {'h':<8} {'u误差':<12} {'u收敛阶':<10} {'σ误差':<12} {'σ收敛阶':<10}")
    print("-" * 60)
    
    for i in range(len(hs)):
        if i == 0:
            order_u = 0.0
            order_sigma = 0.0
        else:
            order_u = np.log(errors_u_L2[i-1] / errors_u_L2[i]) / np.log(hs[i-1] / hs[i])
            order_sigma = np.log(errors_sigma_L2[i-1] / errors_sigma_L2[i]) / np.log(hs[i-1] / hs[i])
        n = 2 ** (i + 3)
        print(f"{n}x{n:<8} {hs[i]:<8.3f} {errors_u_L2[i]:<12.2e} {order_u:<10.2f} {errors_sigma_L2[i]:<12.2e} {order_sigma:<10.2f}")

The following is my running result:

               h        error_u          convergence of u     error_sigma       convergence of sigma

8x8 0.125 3.29e-01 0.00 2.95e-02 0.00
16x16 0.062 2.11e-01 0.64 1.47e-02 1.00
32x32 0.031 1.53e-01 0.46 7.37e-03 1.00
64x64 0.016 1.70e-01 -0.15 3.68e-03 1.00

I don’t know what the problem is. I’m looking forward to your reply and would be very grateful.

Are you thinking about the convergence in u?
This is because you only have boundary conditions for the flux, meaning that the solution is not unique.

If you correct the solution by the constant of the exact solution, you get (code adapted from: Mixed formulation of the Poisson equation with a block-preconditioner/solver # noqa — DOLFINx 0.10.0.post3 documentation) under LGPL license:

Polynomial degree k=1
L2 u errors:  [0.01808778 0.00905067 0.00452672 0.00226355 0.0011318  0.0005659 ]
L2 sigma errors:  [0.05929271 0.02950878 0.01473714 0.00736641 0.00368294 0.00184144]

Convergence rates:
u:  [0.99891943 0.9995594  0.99987923 0.99996914 0.99999224]
sigma:  [1.00671026 1.00168736 1.00042246 1.00010565 1.00002642]

Polynomial degree k=2
L2 u errors:  [1.43391565e-03 3.65857955e-04 9.19198942e-05 2.30083484e-05
 5.75385915e-06 1.43857552e-06]
L2 sigma errors:  [3.10564997e-03 7.76412492e-04 1.94103123e-04 4.85257808e-05
 1.21314452e-05 3.03286130e-06]

Convergence rates:
u:  [1.97060463 1.99283459 1.99821971 1.99955561 1.99988895]
sigma:  [2. 2. 2. 2. 2.]

Polynomial degree k=3
L2 u errors:  [1.17644258e-04 1.47055323e-05 1.83819153e-06 2.29773942e-07
 2.87217427e-08 3.59021784e-09]
L2 sigma errors:  [1.34947022e-15 3.72984443e-15 2.88278712e-15 1.04261911e-14
 4.70155699e-14 1.95761725e-13]

Convergence rates:
u:  [3. 3. 3. 3. 3.]
sigma:  [-1.46672232  0.37165115 -1.854676   -2.1729264  -2.05788821]

from mpi4py import MPI
from petsc4py import PETSc

import numpy as np

import dolfinx.fem.petsc
import ufl
from basix.ufl import element
from dolfinx import fem, mesh
from dolfinx.mesh import CellType, create_unit_square

dtype = dolfinx.default_scalar_type
xdtype = dolfinx.default_real_type


def solve_problem(N, k):
    msh = create_unit_square(MPI.COMM_WORLD, N, N, CellType.triangle, dtype=xdtype)

    # +
    V = fem.functionspace(msh, element("RT", msh.basix_cell(), k, dtype=xdtype))
    W = fem.functionspace(msh, element("Discontinuous Lagrange", msh.basix_cell(), k - 1, dtype=xdtype))
    Q = ufl.MixedFunctionSpace(V, W)

    sigma, u = ufl.TrialFunctions(Q)
    tau, v = ufl.TestFunctions(Q)

    def Exactu(x):
        return (1/3)*x[0]**3 - (1/2)*x[0]**2 + (1/3)*x[1]**3 - (1/2)*x[1]**2

    def Exact_sigma(x):
        return ufl.grad(Exactu(x))

    def f(x):
        return -ufl.div(Exact_sigma(x))
    x = ufl.SpatialCoordinate(msh)


    dx = ufl.Measure("dx", msh)

    a = ufl.extract_blocks(
        ufl.inner(sigma, tau) * dx + ufl.inner(u, ufl.div(tau)) * dx + ufl.inner(ufl.div(sigma), v) * dx
    )
    L = [ufl.ZeroBaseForm((tau,)), -ufl.inner(f(x), v) * dx]


    msh.topology.create_connectivity(msh.topology.dim - 1, msh.topology.dim)
    boundary_facets = mesh.exterior_facet_indices(msh.topology)
    boundary_dofs = fem.locate_dofs_topological(V, 1, boundary_facets)
    g = fem.Function(V, dtype=dtype)
    bcs = [fem.dirichletbc(g, boundary_dofs)]
    sigma, u = fem.Function(V, name="sigma", dtype=dtype), fem.Function(W, name="u", dtype=dtype)

    problem = fem.petsc.LinearProblem(
        a,
        L,
        u=[sigma, u],
        kind="mpi",
        bcs=bcs,
        petsc_options_prefix="demo_mixed_poisson_",
        petsc_options={
            "ksp_type": "preonly",
            "pc_type": "lu",
            "pc_factor_mat_solver_type": "mumps",
            "ksp_error_if_not_converged": True,
            # "ksp_monitor" : None,
            "mat_mumps_icntl_24": 1,
            "mat_mumps_icntl_25": 0
        },
    )
    # -


    # +
    ksp = problem.solver
    # ksp.setMonitor(
    #     lambda _, its, rnorm: PETSc.Sys.Print(f"Iteration: {its:>4d}, residual: {rnorm:.3e}")
    # )
    problem.solve()
    converged_reason = problem.solver.getConvergedReason()
    assert converged_reason > 0, f"Krylov solver has not converged, reason: {converged_reason}."
    if dolfinx.has_adios2:
        from dolfinx.io import VTXWriter

        with VTXWriter(msh.comm, "output_mixed_poisson.bp", u) as f:
            f.write(0.0)
    else:
        print("ADIOS2 required for VTX output.")
    # -


    avg_u = fem.assemble_scalar(fem.form(u * dx))
    avg_u = msh.comm.allreduce(avg_u, op=MPI.SUM)
    avg_u_ex = fem.assemble_scalar(fem.form(Exactu(x) * dx))
    avg_u_ex = msh.comm.allreduce(avg_u_ex, op=MPI.SUM)

    u.x.array[:] += -avg_u + avg_u_ex

    error_L2 = ufl.inner(u - Exactu(x), u - Exactu(x)) * dx
    error_L2 = fem.assemble_scalar(fem.form(error_L2))
    error_L2 = np.sqrt(msh.comm.allreduce(error_L2, op=MPI.SUM))

    error_L2_sigma = ufl.inner(sigma - Exact_sigma(x), sigma - Exact_sigma(x)) * dx
    error_L2_sigma = fem.assemble_scalar(fem.form(error_L2_sigma))
    error_L2_sigma = np.sqrt(msh.comm.allreduce(error_L2_sigma, op=MPI.SUM))
    # if msh.comm.rank == 0:
    #     print(f"L2 error in u: {error_L2:.6e}")
    # if msh.comm.rank == 0:
    #     print(f"L2 error in sigma: {error_L2_sigma:.6e}")

    return error_L2, error_L2_sigma

if __name__ == "__main__":
    for k in range(1, 4):
        print(f"\nPolynomial degree k={k}")
        Ns = np.array([4, 8, 16, 32, 64, 128], dtype=np.int32)
        errors_u = np.zeros_like(Ns, dtype=dtype)
        errors_sigma = np.zeros_like(Ns, dtype=dtype)
        for i, N in enumerate(Ns):
            errors_u[i], errors_sigma[i] = solve_problem(N, k)
        print(f"L2 u errors: ", errors_u)
        print(f"L2 sigma errors: ", errors_sigma)
        print(f"\nConvergence rates:")
        print("u: ", np.log2(errors_u[:-1] / errors_u[1:]) / np.log2(Ns[1:] / Ns[:-1]))
        print("sigma: ", np.log2(errors_sigma[:-1] / errors_sigma[1:]) / np.log2(Ns[1:] / Ns[:-1]))

If you are confused about the k order of convergence of sigma where you might expect k+1, then you’d have to change the RT elements to BDM elements. See The convergence order of H(div) conforming DG method for the NS equation

1 Like

Thanks for your reply, I added an average correction to u, and now I have obtained the correct order of convergence.

Thank you for your reply. I tried doing this and got a good result

Sorry, Sir.
My purpose was to solve the mixed poisson equation with homogeneous Newman boundary conditions on a curved region (disk), as you said, on a rectangular region, I added the average correction and already got the correct convergence order.
But now there are problems solving in the curved edge region. Here is my code:

import gmsh
from dolfinx.io import gmshio
from mpi4py import MPI # type: ignore
from petsc4py import PETSc
import numpy as np
import ufl
from dolfinx import default_scalar_type
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc
from basix.ufl import element, mixed_element 
from dolfinx import default_real_type, fem, io, mesh 
from ufl import  TestFunctions, TrialFunctions, div, inner,dx

def Exactu(x):
    r = x[0]**2 + x[1]**2
    return [4*x[0]*(1-r), 4*x[1]*(1-r)]

def Exactp(x):
    r = x[0]**2 + x[1]**2
    return (1-r)**2 - 1/3  


def source_term(x):
    r = x[0]**2 + x[1]**2
    return 8 - 16*r


def clamped_boundary(x):
    return np.isclose(np.sqrt(x[0]**2 + x[1]**2), 1)

hs = []
errors_p_L2 = []
errors_u_L2= []  

for i in range(5):
    n = 2 ** (i + 3)              
    h = 1.0 / n
    # 生成曲面网格
    gmsh.initialize() # 网格初始化
    membrane = gmsh.model.occ.addDisk(0, 0, 0, 1, 1)  # 生成单位圆盘
    gmsh.model.occ.synchronize()
    gdim = 2
    gmsh.model.addPhysicalGroup(gdim, [membrane], 1)
    gmsh.option.setNumber("Mesh.CharacteristicLengthMin", h)
    gmsh.option.setNumber("Mesh.CharacteristicLengthMax", h)
    gmsh.option.setNumber("Mesh.ElementOrder", 2)  
    gmsh.option.setNumber("Mesh.SecondOrderLinear", 0)  
    gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 0)  
    gmsh.model.mesh.generate(gdim)

    gmsh_model_rank = 0
    mesh_comm = MPI.COMM_WORLD
    msh, cell_markers, facet_markers = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)
    gmsh.finalize()

    k = 2
    Q_el = element("RT", msh.basix_cell(), k)
    P_el = element("DG", msh.basix_cell(), k - 1)
    V = fem.functionspace(msh, mixed_element([Q_el, P_el]))
    (u, p) = TrialFunctions(V)
    (v, q) = TestFunctions(V)

    V0 = V.sub(0)    # RT元
    V1 = V.sub(1)    # P_k 元

    Q, _ = V0.collapse()   
    P, _ = V1.collapse() 

    f = fem.Function(P)
    f.interpolate(source_term)

    u_D = fem.Function(Q)
    u_D.interpolate(Exactu)

    tdim = msh.topology.dim  
    fdim = tdim - 1  
    facets = mesh.locate_entities_boundary(msh, fdim, clamped_boundary)
    dofs = fem.locate_dofs_topological((V0, Q), fdim, facets)
    bc = fem.dirichletbc(u_D, dofs, V0)

    G1 = inner(u, v) * dx - inner(p, div(v)) * dx + inner(div(u), q) * dx 
    G2 = inner(f, q) * dx

    a = fem.form(G1)
    L = fem.form(G2)


    A = assemble_matrix(a, bcs=[bc])
    A.assemble()
    b = create_vector(L)

    w_h = fem.Function(V)

    solver = PETSc.KSP().create(msh.comm)
    solver.setOperators(A)
    solver.setType(PETSc.KSP.Type.PREONLY)
    solver.getPC().setType("lu")
    solver.getPC().setFactorSolverType("mumps")

# 设置MUMPS专门选项 
    opts = PETSc.Options()
    opts["mat_mumps_icntl_14"] = 80    # 增加工作内存
    opts["mat_mumps_icntl_24"] = 1     # 支持奇异矩阵
    opts["mat_mumps_icntl_25"] = 0     # 支持奇异矩阵
    opts["ksp_error_if_not_converged"] = 1  # 错误检查

    solver.setFromOptions()


    with b.localForm() as loc_b:
      loc_b.set(0)
    assemble_vector(b, L)

   # 施加边界条件
    apply_lifting(b, [a], [[bc]])
    b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b, [bc])

# 求解线性系统
    solver.solve(b, w_h.x.petsc_vec)
    w_h.x.scatter_forward()

    u_h = w_h.sub(0).collapse() 
    p_h = w_h.sub(1).collapse()

    # 1. 计算数值解u_h的平均值
    avg_p_numerical = fem.assemble_scalar(fem.form(p_h * dx))
    avg_p_numerical = msh.comm.allreduce(avg_p_numerical, op=MPI.SUM)
    
    p_ex_temp = fem.Function(P)
    p_ex_temp.interpolate(Exactp)
    avg_p_exact = fem.assemble_scalar(fem.form(p_ex_temp * dx))
    avg_p_exact = msh.comm.allreduce(avg_p_exact, op=MPI.SUM)
    
    # 3. 计算修正值并应用
    correction = avg_p_exact - avg_p_numerical
    p_h.x.array[:] += correction  


# 计算误差
    V_ex = fem.functionspace(msh, ("DG", k+2))
    Vv_ex = fem.functionspace(msh, ("RT", k+2))

    p_ex = fem.Function(V_ex)
    p_ex.interpolate(Exactp)

    u_ex = fem.Function(Vv_ex)
    u_ex.interpolate(Exactu)

# L2 误差
    error_u_L2 = np.sqrt(msh.comm.allreduce(fem.assemble_scalar(fem.form(inner(u_h - u_ex, u_h - u_ex) * dx)), op=MPI.SUM))
    error_p_L2 = np.sqrt(msh.comm.allreduce(fem.assemble_scalar(fem.form(inner(p_h - p_ex, p_h - p_ex) * dx)), op=MPI.SUM))
    # 存储误差和网格尺寸
    hs.append(h)
    errors_u_L2.append(error_u_L2)  
    errors_p_L2.append(error_p_L2)
    
    if MPI.COMM_WORLD.rank == 0:
        print(f"网格尺寸: {n}x{n}, h = {h:.3f}")
        print(f"原始变量p的L2误差: {error_p_L2:.2e}")
        print(f"通量u的L2误差: {error_u_L2:.2e}")
        print("-" * 50)

# 计算并输出收敛阶
if MPI.COMM_WORLD.rank == 0 and len(hs) > 1:
    print("\n" + "="*60)
    print("收敛阶分析:")
    print("="*60)
    print(f"{'网格尺寸':<12} {'h':<8} {'u误差':<12} {'u收敛阶':<10} {'p误差':<12} {'p收敛阶':<10}")
    print("-" * 60)
    
    for i in range(len(hs)):
        if i == 0:
            order_u = 0.0
            order_p = 0.0
        else:
            # 计算收敛阶: order = log(error_i/error_{i-1}) / log(h_i/h_{i-1})
            order_u = np.log(errors_u_L2[i-1] / errors_u_L2[i]) / np.log(hs[i-1] / hs[i])
            order_p = np.log(errors_p_L2[i-1] / errors_p_L2[i]) / np.log(hs[i-1] / hs[i])
        n = 2 ** (i + 3)
        print(f"{n}x{n:<8} {hs[i]:<8.3f} {errors_u_L2[i]:<12.2e} {order_u:<10.2f} {errors_p_L2[i]:<12.2e} {order_p:<10.2f}")

The running result is:

============================================================
              h           error_u          convergence_u       error_p          convergence_p      
------------------------------------------------------------
8x8        0.125        1.78e-01        0.00                   1.16e+00                 0.00      
16x16       0.062     5.49e-02        1.70                   8.24e-01               0.49      
32x32       0.031     2.41e-02        1.19                   7.57e+11            -39.74    
64x64       0.016    1.74e-03         3.79                  2.72e+10             4.80      
128x128      0.008    2.60e-04       2.74                 2.04e+09              3.74 

Looking forward to your reply. Thank you!

The averaging is slightly wrong here. In my previous code, a unit square, the area is 1. This is no longer the case in your problem, i.e.

is wrong, and should be replaced with:

 w_h.x.scatter_forward()

    u_h = w_h.sub(0).collapse() 
    p_h = w_h.sub(1).collapse()

    # 计   x = ufl.SpatialCoordinate(msh)
    avg_p = fem.assemble_scalar(fem.form(p_h * dx))
    avg_p = msh.comm.allreduce(avg_p, op=MPI.SUM)

    area = fem.assemble_scalar(fem.form(fem.Constant(msh, 1.0) * dx))
    area = msh.comm.allreduce(area, op=MPI.SUM)
    avg_p /= area

    x = ufl.SpatialCoordinate(msh)
    avg_p_ex = fem.assemble_scalar(fem.form(Exactp(x) * dx))
    avg_p_ex = msh.comm.allreduce(avg_p_ex, op=MPI.SUM)
    avg_p_ex /= area


    p_h.x.array[:] += -avg_p + avg_p_ex

Full script:

import gmsh
from dolfinx.io import gmshio
from mpi4py import MPI # type: ignore
from petsc4py import PETSc
import numpy as np
import ufl
from dolfinx import default_scalar_type
from dolfinx.fem.petsc import assemble_matrix, assemble_vector, apply_lifting, create_vector, set_bc
from basix.ufl import element, mixed_element 
from dolfinx import default_real_type, fem, io, mesh 
from ufl import  TestFunctions, TrialFunctions, div, inner,dx

def Exactu(x):
    r = x[0]**2 + x[1]**2
    return [4*x[0]*(1-r), 4*x[1]*(1-r)]

def Exactp(x):
    r = x[0]**2 + x[1]**2
    return (1-r)**2 - 1/3  


def source_term(x):
    r = x[0]**2 + x[1]**2
    return 8 - 16*r


def clamped_boundary(x):
    return np.isclose(np.sqrt(x[0]**2 + x[1]**2), 1)

hs = []
errors_p_L2 = []
errors_u_L2= []  

for i in range(5):
    n = 2 ** (i + 3)              
    h = 1.0 / n
    # 生成曲面网格
    gmsh.initialize() # 网格初始化
    membrane = gmsh.model.occ.addDisk(0, 0, 0, 1, 1)  # 生成单位圆盘
    gmsh.model.occ.synchronize()
    gdim = 2
    gmsh.model.addPhysicalGroup(gdim, [membrane], 1)
    gmsh.option.setNumber("Mesh.CharacteristicLengthMin", h)
    gmsh.option.setNumber("Mesh.CharacteristicLengthMax", h)
    gmsh.option.setNumber("Mesh.ElementOrder", 2)  
    gmsh.option.setNumber("Mesh.SecondOrderLinear", 0)  
    gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 0)  
    gmsh.model.mesh.generate(gdim)

    gmsh_model_rank = 0
    mesh_comm = MPI.COMM_WORLD
    msh, cell_markers, facet_markers = gmshio.model_to_mesh(gmsh.model, mesh_comm, gmsh_model_rank, gdim=gdim)
    gmsh.finalize()

    k = 3
    Q_el = element("RT", msh.basix_cell(), k)
    P_el = element("DG", msh.basix_cell(), k - 1)
    V = fem.functionspace(msh, mixed_element([Q_el, P_el]))
    (u, p) = TrialFunctions(V)
    (v, q) = TestFunctions(V)

    V0 = V.sub(0)    # RT元
    V1 = V.sub(1)    # P_k 元

    Q, _ = V0.collapse()   
    P, _ = V1.collapse() 

    f = fem.Function(P)
    f.interpolate(source_term)

    u_D = fem.Function(Q)
    u_D.interpolate(Exactu)

    tdim = msh.topology.dim  
    fdim = tdim - 1  
    facets = mesh.locate_entities_boundary(msh, fdim, clamped_boundary)
    dofs = fem.locate_dofs_topological((V0, Q), fdim, facets)
    bc = fem.dirichletbc(u_D, dofs, V0)

    G1 = inner(u, v) * dx - inner(p, div(v)) * dx + inner(div(u), q) * dx 
    G2 = inner(f, q) * dx

    a = fem.form(G1)
    L = fem.form(G2)


    A = assemble_matrix(a, bcs=[bc])
    A.assemble()
    b = create_vector(L)

    w_h = fem.Function(V)

    solver = PETSc.KSP().create(msh.comm)
    solver.setOperators(A)
    solver.setType(PETSc.KSP.Type.PREONLY)
    solver.getPC().setType("lu")
    solver.getPC().setFactorSolverType("mumps")

# 设置MUMPS专门选项 
    opts = PETSc.Options()
    opts["mat_mumps_icntl_14"] = 80    # 增加工作内存
    opts["mat_mumps_icntl_24"] = 1     # 支持奇异矩阵
    opts["mat_mumps_icntl_25"] = 0     # 支持奇异矩阵
    opts["ksp_error_if_not_converged"] = 1  # 错误检查

    solver.setFromOptions()


    with b.localForm() as loc_b:
      loc_b.set(0)
    assemble_vector(b, L)

   # 施加边界条件
    apply_lifting(b, [a], [[bc]])
    b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
    set_bc(b, [bc])

# 求解线性系统
    solver.solve(b, w_h.x.petsc_vec)
    w_h.x.scatter_forward()

    u_h = w_h.sub(0).collapse() 
    p_h = w_h.sub(1).collapse()

    # 计   x = ufl.SpatialCoordinate(msh)
    avg_p = fem.assemble_scalar(fem.form(p_h * dx))
    avg_p = msh.comm.allreduce(avg_p, op=MPI.SUM)

    area = fem.assemble_scalar(fem.form(fem.Constant(msh, 1.0) * dx))
    area = msh.comm.allreduce(area, op=MPI.SUM)
    avg_p /= area

    x = ufl.SpatialCoordinate(msh)
    avg_p_ex = fem.assemble_scalar(fem.form(Exactp(x) * dx))
    avg_p_ex = msh.comm.allreduce(avg_p_ex, op=MPI.SUM)
    avg_p_ex /= area


    p_h.x.array[:] += -avg_p + avg_p_ex



    # L2 误差
    error_u_L2 = np.sqrt(msh.comm.allreduce(fem.assemble_scalar(fem.form(inner(u_h - ufl.as_vector(Exactu(x)), u_h - ufl.as_vector(Exactu(x))) * dx)), op=MPI.SUM))
    error_p_L2 = np.sqrt(msh.comm.allreduce(fem.assemble_scalar(fem.form(inner(p_h - Exactp(x), p_h - Exactp(x)) * dx)), op=MPI.SUM))
    # 存储误差和网格尺寸
    hs.append(h)
    errors_u_L2.append(error_u_L2)  
    errors_p_L2.append(error_p_L2)
    
    if MPI.COMM_WORLD.rank == 0:
        print(f"网格尺寸: {n}x{n}, h = {h:.3f}")
        print(f"原始变量p的L2误差: {error_p_L2:.2e}")
        print(f"通量u的L2误差: {error_u_L2:.2e}")
        print("-" * 50)

# 计算并输出收敛阶
if MPI.COMM_WORLD.rank == 0 and len(hs) > 1:
    print("\n" + "="*60)
    print("收敛阶分析:")
    print("="*60)
    print(f"{'网格尺寸':<12} {'h':<8} {'u误差':<12} {'u收敛阶':<10} {'p误差':<12} {'p收敛阶':<10}")
    print("-" * 60)
    
    for i in range(len(hs)):
        if i == 0:
            order_u = 0.0
            order_p = 0.0
        else:
            # 计算收敛阶: order = log(error_i/error_{i-1}) / log(h_i/h_{i-1})
            order_u = np.log(errors_u_L2[i-1] / errors_u_L2[i]) / np.log(hs[i-1] / hs[i])
            order_p = np.log(errors_p_L2[i-1] / errors_p_L2[i]) / np.log(hs[i-1] / hs[i])
        n = 2 ** (i + 3)
        print(f"{n}x{n:<8} {hs[i]:<8.3f} {errors_u_L2[i]:<12.2e} {order_u:<10.2f} {errors_p_L2[i]:<12.2e} {order_p:<10.2f}")

yields

网格尺寸         h        u误差          u收敛阶       p误差          p收敛阶      
------------------------------------------------------------
8x8        0.125    5.29e-04     0.00       1.11e-04     0.00      
16x16       0.062    5.12e-04     0.05       5.18e-05     1.10      
32x32       0.031    2.26e-06     7.83       1.74e-06     4.89      
64x64       0.016    2.94e-08     6.26       2.15e-07     3.02      
128x128      0.008    4.56e-09     2.69       2.70e-08     2.99    

Note that I increased the degree to 3