This is a read only copy of the old FEniCS QA forum. Please visit the new QA forum to ask questions

periodic boundary conditions using nullspace

+2 votes

I am trying to implement periodic boundaries on curved boundaries. As a test I modified the periodic Poisson example implementing the constraints from the periodic boundaries as null-space. The problem: I obtain nearly the same solution as in the example but the values u(0,y)=u(1,y) at the boundaries differ slightly from the ones obtained in the demo! They should be exactly the same! I have verified by using another FEM software that the solution of the demo is correct (unless that software has the same problem ;-)

I have checked the null-space. As far as I can see it is correct. So what is the problem?

from dolfin import *
import numpy as N

parameters['allow_extrapolation'] = True

class Constraint:
    """
    Constraint implements a tie between the values at two points p1 and p2.

    Example: 

    Create a tie between the values at (0.0, 0.5) and (1.0, 0.5)

    mesh = UnitSquareMesh(8, 8)
    V = FunctionSpace(mesh, 'CG', 2)
    constraint = Constraint(V)
    tie = constraint.vector(Point(0.0, 0.5), Point(1.0, 0.5))

    The constraint equation is given by tie.inner(u.vector()) == 0,
    i.e. all ties span the nullspace of the linear equation.
    """
    def __init__(self, V):
        self.V = V
        self.mesh = V.mesh()
        self.dofmap = V.dofmap()
        self.finite_element = V.element()
    def evaluate_basis(self, p):
        bbt = mesh.bounding_box_tree()
        id = bbt.compute_first_entity_collision(p)
        if id >= mesh.num_cells():
            id = bbt.compute_closest_entity(p)[0]
        c = Cell(self.mesh, id)
        vc = c.get_vertex_coordinates()
        dofs = self.dofmap.cell_dofs(id)
        no_basis_fns = self.finite_element.space_dimension()
        value_dimension = self.finite_element.value_dimension(0)
        basis = N.zeros((no_basis_fns, value_dimension), dtype=N.float64)
        coords = N.zeros(3, dtype=N.float64)
        coords[0], coords[1], coords[2] = p.x(), p.y(), p.z()
        self.finite_element.evaluate_basis_all(basis, coords, vc, 0)
        u = Function(self.V)
        v = u.vector()
        # fixme: implement mixed spaces
        for k in range(value_dimension):
            for j in range(no_basis_fns):
                l = no_basis_fns*(k-1)+j
                v[dofs[l]] = basis[j][k]
        return v
    def vector(self, p1, p2):
        v = (self.evaluate_basis(p1)-self.evaluate_basis(p2))
        v /= v.norm('l2')
        return v

class PeriodicBoundaryCondition(SubDomain):
    def __init__(self):
        SubDomain.__init__(self)    
        pass
    def inside(self, x, on_boundary):
        return near(x[0], 0.0) and on_boundary
    def map(self, x, y):
        y[0] = x[0]-1.0
        y[1] = x[1]
    def nullspace(self, V):
        r = Constraint(V)
        dofmap = V.dofmap()
        coords = dofmap.tabulate_all_coordinates(V.mesh())
        s = []
        x = [0, 0]
        for x[0], x[1] in zip(coords[0::2], coords[1::2]):
            y = [0, 0]
            self.map(x, y)
            if self.inside(y, True):
                A = Point(x[0], x[1])
                B = Point(y[0], y[1])
                s.append(r.vector(A, B))
        return VectorSpaceBasis(s)

class DirichletBoundary(SubDomain):
    def inside(self, x, on_boundary):
        return bool((near(x[1], 0.0) or near(x[1], 1.0)) and on_boundary)

class Source(Expression):
    def eval(self, values, x):
        dx = x[0] - 0.5
        dy = x[1] - 0.5
        values[0] = x[0]*sin(5.0*pi*x[1]) + 1.0*exp(-(dx*dx + dy*dy)/0.02)

mesh = UnitSquareMesh(32, 32)
mesh.init()
V = FunctionSpace(mesh, 'CG', 1)

pbc = PeriodicBoundaryCondition()
nullspace = pbc.nullspace(V)

f = Source()
u, v = TrialFunction(V), TestFunction(V)
a = inner(grad(u), grad(v))*dx
L = f*v*dx

# Create Dirichlet boundary condition
zero = Constant(0.0)
db = DirichletBoundary()
dbc = DirichletBC(V, zero, db)

# Compute solution
u = Function(V)
U = u.vector()

A, b = assemble_system(a, L, [dbc])

prm = parameters['krylov_solver']
prm['relative_tolerance'] = 1.0e-16
prm['absolute_tolerance'] = 1.0e-16

nullspace.orthogonalize(b)
solver = KrylovSolver()
solver.set_nullspace(nullspace)
solver.solve(A, U, b)

print "dimension of nullspace", nullspace.dim()

# Save solution to file
file = File("periodic.pvd")
file << u

# Plot solution
plot(u, interactive=True)

The following plot shows the discrepancy:

comparison of u(0, y) and u(1, y) using the two different methods

asked Feb 14, 2014 by monien FEniCS Novice (790 points)
edited Feb 14, 2014 by monien

I don't follow. A does no have a single null space as far as I can tell?

Start with the equation resulting assembling the PDE including the Dirichlet boundaries, say
$$A u = b$$.
The periodic boundary conditions can be written in the form
$$ B^T u = 0 $$,
where $B$ is an (NxM) matrix where M is the number of constrained dofs and N the number of global dofs. B can be orthonormalized (it just defines a M-dimensional subspace of the N-dimensional vector-space). Using Lagrange multiplier for the constraint the equation we have to solve the equations
$$A u + B \lambda = b$$
and
$$B^T u = 0$$.
Using the fact that
$$B^T B=1$$
in the M-dimensional subspace of the constraint we can eliminate $\lambda$ and obtain
$$(1-B^TB)A u=(1-B^TB)b$$
This is the equation which is solved by the Krylov solver with the null-space defined $B$. The rank of $(1-B^TB)A$ is $N-M$ the kernel has dimension $M$.

The current implementation of periodic boundary conditions is not flexible enough to deal with curved boundaries easily. The implementation above using Krylov solvers has a problem. My experience with Krylov solvers is that they loose orthogonality after a few iterations (approximately 100 iterations with double) and that might be responsible for the discrepancy. The alternative is to set up a block matrix system (with the above notation):
$$
\left(\begin{array}{cc}A & B \\ B^{T} & 0\end{array}\right)
\left(\begin{array}{c}u\\ \lambda\end{array}\right)=
\left(\begin{array}{c}b\\0\end{array}\right)
$$
and then use a direct solver. Now the only issue is to convert the VectorSpaceBasis to a matrix.

...