def solve_manufactured_problem(n, degree):
msh = mesh.create_unit_square(MPI.COMM_WORLD, n, n)
V = fem.functionspace(msh, ("Lagrange", degree))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
x = ufl.SpatialCoordinate(msh)
u_exact = ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1])
f = 2.0 * ufl.pi**2 * u_exact
a = ufl.dot(ufl.grad(u), ufl.grad(v)) * ufl.dx
L = f * v * ufl.dx
fdim = msh.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(
msh,
fdim,
lambda x: np.isclose(x[0], 0.0)
| np.isclose(x[0], 1.0)
| np.isclose(x[1], 0.0)
| np.isclose(x[1], 1.0),
)
dofs = fem.locate_dofs_topological(V, fdim, boundary_facets)
bc = fem.dirichletbc(PETSc.ScalarType(0.0), dofs, V)
linear_problem_kwargs = {
"petsc_options": {"ksp_type": "preonly", "pc_type": "lu"}
}
if "petsc_options_prefix" in inspect.signature(LinearProblem).parameters:
linear_problem_kwargs["petsc_options_prefix"] = f"mms_p{degree}_{n}_"
problem = LinearProblem(a, L, bcs=[bc], **linear_problem_kwargs)
uh = problem.solve()
error_sq_local = fem.assemble_scalar(fem.form((uh - u_exact) ** 2 * ufl.dx))
error_sq = msh.comm.allreduce(error_sq_local, op=MPI.SUM)
return 1.0 / n, np.sqrt(error_sq.real)
n_values = [4, 8, 16, 32]
convergence_results = {}
for degree in (1, 2):
hs = []
errors = []
for n in n_values:
h, error_L2 = solve_manufactured_problem(n, degree)
hs.append(h)
errors.append(error_L2)
hs = np.array(hs)
errors = np.array(errors)
orders = np.log(errors[:-1] / errors[1:]) / np.log(hs[:-1] / hs[1:])
fitted_order = np.polyfit(np.log(hs), np.log(errors), 1)[0]
convergence_results[degree] = {
"h": hs,
"error_L2": errors,
"orders": orders,
"fitted_order": fitted_order,
}
if MPI.COMM_WORLD.rank == 0:
for degree, data in convergence_results.items():
print(f"P{degree} elements")
print(" h L2 error observed order")
for i, (h, err) in enumerate(zip(data["h"], data["error_L2"])):
order_text = "---" if i == 0 else f"{data['orders'][i - 1]:.3f}"
print(f" {h:0.5f} {err:0.6e} {order_text}")