Natural boundary condition is not satisfied

Hello,
I am implementing Navier-Stokes equations in Fenics, following this example. The manifold \Omega is a rectangle, and \partial \Omega_{\rm IN} the left boundary of \Omega. Among the boundary conditions for the pressure increment, \phi, I impose

n^i \partial_i \phi = 0

on \partial \Omega_{\rm IN}. I implement these BCs by setting to zero the term

\langle n^i \partial_i \phi \, \nu_\phi \rangle_{\partial \Omega_{\rm IN}} = 0
in the variational functional, where n is the normal outwards vector and \nu_{\phi} the test function related to \phi.

However, if I then compute

\int _{\partial \Omega_{\rm IN}} n^i \partial_i \phi d S
I obtain a value which is significantly different from zero.

Here is a minimal working example (mesh files are here):

from __future__ import print_function
from fenics import *
from mshr import *
import meshio
import ufl as ufl

#CHANGE PARAMETERS HERE
tol = 1E-3
L = 2.2
h = 0.41
T = 0.001
num_steps = 4
#CHANGE PARAMETERS HERE

dt = T / num_steps  # time step size
mu = 0.001
rho = 1.0

i, j, k = ufl.indices(3)

#create mesh
mesh=Mesh()
with XDMFFile("triangle_mesh.xdmf") as infile:
    infile.read(mesh)
mvc = MeshValueCollection("size_t", mesh, 2)
with XDMFFile("line_mesh.xdmf") as infile:
    infile.read(mvc, "name_to_read")

O = VectorFunctionSpace(mesh, 'P', 2, dim=2)
Q = FunctionSpace(mesh, 'P', 1)

v = Function(O)
v_ = Function(O)
sigma = Function(Q)
phi = Function(Q)
nu = TestFunction(O)
q = TestFunction(Q)

inflow   = 'near(x[0], 0)'
outflow  = 'near(x[0], 2.2)'
walls    = 'near(x[1], 0) || near(x[1], 0.41)'


mf = dolfin.cpp.mesh.MeshFunctionSizet(mesh, mvc)

#test for surface elements
ds_l = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=2)
ds_r = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=3)
ds_t = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=4)
ds_b = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=5)


xdmffile_v = XDMFFile("v.xdmf")
xdmffile_sigma = XDMFFile( "sigma.xdmf")

v_n = Function(O)
v_ = Function(O)
sigma_n = Function(Q)
sigma_ = Function(Q)

inflow_profile_v = Expression(('4.0*1.5*x[1]*(0.41 - x[1]) / pow(0.41, 2)', '0'), degree=2, h=h)

# Define symmetric gradient
def epsilon(u):
    # nabla_grad(u)_{i,j} = (u[j]).dx[i]
    #sym(nabla_grad(u)) =  nabla_grad(u)_{i,j} + nabla_grad(u)_{j,i}
    # return sym(nabla_grad(u))
    return as_tensor(0.5*(u[i].dx(j) + u[j].dx(i)), (i,j))

#the normal vector on the inflow and outflow
def n_inout():
    x = ufl.SpatialCoordinate(mesh)
    u = as_tensor([conditional(lt(x[0], L/2), -1.0, 1.0), 0.0] )
    return as_tensor(u[k], (k))

# Define boundary conditions
bcv_inflow = DirichletBC(O, inflow_profile_v, inflow)
bcv_walls = DirichletBC(O, Constant((0, 0)), walls)
bc_phi_outflow = DirichletBC(Q, Constant(0), outflow)
bc_v = [bcv_inflow, bcv_walls]
bc_phi = [bc_phi_outflow]

V = 0.5 * (v_n + v_)
Deltat = Constant(dt)
mu = Constant(mu)
rho = Constant(rho)

# step 1
F1v = rho * ( (v_[i] - v_n[i]) / Deltat + v_n[j] *((v_n[i]).dx(j)) ) * nu[i] * dx \
      + ( 2.0 * mu * (epsilon(V))[i, j] * (nu[i]).dx(j) + sigma_n * (nu[i]).dx(i) ) * dx \
      + (- sigma_n * n_inout()[i] * nu[i]  - mu * (V[j]).dx(i) * n_inout()[j] * nu[i]) * ds

F2 = ( (phi.dx(i)) * (q.dx(i))  + (rho / Deltat) * ((v_[i]).dx(i)) * q ) * dx

F3v = ((v_[i] - v[i]) - (Deltat / rho) * (phi.dx(i))) * nu[i] * dx


print("Starting time iteration ...", flush=True)
# Time-stepping
t = 0
for n in range(num_steps):
    t += dt

    solve(F1v == 0, v_, bc_v)
    solve(F2 ==0, phi, bc_phi)
    solve(F3v == 0, v)

    # Update previous solution
    v_n.assign(v)
    sigma_n.assign(-phi + sigma_n)

    # Write the solution to file
    xdmffile_v.write(v_n, t)
    xdmffile_sigma.write(sigma_n, t)

    print("BC = ", assemble(((phi.dx(i)) * (n_inout())[i])**2 * ds_l))
    print("\t%.2f %%" % (100.0 * (t / T)), flush=True)

print("... done.", flush=True)

The natural BC is imposed in F2 and the printout of the natural BC is in print("BC = ", assemble(((phi.dx(i)) * (n_inout())[i])**2 * ds_l)).

The output is

Starting time iteration ...
[...] BC =  5746230.869323557
	25.00 %
[...]
BC =  3392726.0511582345
	50.00 %
[...]

Why is this residual so large ? Thank you !

First of all, why do you need to use an external mesh to illustrate this? Your mesh is a square channel, that can easily be created with a RectangleMesh.
Secondly, your mesh is incredibly coarse, and you should check who the solution changes with mesh refinement.

This also boils down to solving a Poisson equation on a square domain, which you should start investigating first.
Try for instance the following:

from fenics import *

try:
    import ufl as ufl
except ImportError:
    import ufl_legacy as ufl

# CHANGE PARAMETERS HERE
tol = 1e-3
L = 2.2
h = 0.41
T = 0.001
num_steps = 1
# CHANGE PARAMETERS HERE

dt = T / num_steps  # time step size
mu = 0.001
rho = 1.0

i, j, k = ufl.indices(3)

# create mesh
mesh = RectangleMesh(Point(0, 0), Point(L, h), 200, 50)

mf = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 0)

eps = 100 * DOLFIN_EPS


class left(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[0], 0, eps)


left().mark(mf, 2)


class right(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[0], L, eps)


right().mark(mf, 3)


class top(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[1], h, eps)


top().mark(mf, 4)


class bottom(SubDomain):
    def inside(self, x, on_boundary):
        return near(x[1], 0, eps)


bottom().mark(mf, 5)

Q = FunctionSpace(mesh, "P", 1)


# test for surface elements
ds_l = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=2)
ds_r = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=3)
ds_t = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=4)
ds_b = Measure("ds", domain=mesh, subdomain_data=mf, subdomain_id=5)


inflow_profile_v = Expression(
    ("4.0*1.5*x[1]*(h- x[1]) / pow(h, 2)", "0"),
    degree=2,
    h=h,
    domain=mesh,
)

bc_phi_outflow = DirichletBC(Q, Constant(0), mf, 3)
bc_phi = [bc_phi_outflow]

Deltat = Constant(dt)
phi = TrialFunction(Q)
q = TestFunction(Q)
a = (phi.dx(i) * q.dx(i)) * dx
L = -(rho / Deltat) * (inflow_profile_v[i].dx(i) * q) * dx

phih = Function(Q, name="phi")

solve(a == L, phih, bc_phi)
n = FacetNormal(mesh)
expr = dot(grad(phih), n)
print("BC = ", assemble(inner(expr, expr) * ds_l))


ff = File("phi.pvd")
ff << phih

which will yield

BC =  7.481641223894098e-24

which enforces the solution correctly.
You should now work on making this closer and closer to your example to see where you have made a mistake.
For instance, using an iterative solver (with no options specified), is rarely a good idea.

Because, like I said in the OP, the natural BCs are enforced only on \partial \Omega_\textrm{IN}, thus I need to extract ds_l and then only way I knew to do it is by generating the mesh externally, tagging the ds and reading it back.

Does the accuracy with which a BC is satisfied depend on the mesh size ?

I will, thank you.

Why ? What options should I specify? Please keep in mind that I need to implement this for a nonlinear variational problem, in my original PDE.

It is a weak enforcement, ie in an integral sense, which in turn depends on your pde.

Navier-Stokes is a non-linear PDE, but with a splitting scheme you split it into 3 linear PDEs.

As the second step, the pressure correction, is a Poisson problem, you should likely use a multigrid method (boomeramg).
You should also ensure that your system is assembled in a symmetric fashion (ie ensure that system assembler is called), so that you can use CG.

So, as I already asked, does the accuracy with which a BC is satisfied depend on the mesh size ?

May you please give me some references on how what a multigrid ‘boomerang’ method is and how to implement it in Fenics? (And yes, I looked up boomerang and multigrid combined with Fenics online, and the results are not intelligible).

How may I assemble in a symmetric way? I found this post but, like I said before, mine is a toy example for this forum, and the problem in which I will actually use this is a nonlinear one.

Yes, in some sense it does. If you have too few elements to resolve the physics you would like to adhere to, it might differ slightly.

However, as I showed with the built in mesh, for the Poisson problem at hand it should be possible to resolve the Neumann condition.

Searching for boomeramg in the dolfin repo yields:
https://bitbucket.org/search?q=repo%3Adolfin%20Boomeramg&account={795b0eed-436d-4242-83d7-a46a0b061354}
ie set the solver to hypre_amg to use boomeramg.
https://bitbucket.org/fenics-project/dolfin/src/1c52e837eb54cc34627f90bde254be4aa8a2ae17/python/dolfin/fem/solving.py?at=master#lines-117

You are making it quite hard to understand your problem. You say that the splitting scheme used for Navier Stokes is your toy problem. However, it is only for the second step of your splitting scheme you see an issue. Where is your non-linearity in this equation?

Regarding symmetry, it seems like solve(F==0) uses the symmetric system assembler, so at least that is good.

I’ve already shown you an implementation of the Poisson problem (where you use the divergence of a velocity field on the rhs of the eq) that satisfies the weak condition.

One question for your problem is

Why not just use FacetNormal(mesh)?