Consider again the variable-coefficient Poisson problem from the section Defining subdomains for different materials. We will now discuss how to implement general combinations of boundary conditions of Dirichlet, Neumann, and Robin type for this model problem.

We extend our repertoire of boundary conditions to three types:
Dirichlet, Neumann, and Robin. Dirichlet conditions apply to some
parts \( \GD^0, \GD^1, \ldots \), of the boundary:
$$ u = \ub^0\hbox{ on }\GD^0,\quad
u = \ub^1\hbox{ on }\GD^1, \quad \ldots,$$
where \( \ub^i \) are prescribed functions, \( i=0,1,\ldots \).
On other parts, \( \GN^0, \GN^1, \ldots \), we have
Neumann conditions:
$$ -\kappa{\partial u\over\partial n} = g_{0}\hbox{ on }\GN^0,\quad
-\kappa{\partial u\over\partial n} = g_{1}\hbox{ on }\GN^1,\quad \ldots,
$$
Finally, we have *Robin conditions*:
$$
\begin{equation*}
-\kappa{\partial u\over\partial n} = r(u-s),
\tag{4.5}
\end{equation*}
$$
where \( r \) and \( s \) are specified functions. The Robin condition is
most often used to model heat transfer to the surroundings and arise
naturally from Newton's cooling law. In that case, \( r \) is a heat
transfer coefficient, and \( s \) is the temperature of the
surroundings. Both can be space and time-dependent.
The Robin conditions apply
at some parts \( \GR^0, \GR^1, \ldots \), of the boundary:
$$ -\kappa{\partial u\over\partial n} = r_0(u-s_0)\hbox{ on }\GR^0,\quad
-\kappa{\partial u\over\partial n} = r_1(u-s_1)\hbox{ on }\GR^1,\quad \ldots
$$

With the notation above, the model problem to be solved with multiple Dirichlet, Neumann, and Robin conditions can be formulated as follows: $$ \begin{alignat}{2} -\nabla\cdot(\kappa\nabla u) &= f \quad&&\mbox{in } \Omega, \tag{4.6}\\ u &= \ub^i &&\mbox{on } \GD^i,\quad i=0,1,\ldots \tag{4.7}\\ -\kappa{\partial u\over\partial n} &= g_i &&\mbox{on } \GN^i,\quad i=0,1,\ldots \tag{4.8}\\ -\kappa{\partial u\over\partial n} &= r_i(u-s_i) \quad&&\mbox{on } \GR^i,\quad i=0,1,\ldots \tag{4.9} \end{alignat} $$

As usual, we multiply by a test function \( v \) and integrate by parts: $$ \begin{equation*} -\int_\Omega \nabla\cdot(\kappa\nabla u) v \dx = \int_\Omega \kappa\nabla u\cdot \nabla v \dx - \int_{\partial\Omega}\kappa\frac{\partial u}{\partial n}v \ds\tp \end{equation*} $$ On the Dirichlet part of the boundary (\( \GD^i \)), the boundary integral vanishes since \( v = 0 \). On the remaining part of the boundary, we split the boundary integral into contributions from the Neumann parts (\( \GN^i \)) and Robin parts (\( \GR^i \)). Inserting the boundary conditions, we obtain $$ \begin{align*} -\int_{\partial\Omega} \kappa\frac{\partial u}{\partial n}v \ds &= -\sum_i\int_{\GN^i} \kappa\frac{\partial u}{\partial n} \ds -\sum_i\int_{\GR^i} \kappa\frac{\partial u}{\partial n} \ds\\ &= \sum_i\int_{\GN^i}g_i \ds + \sum_i\int_{\GR^i}r_i(u-s_i) \ds\tp \end{align*} $$ We thus obtain the following variational problem: $$ \begin{equation} F = \int_{\Omega} \kappa\nabla u\cdot \nabla v \dx + \sum_i\int_{\GN^i} g_iv \ds + \sum_i\int_{\GR^i}r_i(u-s_i)v \ds - \int_{\Omega} fv \dx =0\tp \tag{4.10} \end{equation} $$

We have been used to writing this variational formulation in the
standard notation \( a(u,v)=L(v) \), which requires that we identify all
integral depending on the trial function \( u \), and collect these in
\( a(u,v) \), while the remaining integrals go into \( L(v) \). The integrals
from the Robin condition must for this reason be split into two parts:
$$
\begin{equation*}
\int_{\GR^i}r_i(u-s_i)v \ds
= \int_{\GR^i} r_iuv \ds - \int_{\GR^i}r_is_iv \ds\tp
\end{equation*}
$$
We then have
$$
\begin{align}
a(u, v) &= \int_{\Omega} \kappa\nabla u\cdot \nabla v \dx
+ \sum_i\int_{\GR^i}r_iuv \ds,
\tag{4.11}\\
L(v) &= \int_{\Omega} fv \dx -
\sum_i\int_{\GN^i} g_i v \ds + \sum_i\int_{\GR^i}r_is_iv \ds\tp
\tag{4.12}
\end{align}
$$
Alternatively, we may keep the formulation
(4.10) and either solve the variational
problem as a nonlinear problem (`F == 0`

) in FEniCS or use the FEniCS
functions `lhs`

and `rhs`

to extract the bilinear and linear parts of
`F`

:

```
a = lhs(F)
L = rhs(F)
```

Note that if we choose to solve this linear problem as a nonlinear problem, the Newton iteration will converge in a single iteration.

Let us examine how to extend our Poisson solver to handle general combinations of Dirichlet, Neumann, and Robin boundary conditions. Compared to our previous code, we must consider the following extensions:

- Defining markers for the different parts of the boundary.
- Splitting the boundary integral into parts using the markers.

`MeshFunction`

, but contrary to the section Defining subdomains for different materials, this is not a function over cells, but
a function over the facets of the mesh. We use a `FacetFunction`

for
this purpose:

```
boundary_markers = FacetFunction('size_t', mesh)
```

As in the section Defining subdomains for different materials we use a subclass of
`SubDomain`

to identify the various parts of the mesh
function. Problems with domains of more complicated geometries may set
the mesh function for marking boundaries as part of the mesh
generation. In our case, the boundary \( x = 0 \) can be marked
as follows:

```
class BoundaryX0(SubDomain):
tol = 1E-14
def inside(self, x, on_boundary):
return on_boundary and near(x[0], 0, tol)
bx0 = BoundaryX0()
bx0.mark(boundary_markers, 0)
```

Similarly, we create the classes `BoundaryX1`

(\( x=1 \)), `BoundaryY0`

(\( y=0 \)), and `BoundaryY1`

(\( y=1 \)) boundary, and mark these as
subdomains 1, 2, and 3, respectively.

For generality of the implementation, we let the user specify what kind of boundary condition that applies to each of the four boundaries. We set up a Python dictionary for this purpose, with the key as subdomain number and the value as a dictionary specifying the kind of condition as key and a function as its value. For example,

```
boundary_conditions = {0: {'Dirichlet': u_D},
1: {'Robin': (r, s)},
2: {'Neumann': g},
3: {'Neumann', 0}}
```

specifies

- a Dirichlet condition \( u = \ub \) for \( x = 0 \);
- a Robin condition \( -\kappa\partial_n u = r(u-s) \) for \( x = 1 \);
- a Neumann condition \( -\kappa\partial_n u = g \) for \( y = 0 \);
- a Neumann condition \( -\kappa\partial_n u = 0 \) for \( y = 1 \).

`DirichletBC`

objects. Based on the `boundary_conditions`

data structure above, we
can construct this list by the following code snippet:

```
bcs = []
for i in boundary_conditions:
if 'Dirichlet' in boundary_conditions[i]:
bc = DirichletBC(V, boundary_conditions[i]['Dirichlet'],
boundary_markers, i)
bcs.append(bc)
```

A new aspect of the variational problem is the two distinct
boundary integrals over \( \GN^i \) and \( \GR^i \).
Having a mesh function over exterior cell facets (our
`boundary_markers`

object), where subdomains (boundary parts) are
numbered as \( 0,1,2,\ldots \), the special symbol `ds(0)`

implies integration over subdomain (part) 0, `ds(1)`

denotes
integration over subdomain (part) 1, and so on.
The idea of multiple `ds`

-type objects generalizes to volume
integrals too: `dx(0)`

, `dx(1)`

, etc., are used to
integrate over subdomain 0, 1, etc., inside \( \Omega \).

To express integrals over the boundary parts using `ds(i)`

, we must
first redefine the measure `ds`

in terms of our boundary markers:

```
ds = Measure('ds', domain=mesh, subdomain_data=boundary_markers)
```

Similarly, if we want integration over different parts of the domain,
we redefine `dx`

as

```
dx = Measure('dx', domain=mesh, subdomain_data=domain_markers)
```

where `domain_markers`

is a `CellFunction`

defining subdomains in \( \Omega \).

Suppose we have a Robin condition with values `r`

and `s`

on subdomain
`R`

, and a Neumann condition with value `g`

on subdomain `N`

. The
variational form can then be written

```
a = kappa*dot(grad(u), grad(v))*dx + r*u*v*ds(R)
L = f*v*dx - g*v*ds(N) + r*s*v*ds(R)
```

In our case, things get a bit more complicated since the
information about integrals in Neumann and Robin conditions
are in the `boundary_conditions`

data structure. We can collect
all Neumann conditions by the following code snippet:

```
integrals_N = []
for i in boundary_conditions:
if 'Neumann' in boundary_conditions[i]:
if boundary_conditions[i]['Neumann'] != 0:
g = boundary_conditions[i]['Neumann']
integrals_N.append(g*v*ds(i))
```

Applying `sum(integrals_N)`

will apply the `+`

operator to
the variational forms in the `integrals_N`

list and result
in the integrals we need for the right-hand side `L`

of the
variational form.

The integrals in the Robin condition can similarly be collected in lists:

```
integrals_R_a = []
integrals_R_L = []
for i in boundary_conditions:
if 'Robin' in boundary_conditions[i]:
r, s = boundary_conditions[i]['Robin']
integrals_R_a.append(r*u*v*ds(i))
integrals_R_L.append(r*s*v*ds(i))
```

We are now in a position to define the `a`

and `L`

expressions
in the variational formulation:

```
a = kappa*dot(grad(u), grad(v))*dx + sum(integrals_R_a)
L = f*v*dx - sum(integrals_N) + sum(integrals_R_L)
```

Alternatively, we may use the FEniCS functions `lhs`

and `rhs`

as
mentioned above to simplify the extraction of terms for the Robin
integrals:

```
integrals_R = []
for i in boundary_conditions:
if 'Robin' in boundary_conditions[i]:
r, s = boundary_conditions[i]['Robin']
integrals_R.append(r*(u - s)*v*ds(i))
F = kappa*dot(grad(u), grad(v))*dx + \
sum(integrals_R) - f*v*dx + sum(integrals_N)
a, L = lhs(F), rhs(F)
```

This time we can more naturally define the integrals from the
Robin condition as `r*(u - s)*v*ds(i)`

.

The complete code can be found in the function
`solver_bcs`

in the program
`ft10_poisson_extended.py`.

We will use the same exact solution \( \uex=1+x^2+2y^2 \) as in the chapter Fundamentals: Solving the Poisson equation, and thus take \( \kappa=1 \) and \( f=-6 \). Our domain is the unit square, and we assign Dirichlet conditions at \( x=0 \) and \( x=1 \), a Robin condition at \( y=0 \), and a Neumann condition at \( y=1 \). With the given exact solution \( \uex \), we realize that the Neumann condition at \( y=1 \) is \( -\partial u / \partial n = - \partial u / \partial y = 4y = 4 \), while the Robin condition at \( y=0 \) can be selected in many ways. Since \( -\partial u/\partial n=\partial u/\partial y=0 \) at \( y=0 \), we can select \( s=\uex \) and specify \( r \neq 0 \) arbitrarily in the Robin condition. We will set \( r = 1000 \) and \( s = \uex \).

The boundary parts are thus \( \GD^0 \): \( x=0 \), \( \GD^1 \): \( x=1 \), \( \GR^0 \): \( y=0 \), and \( \GN^0 \): \( y=1 \).

When implementing this test problem, and especially other test
problems with more complicated expressions, it is advantageous to use
symbolic computing. Below we define the exact solution as a `sympy`

expression and derive other functions from their mathematical
definitions. Then we turn these expressions into C/C++ code, which
can then be used to define `Expression`

objects.

```
# Define manufactured solution in sympy and derive f, g, etc.
import sympy as sym
x, y = sym.symbols('x[0], x[1]') # needed by UFL
u = 1 + x**2 + 2*y**2 # exact solution
u_e = u # exact solution
u_00 = u.subs(x, 0) # restrict to x = 0
u_01 = u.subs(x, 1) # restrict to x = 1
f = -sym.diff(u, x, 2) - sym.diff(u, y, 2) # -Laplace(u)
f = sym.simplify(f) # simplify f
g = -sym.diff(u, y).subs(y, 1) # compute g = -du/dn
r = 1000 # Robin data, arbitrary
s = u # Robin data, u = s
# Collect variables
variables = [u_e, u_00, u_01, f, g, r, s]
# Turn into C/C++ code strings
variables = [sym.printing.ccode(var) for var in variables]
# Turn into FEniCS Expressions
variables = [Expression(var, degree=2) for var in variables]
# Extract variables
u_e, u_00, u_01, f, g, r, s = variables
# Define boundary conditions
boundary_conditions = {0: {'Dirichlet': u_00}, # x = 0
1: {'Dirichlet': u_01}, # x = 1
2: {'Robin': (r, s)}, # y = 0
3: {'Neumann': g}} # y = 1
```

The complete code can be found in the function
`demo_bcs`

in the program
`ft10_poisson_extended.py`.

It is easy to make mistakes when implementing a problem with many
different types of boundary conditions, as in the present case. One
method to debug boundary conditions is to run through all vertex
coordinates and check if the `SubDomain.inside`

method marks the
vertex as on the boundary. Another useful method is to list which
degrees of freedom that are subject to Dirichlet conditions, and for
first-order Lagrange (\( \mathsf{P}_1 \)) elements, print the
corresponding vertex coordinates as illustrated by the following
code snippet:

```
if debug1:
# Print all vertices that belong to the boundary parts
for x in mesh.coordinates():
if bx0.inside(x, True): print('%s is on x = 0' % x)
if bx1.inside(x, True): print('%s is on x = 1' % x)
if by0.inside(x, True): print('%s is on y = 0' % x)
if by1.inside(x, True): print('%s is on y = 1' % x)
# Print the Dirichlet conditions
print('Number of Dirichlet conditions:', len(bcs))
if V.ufl_element().degree() == 1: # P1 elements
d2v = dof_to_vertex_map(V)
coor = mesh.coordinates()
for i, bc in enumerate(bcs):
print('Dirichlet condition %d' % i)
boundary_values = bc.get_boundary_values()
for dof in boundary_values:
print(' dof %2d: u = %g' % (dof, boundary_values[dof]))
if V.ufl_element().degree() == 1:
print(' at point %s' %
(str(tuple(coor[d2v[dof]].tolist()))))
```

`inside`

method.`inside`

method. Then it will be surprising to see that this method is
called not only for the points assoicated with degrees of freedom.
For \( \mathsf{P}_1 \) elements the method is also called for each
midpoint on each facet of the cells. This is because a Dirichlet
condition is by default set only if the entire facet can be said to be
subject to the condition defining the boundary.