Drift-Diffusion: Neumann BC with du/dx instead of du/dn

Hello all,

i want to impose a Neumann boundary condition to a drift–diffusion-problem that simply lets a flux pass through the boundaries. I do this by adding a boundary integral (ds) over the flux term to the variational form. To test this approach, I have implemented a simple example where a concentration peak moves through the cell with a constant drift velocity \mathbf{b} and, eventually, leaves the cell.

So the drift–diffusion equation looks like this:

\frac{\partial u}{\partial t} = -\nabla\mathbf{j}\\ \mathbf{j} = -D\left(\nabla u - \mathbf{b}\cdot u\right)

And, initially, I thought that the Neumann boundary condition can be set in a simple way, like this:
\int\limits_\Omega\left(u(t_{n+1})-u(t_n)\right)\cdot v\,\mathrm{d}x -\Delta t\cdot\int\limits_\Omega\mathbf{j}\cdot\nabla v\,\mathrm{d}x+\Delta t\cdot\int\limits_{\partial\Omega}\mathbf{j}\,v\,\mathrm{d}\mathbf{s} = 0

Minimal code example:

import numpy as np
import matplotlib.pyplot as plt
from dolfin import *

mesh = IntervalMesh(1000, -5, 5)
V = FunctionSpace(mesh, "CG", 1)
u, u_copy = Function(V), Function(V)
v = TestFunction(V)

# Drift-Diffusion equation
D, dt, drift = Constant(1.), Constant(.01), Constant(+10.)
j = -D*(u.dx(0)-u*drift)
F = (u-u_copy)*v*dx - dt*j*v.dx(0)*dx + dt*j*v*ds

# initial concentration profile
u0 = Expression('exp(-(x[0]-m)*(x[0]-m)/(2*l*l))', m=0., l=.1, degree=1)
u.interpolate(u0)

# solve time propagation
for i in range(100):
    u_copy.vector()[:] = u.vector()[:]
    solve(F==Constant(0.), u, [])
    plot(u)
plt.show()

For b=+10, this appears to work, but, if I switch the sign to b=-10, the outcome of the simulation is completely different:

Basically, the Neumann boundary condition does what I want it to do on the right boundary, but not on the left one. I think this is because I actually want to prescribe gradients of the form \mathrm{d}/\mathrm{d}x, rather than \mathrm{d}/\mathrm{d}\mathbf{n} (with \mathbf{n} being the normal vector of the outer boundary).

For the 1-dimensional case, I could fix this by having separate terms (-dt*j*ds(1) for the right boundary, +dt*j*ds(2) for the left boundary) for the two boundaries. I am wondering, though, if there is a simpler and more general solution to this that would also work for more complex geometries in higher dimensions.

Why doesn’t n = ufl.FacetNormal(mesh) accommodate what you need? In 1D it will be n = -1 on the left and n = +1 on the right as you require.

1 Like

There seems to be a problem with the dimensions in the variational expression then. I tried formulating the boundary term as + dt*n*j*v*ds, with n = FacetNormal(mesh)

But then, I get this error:
ufl.log.UFLException: Can only integrate scalar expressions. The integrand is a tensor expression with value shape (1,) and free indices with labels ().

Looks like a generalisation of the tensor formulated nature of the facet normal. You could just try n[0]. In 1D it will always have shape (1,).

1 Like

Nice, thanks a lot! In case of higher dimensions, would I have to do the same?

I’ve only glanced at your formulation. The flux is typically written:

\frac{\partial u}{\partial \vec{n}} = \nabla u \cdot \vec{n}.

You can formulate these terms as necessary in UFL, it all depends on the weak formulation and boundary conditions you’re attempting to discretise.

1 Like

Alright, thanks. Guess I’ll try it out in 2D to see if something like inner(j, n) works.