In one of my simulations, I noticed some potential inaccuracies after integration and I am trying to understand. Please consider the following simple code that performs integration on the same function represented two different ways. Method A is the interpolation of a function and then integrating, Method B is directly integrating a non-interpolated function. Can someone please help me understand the difference between the two cases? I expect them to be the same!
from dolfinx import mesh, fem, nls, plot, cpp
import ufl
# setup
nx, ny = 20,20
domain = mesh.create_rectangle(comm=MPI.COMM_WORLD, points=((-1., -1.), (1., 1.)),n=(nx, ny), cell_type=mesh.CellType.triangle)
W = fem.VectorFunctionSpace(domain, ("CG",1), dim = 2)
W0,_ = W.sub(0).collapse()
# create some functions
F = fem.Function(W)
f1, f2 = ufl.split(F)
F.sub(0).interpolate(lambda x: x[0]**2)
F.sub(1).interpolate(lambda x: x[1]+2)
# Two ways to represent the same function
A = fem.Function(W0)
B = f1/f2
A.interpolate(fem.Expression(B,W0.element.interpolation_points()))
# Look at the integration:
# Method A:
fem.assemble_scalar(fem.form(A*ufl.dx))
0.7365659808912665
# Method B:
fem.assemble_scalar(fem.form(B*ufl.dx))
0.7360701674392127
Dividing a CG-1 function by another CG-1 function is not in CG-1, and thus by interpolating prior to integrating, you introduce an error.
Consider the following:
Use a CG2 space to have each of the polynomials being represented exactly, then the integral should be equal to \frac{2\log 3}{3}.
import numpy as np
from dolfinx import mesh, fem
import ufl
from mpi4py import MPI
# setup
nx, ny = 20, 20
domain = mesh.create_rectangle(comm=MPI.COMM_WORLD, points=(
(-1., -1.), (1., 1.)), n=(nx, ny), cell_type=mesh.CellType.triangle)
W = fem.VectorFunctionSpace(domain, ("Lagrange", 2), dim=2)
W0, _ = W.sub(0).collapse()
F = fem.Function(W)
f1, f2 = ufl.split(F)
F.sub(0).interpolate(lambda x: x[0]**2)
F.sub(1).interpolate(lambda x: x[1]+2)
# Two ways to represent the same function
A = fem.Function(W0)
B = f1/f2
A.interpolate(fem.Expression(B, W0.element.interpolation_points()))
# Look at the integration:
# Method A:
A = fem.assemble_scalar(fem.form(A*ufl.dx))
# Method B:
B = fem.assemble_scalar(fem.form(B*ufl.dx))
exact = 2 * np.log(3)/3
print(f"{exact=} {A=} {B=} {A-exact=:.2e} {B-exact=:.2e}")
Thank you. The behaviour is a little different if we consider the case of constants, still with CG1. Printing the error as you did, I would expect to be a couple orders lower for machine precision/exact integration. No?
from dolfinx import mesh, fem, nls, plot, cpp
import ufl
from mpi4py import MPI
# setup
nx, ny = 20,20
domain = mesh.create_rectangle(comm=MPI.COMM_WORLD, points=((-1., -1.), (1., 1.)),n=(nx, ny), cell_type=mesh.CellType.triangle)
W = fem.VectorFunctionSpace(domain, ("CG",1), dim = 2)
W0,_ = W.sub(0).collapse()
# create some functions
F = fem.Function(W)
f1, f2 = ufl.split(F)
F.sub(0).x.set(2)
F.sub(1).x.set(2)
# Two ways to express the same function
A = fem.Function(W0)
B = f1/f2
A.interpolate(fem.Expression(B,W0.element.interpolation_points()))
# Look at the integration:
# Method A:
A = fem.assemble_scalar(fem.form(A*ufl.dx))
# Method B:
B = fem.assemble_scalar(fem.form(B*ufl.dx))
exact = 4.
print(f"{exact=} {A=} {B=} {A-exact=:.2e} {B-exact=:.2e}")
This here is quite dangerous, it would not work if you wanted to set different numbers to each sub-space.
Machine precision (1e-16) is rarely achieved when one do sums, divisions etc. The error can accumulate.
Say we have number a and b, which is the same up to 10e-16, i.e. a = c + A \cdot 10^{-16}, b= c + B\cdot 10^{-16}, then