Periodicity in complex meshes not working

Hi,

I am currently facing some problems with complex meshes and periodic boundary conditions in fenics 2019.1: as long as I am doing 2D simulations, my implementation of periodic boundary conditions works fine - when I switch to 3D, I always find points on the boundary of my domains for which the periodicity mapping did not work.

One strange thing I noticed is that - during the checkup whether a point is inside the periodic subdomain or how the mapping works - the points that are passed to these methods are totally different from the ones that I can find by, e.g. checking the function spaces dof coordinates. The ones that are passed to the inside() and map() methods are really on the domain boundaries - if I check the dof coordinates, the number of coordinates on the boundary decreases. Does anybody know where that comes from? I am using simple Langrangian elements!

For the minimal code example below, I generated a gmsh mesh using this tool: https://github.com/NEFM-TUDresden/gmshModel. The meshes from the package work fine, if I am not using periodic BCs and I double-checked them to be periodic. The example I am using in the code is the hexagonal unit cell example from the repository. From what I have noticed, periodicity errors are only present for complex meshes - as here with inclusions on the boundary - and never in simple meshes.

To check for periodicity I am simply loading the mesh, generating a periodic domain for which the mapping seems to work fine and then I am creating a function on a FiniteElement function space (Lagrangian, first order). I have filled the vector of the function f with random numbers, before I applied the BCs explicitely. Afterwards, I am evaluating the values of the function f at the coordinates that I have logged during the mapping within the periodic subdomain. Comparing “master” and “slave” results with this method, leads to considerable errors that indicate that the periodicity mapping somehow is not working correctly, which is what I am experiencing in many of my simulations.

##############################################################################
# TEST FILE FOR THE IMPLEMENTATION OF PERIODIC BOUNDARY CONDITIONS IN FENICS #
##############################################################################

# load required libraries
#########################
from dolfin import *                                                            # dolfin for fenics fe capabilities
import numpy as np                                                              # numpy for fast array computations
import copy as cp
import matplotlib.pyplot as plt                                                 # matplotlib for figures

# Optimization options for the form compiler
############################################
parameters["form_compiler"]["cpp_optimize"] = True                              # optimize automatically generated cpp code
parameters["allow_extrapolation"] = True                                        # must-do: otherwise this example would not work


###############################
# Function to read XDMF files #
###############################
def readXDMF(fileName,comm=MPI.comm_world,boundaries=False,subdomains=False):

    # create empty mesh object
    mesh = Mesh()

    # read mesh
    with XDMFFile(comm, fileName+"_domain.xdmf") as infile:                     # temporarily open meshFile (assume that mesh is stores as "file"_domain.xdmf)
        infile.read(mesh)                                                       # read mesh

    # get mesh dimension
    dim=mesh.topology().dim()

    # read boundary data if necessary
    if boundaries:                                                              # boundary data is stored in mesh file
        boundariesMVC = MeshValueCollection("size_t",mesh,dim=dim)              # create mesh value collection
        with XDMFFile(comm,fileName+"_boundaries.xdmf") as infile:              # temporarily open boundary meshFile (assume that mesh is stored as "file"_boundaries.xdmf)
            infile.read(boundariesMVC,'boundaries')                             # read boundary mesh
        boundaryMarkers = cpp.mesh.MeshFunctionSizet(mesh,boundariesMVC)        # create MeshFunction for boundary data

    # read subdomain data if necessary
    if subdomains:
        subdomainsMVC = MeshValueCollection("size_t",mesh,dim=dim)              # create mesh value collection
        with XDMFFile(comm,fileName+"_domain.xdmf") as infile:                  # temporarily open boundary meshFile (assume that mesh is stored as "file"_domain.xdmf)
            infile.read(subdomainsMVC,'subdomains')                             # read boundary mesh
        subdomainMarkers = cpp.mesh.MeshFunctionSizet(mesh,subdomainsMVC)       # create MeshFunction for subdomain data

    return mesh, boundaryMarkers, subdomainMarkers

########################
# PeriodicDomain class #
########################
class PeriodicDomain(SubDomain):

    def __init__(self,mesh,boundingBox,periodicAxes=None,tolerance=DOLFIN_EPS):

        # calculate relevant information from bounding box array
        axesMin=np.array(boundingBox[0])
        axesMax=np.array(boundingBox[1])
        relevantAxes=np.flatnonzero(axesMax-axesMin)

        # initialize parent class
        super().__init__()

        # set relevant information for further calculations
        self.relevantAxes=relevantAxes                                          # relevant calculation axes
        self.periodicAxes=periodicAxes                                          # periodic axes
        self.axesMin=axesMin                                                    # minimum axes coordinates (lower, left, back point of RVE)
        self.axesMax=axesMax                                                    # maximum axes coordinates (upper, right, front point of RVE)
        self.tolerance=tolerance                                                # allowed tolerance
        self.mappedMaster=[]                                                    # list of mapped master coordinates (for debugging)
        self.mappedSlave=[]                                                     # list of mapped slave coordinates (for debugging)


    ##########################################################################
    # Overwrite inside method of SubDomain class to define master subdomains #
    ##########################################################################
    def inside(self,x,on_boundary):
        # check if point x belongs to AT LEAST ONE master but NO slave entities
        xInMasterEntity=False                                                   # initialze xInMasterEntity flag to be False by default
        if on_boundary:                                                         # check if x is a boundary coordinate
            for axis in self.periodicAxes:                                      # loop over all periodic axes
                if abs(x[axis]-self.axesMax[axis]) < self.tolerance:            # -> x is part of the current axis slave entity
                    xInMasterEntity=False                                       # ->-> set master entity membership information
                    break                                                       # ->-> stop further checks: x cannot be part of a master entity
                elif abs(x[axis]-self.axesMin[axis]) < self.tolerance:          # -> x is part of the current axis master entity
                    xInMasterEntity=True                                        # ->-> set master entity membership information

        return xInMasterEntity


    #################################################################################
    # Overwrite map method of SubDomain class to define the mapping master -> slave #
    #################################################################################
    def map(self,x,y):
        xInSlaveEntity=False                                                    # initialize xInSlaveEntity flag to be False by default
        for axis in self.relevantAxes:                                          # loop over all axes with nonzero extent
            if axis in self.periodicAxes:                                       # current axis is supposed to be periodic
                if abs(x[axis]-self.axesMax[axis]) < self.tolerance:            # -> point is on slave entity for the current axis
                    y[axis]=self.axesMin[axis]                                  # ->-> set coordinate to master nodes coordinate
                    xInSlaveEntity=True                                         # ->-> update slave entity membership flag
                else:                                                           # -> point is not on slave entity for the current axis
                    y[axis]=x[axis]                                             # ->-> leave coordinate unchanged
            else:                                                               # current axis is not supposed to be periodic
                y[axis]=x[axis]                                                 # -> leave coordinate unchanged

        # debugging information
        if xInSlaveEntity:
            self.mappedMaster.append(cp.deepcopy(y))                            # add y to list mapped master coordinates
            self.mappedSlave.append(cp.deepcopy(x))                             # add x to list of mapped slave coordinates


# relevant simulation parameters:
#################################
# domain information
meshFile = "HexagonalCell"                                                      # mesh file name
domainBB=[[0, 0, 0],[6, 6*3**(0.5), 6*3**(0.5)]]                                # domain bounding box (lower front left and upper back right points)

# domain information (mesh, function spaces, ...)
#################################################
# import mesh
mesh, boundaries, subdomains = readXDMF(meshFile, MPI.comm_world, boundaries=True, subdomains=True) #read mesh

# generate subdomains for periodicity constraints and fixed center point
periodicDomain=PeriodicDomain(mesh,domainBB,periodicAxes=[0,1,2],tolerance=1e-4)# periodicity constraints

# define element and function spaces
V = FiniteElement("CG", mesh.ufl_cell(), 1)
W = FunctionSpace(mesh,V,constrained_domain=periodicDomain)

# boundary conditions
BC=DirichletBC(W,Constant(1.5),periodicDomain)
f = Function(W)
f.vector()[:]=np.random.rand(f.vector().size())
BC.apply(f.vector())

# check error in periodicity
periodicityError = np.zeros(len(periodicDomain.mappedMaster))
for bndPt in range(0,len(periodicDomain.mappedMaster)):
    periodicityError[bndPt]=f(periodicDomain.mappedSlave[bndPt])-f(periodicDomain.mappedMaster[bndPt])

plt.plot(periodicityError)
plt.show()

Maybe someone of you has made similar observations or has a hint on what I might be doing wrong…

To make it a little easier:
The first thing that would help me while trying to find what is going wrong here is an answer to the question why the function spaces dof coordinates (W.tabulate_dof_coordinates) for Langrangian finite elements are not the same as the mesh coordinates - especially on the boundary where I find dofs that are supposed to be on the boundary (and they are, when I check mesh.coordinates()) but apparently are not.

Most likely the subdomain functions are evaluating at quadrature points, Which there are more of than dof coordinates.

Note that the ordering of the mesh coordinates are not the same as the degrees of freedom. To Get a mapping from degrees of freedom to vertices use dof_to_vertex_map.

Please Also reduce the size of your example by removing unused code paths, like the one below.[quote=“Flifff, post:1, topic:4530”]

if subdomains and boundaries:
        return mesh, boundaryMarkers, subdomainMarkers
    elif subdomains:
        return mesh, subdomainMarkers
    elif boundaries:
        return mesh, boundaryMarkers
    else:
        return mesh

[/quote]

Additionally, have you tested your implementation on a built in 3D mesh, such as the UnitCubeMesh?
Simplifying the problem will make it more likely to get replies.

Thanks for the fast reply - I will try to make the example a little easier so that more poeple are able to reproduce the behavior. I’ll post the updated code when I worked that out.

To answer your question: yes, I checked everything in 2D and 3D with simple meshes and it always works. Only when I try to do simulations with more involved meshes which have different subdomains on the boundaries, my tests indicate that the mapping is not correct on the whole boundary. This is what makes me wonder if there is a problem with different subdomains on the boundary.

The other point that I find less points on the boundary, if I am using f.tabulate_dof_coordinates() should - as far as I understand - have nothing to do with functions being evaluated at quadrature points since I am explicitely asking for the dof coordinates. But when I try to identify coordinates on the boundary with this array, I will always find less then I logged during the mapping procedure for the periodic subdomain, since points that are supposed to be on the boundary are not.

I try to make my point a little easier to understand by changing the example code - unfortunately, this still needs a more complicated mesh with subdomains on the boundary.

OK, I changed the example to make it a little more easy to follow and to allow for the use of external as well as internal meshes. Here is the updated code:

##############################################################################
# TEST FILE FOR THE IMPLEMENTATION OF PERIODIC BOUNDARY CONDITIONS IN FENICS #
##############################################################################

# load required libraries
#########################
from dolfin import *                                                            # dolfin for fenics fe capabilities
import numpy as np                                                              # numpy for fast array computations
import copy as cp                                                               # copy for deepcopies
import os                                                                       # file checks
from gmshModel.Model import HexagonalCell                                       # gmshModel for generation of hexagonal cell mesh
import meshio                                                                   # meshio for conversion to fenics readable xdmf mesh
import matplotlib.pyplot as plt                                                 # "normal" matplotlib plots
from mpl_toolkits.mplot3d import Axes3D                                         # 3D matplotlib plots

###################
# Mesh generation #
###################
internal=True
if internal:                                                                    # use internal box mesh
    mesh=UnitCubeMesh(5,5,5)
    domainBB=[[0,0,0],[1,1,1]]
else:                                                                           # use complex mesh
    if not os.path.isfile("HexagonalCell.xdmf"):                                # create if not existing
        # use gmshModel to creat msh-file
        hexCell=HexagonalCell(radius=2.5,inclusionType="Sphere",distance=6)
        hexCell.createGmshModel()
        hexCell.createMesh()
        hexCell.saveMesh("HexagonalCell.msh")
        # write fenics readable xdmf file using meshio
        mshFile=meshio.read("HexagonalCell.msh")
        data_array = [arr for (t, arr) in mshFile.cells if t == "tetra"]
        cells = [meshio.CellBlock(type="tetra",data=np.concatenate(data_array))]
        cell_data = {"subdomains": [np.concatenate([mshFile.cell_data["gmsh:physical"][i] for i, cellBlock in enumerate(mshFile.cells) if cellBlock.type == "tetra"])]}
        domain = meshio.Mesh(points=mshFile.points[:, :3],cells=cells,cell_data=cell_data)
        meshio.write("HexagonalCell.xdmf",domain,file_format="xdmf")

    mesh=Mesh()                                                                 # load existing mesh
    with XDMFFile("HexagonalCell.xdmf") as infile:
        infile.read(mesh)
        subdomainsMVC = MeshValueCollection("size_t",mesh,dim=3)
        infile.read(subdomainsMVC,'subdomains')
    subdomains = cpp.mesh.MeshFunctionSizet(mesh,subdomainsMVC)
    domainBB=[[0,0,0],[6,6*np.sqrt(3),6*np.sqrt(3)]]

########################
# PeriodicDomain class #
########################
class PeriodicDomain(SubDomain):

    def __init__(self,mesh,boundingBox,periodicAxes=None,tolerance=DOLFIN_EPS):

        # calculate relevant information from bounding box array
        axesMin=np.array(boundingBox[0])
        axesMax=np.array(boundingBox[1])
        relevantAxes=np.flatnonzero(axesMax-axesMin)
        dimension=len(relevantAxes)
        center=Point((axesMax[relevantAxes]+axesMin[relevantAxes])/2)

        # initialize parent class
        super().__init__()

        # set relevant information for further calculations
        self.relevantAxes=relevantAxes                                          # relevant calculation axes
        self.periodicAxes=periodicAxes                                          # periodic axes
        self.axesMin=axesMin                                                    # minimum axes coordinates (lower, left, back point of RVE)
        self.axesMax=axesMax                                                    # maximum axes coordinates (upper, right, front point of RVE)
        self.tolerance=tolerance                                                # allowed tolerance
        self.master=[]                                                          # list of master coordinates (for debugging)
        self.mappedMaster=[]                                                    # list of mapped master coordinates (for debugging)
        self.mappedSlave=[]                                                     # list of mapped slave coordinates (for debugging)

    # Overwrite inside method of SubDomain class to define master subdomains
    def inside(self,x,on_boundary):
        # check if point x belongs to AT LEAST ONE master but NO slave entities
        xInMasterEntity=False                                                   # initialze xInMasterEntity flag to be False by default
        if on_boundary:                                                         # check if x is a boundary coordinate
            for axis in self.periodicAxes:                                      # loop over all periodic axes
                if abs(x[axis]-self.axesMax[axis]) < self.tolerance:            # -> x is part of the current axis slave entity
                    xInMasterEntity=False                                       # ->-> set master entity membership information
                    break                                                       # ->-> stop further checks: x cannot be part of a master entity
                elif abs(x[axis]-self.axesMin[axis]) < self.tolerance:          # -> x is part of the current axis master entity
                    xInMasterEntity=True                                        # ->-> set master entity membership information

        if xInMasterEntity:
            self.master.append(cp.deepcopy(x))

        return xInMasterEntity

    # Overwrite map method of SubDomain class to define the mapping master -> slave
    def map(self,x,y):
        xInSlaveEntity=False                                                    # initialize xInSlaveEntity flag to be False by default
        for axis in self.relevantAxes:                                          # loop over all axes with nonzero extent
            if axis in self.periodicAxes:                                       # current axis is supposed to be periodic
                if abs(x[axis]-self.axesMax[axis]) < self.tolerance:            # -> point is on slave entity for the current axis
                    y[axis]=self.axesMin[axis]                                  # ->-> set coordinate to master nodes coordinate
                    xInSlaveEntity=True                                         # ->-> update slave entity membership flag
                else:                                                           # -> point is not on slave entity for the current axis
                    y[axis]=x[axis]                                             # ->-> leave coordinate unchanged
            else:                                                               # current axis is not supposed to be periodic
                y[axis]=x[axis]                                                 # -> leave coordinate unchanged

        # debugging information
        if xInSlaveEntity:
            self.mappedMaster.append(cp.deepcopy(y))                            # add y to list mapped master coordinates
            self.mappedSlave.append(cp.deepcopy(x))                             # add x to list of mapped slave coordinates


############
# Analysis #
############
# define periodic domain
periodicDomain=PeriodicDomain(mesh,domainBB,periodicAxes=[0,1,2],tolerance=1e-4)# periodicity constraints

# define element and function spaces
V = FiniteElement("CG", mesh.ufl_cell(), 2)
W = FunctionSpace(mesh,V,constrained_domain=periodicDomain)

# boundary conditions
BC=DirichletBC(W,Constant(1.5),periodicDomain)

# define function, fill it with random numbers and apply BCs to see what happens
f = Function(W)
f.vector()[:]=np.random.rand(f.vector().size())
BC.apply(f.vector())

# check error in periodicity
periodicityError = np.zeros(len(periodicDomain.mappedMaster))
for bndPt in range(0,len(periodicDomain.mappedMaster)):
    periodicityError[bndPt]=f(periodicDomain.mappedSlave[bndPt])-f(periodicDomain.mappedMaster[bndPt])


###########################
# Plots for visualization #
###########################
# show original mapping in plot
master=np.asarray(periodicDomain.master)                                        # coordinates identified by inside method
mappedMaster=np.asarray(periodicDomain.mappedMaster)                            # coordinates the slave coordinates are mapped to
mappedSlave=np.asarray(periodicDomain.mappedSlave)                              # slave coordinatesidentified by map method
fig1 = plt.figure()
ax = fig1.gca(projection='3d')
ax.plot(master[:,0],master[:,1],master[:,2],'xb')
ax.plot(mappedMaster[:,0],mappedMaster[:,1],mappedMaster[:,2],'or',markersize=9,markerfacecolor='none')
ax.plot(mappedSlave[:,0],mappedSlave[:,1],mappedSlave[:,2],'xg')

# show error in periodicity of master and slave coordinates
fig2 = plt.figure()
plt.plot(periodicityError)
plt.show()

What I can see when I am using a builtin mesh is the following:
The original mapping that is performed by the PeriodicDomain class works just fine. In the resulting plot you can see the coordinates that are identified as master coordinates in blue, the slave coordinates in green and their mapping onto the master coordinates in red. Each red circle is exactly where it is supposed to be - at one coordinate that was identified to be a master coordinate. Moreover, if I try to examine the error in the periodicity after the application of the BCs, I get an error around 10E-15 which is just fine from my understanding. See the plots below:

If I do the exact same thing with the more complex mesh generated by gmshModel, I can see the following:
The mapping of the points looks fine as well - I do not get the impression that there is something wrong with the mesh! It is periodic and the associated boundary coordinates are identified by the PeriodicDomain class without any errors. However, if I calculate the error in the periodicity of the function f after I applied the BCs, the result is completely different. Although all boundary nodes are supposed to have a value of exactly 1.5, there are errors up to 1.4 which is what I always observe when I am performing a “real” simulation with more complex meshes that are contrained to be periodic. In the pictures below, I just used linear Lagrange elements, since the plot of the mapping is a little packed for quadratic elements:

I will try to examine the solutions of the nodes instead of the function evaluation that is done at the moment. However, I can’t see why the application of periodic BCs leads to these huge errors.

I would suggest reducing the number of faces with periodic boundary conditions to just one (one face constrained to the other) and additionally reduce the number or cells. It would make it quite alot easier for both you and others to observe what goes wrong.

Ok, thanks for the advice. If I evaluated everything with the updated code below, I get the following for the internal mesh:

Still, everything looks nice for the mapping but what I do not understand is where the additional nodes that are identified as master nodes come from. The mesh is a simple UnitCubeMesh and there are no coordinates anywhere near these points.

If I have a look on the mapping for the complex mesh, I still see an error in the mapping - this time only at one point. However, the additional master nodes that were identified are also visible here and I don’t know where they come from:

Here the updated code with fewer cells and periodicity only in the x-direction:

##############################################################################
# TEST FILE FOR THE IMPLEMENTATION OF PERIODIC BOUNDARY CONDITIONS IN FENICS #
##############################################################################

# load required libraries
#########################
from dolfin import *                                                            # dolfin for fenics fe capabilities
import numpy as np                                                              # numpy for fast array computations
import copy as cp                                                               # copy for deepcopies
import os                                                                       # file checks
from gmshModel.Model import HexagonalCell                                       # gmshModel for generation of hexagonal cell mesh
import meshio                                                                   # meshio for conversion to fenics readable xdmf mesh
import matplotlib.pyplot as plt                                                 # "normal" matplotlib plots
from mpl_toolkits.mplot3d import Axes3D                                         # 3D matplotlib plots

###################
# Mesh generation #
###################
internal=False
if internal:                                                                    # use internal box mesh
    mesh=UnitCubeMesh(2,2,2)
    domainBB=[[0,0,0],[1,1,1]]
else:                                                                           # use complex mesh
    if not os.path.isfile("HexagonalCell.xdmf"):                                # create if not existing
        # use gmshModel to creat msh-file
        hexCell=HexagonalCell(radius=2.5,inclusionType="Sphere",distance=6)
        hexCell.createGmshModel()
        hexCell.createMesh(refinementOptions={"maxMeshSize":2,"elementsPerCircumference":12,"interInclusionRefinement":True})
        hexCell.visualizeMesh()
        hexCell.saveMesh("HexagonalCell.msh")
        # write fenics readable xdmf file using meshio
        mshFile=meshio.read("HexagonalCell.msh")
        data_array = [arr for (t, arr) in mshFile.cells if t == "tetra"]
        cells = [meshio.CellBlock(type="tetra",data=np.concatenate(data_array))]
        cell_data = {"subdomains": [np.concatenate([mshFile.cell_data["gmsh:physical"][i] for i, cellBlock in enumerate(mshFile.cells) if cellBlock.type == "tetra"])]}
        domain = meshio.Mesh(points=mshFile.points[:, :3],cells=cells,cell_data=cell_data)
        meshio.write("HexagonalCell.xdmf",domain,file_format="xdmf")

    mesh=Mesh()                                                                 # load existing mesh
    with XDMFFile("HexagonalCell.xdmf") as infile:
        infile.read(mesh)
        subdomainsMVC = MeshValueCollection("size_t",mesh,dim=3)
        infile.read(subdomainsMVC,'subdomains')
    subdomains = cpp.mesh.MeshFunctionSizet(mesh,subdomainsMVC)
    domainBB=[[0,0,0],[6,6*np.sqrt(3),6*np.sqrt(3)]]

########################
# PeriodicDomain class #
########################
class PeriodicDomain(SubDomain):

    def __init__(self,mesh,boundingBox,periodicAxes=None,tolerance=DOLFIN_EPS):

        # calculate relevant information from bounding box array
        axesMin=np.array(boundingBox[0])
        axesMax=np.array(boundingBox[1])
        relevantAxes=np.flatnonzero(axesMax-axesMin)
        dimension=len(relevantAxes)
        center=Point((axesMax[relevantAxes]+axesMin[relevantAxes])/2)

        # initialize parent class
        super().__init__()

        # set relevant information for further calculations
        self.relevantAxes=relevantAxes                                          # relevant calculation axes
        self.periodicAxes=periodicAxes                                          # periodic axes
        self.axesMin=axesMin                                                    # minimum axes coordinates (lower, left, back point of RVE)
        self.axesMax=axesMax                                                    # maximum axes coordinates (upper, right, front point of RVE)
        self.tolerance=tolerance                                                # allowed tolerance
        self.master=[]                                                          # list of master coordinates (for debugging)
        self.mappedMaster=[]                                                    # list of mapped master coordinates (for debugging)
        self.mappedSlave=[]                                                     # list of mapped slave coordinates (for debugging)

    # Overwrite inside method of SubDomain class to define master subdomains
    def inside(self,x,on_boundary):
        # check if point x belongs to AT LEAST ONE master but NO slave entities
        xInMasterEntity=False                                                   # initialze xInMasterEntity flag to be False by default
        if on_boundary:                                                         # check if x is a boundary coordinate
            for axis in self.periodicAxes:                                      # loop over all periodic axes
                if abs(x[axis]-self.axesMax[axis]) < self.tolerance:            # -> x is part of the current axis slave entity
                    xInMasterEntity=False                                       # ->-> set master entity membership information
                    break                                                       # ->-> stop further checks: x cannot be part of a master entity
                elif abs(x[axis]-self.axesMin[axis]) < self.tolerance:          # -> x is part of the current axis master entity
                    xInMasterEntity=True                                        # ->-> set master entity membership information

        if xInMasterEntity:
            self.master.append(cp.deepcopy(x))
            print(x)

        return xInMasterEntity

    # Overwrite map method of SubDomain class to define the mapping master -> slave
    def map(self,x,y):
        xInSlaveEntity=False                                                    # initialize xInSlaveEntity flag to be False by default
        for axis in self.relevantAxes:                                          # loop over all axes with nonzero extent
            if axis in self.periodicAxes:                                       # current axis is supposed to be periodic
                if abs(x[axis]-self.axesMax[axis]) < self.tolerance:            # -> point is on slave entity for the current axis
                    y[axis]=self.axesMin[axis]                                  # ->-> set coordinate to master nodes coordinate
                    xInSlaveEntity=True                                         # ->-> update slave entity membership flag
                else:                                                           # -> point is not on slave entity for the current axis
                    y[axis]=x[axis]                                             # ->-> leave coordinate unchanged
            else:                                                               # current axis is not supposed to be periodic
                y[axis]=x[axis]                                                 # -> leave coordinate unchanged

        # debugging information
        if xInSlaveEntity:
            self.mappedMaster.append(cp.deepcopy(y))                            # add y to list mapped master coordinates
            self.mappedSlave.append(cp.deepcopy(x))                             # add x to list of mapped slave coordinates


############
# Analysis #
############
# define periodic domain
periodicDomain=PeriodicDomain(mesh,domainBB,periodicAxes=[0],tolerance=1e-8)# periodicity constraints

# define element and function spaces
V = FiniteElement("CG", mesh.ufl_cell(), 1)
W = FunctionSpace(mesh,V,constrained_domain=periodicDomain)

# boundary conditions
BC=DirichletBC(W,Constant(1.5),periodicDomain)

# define function, fill it with random numbers and apply BCs to see what happens
f = Function(W)
f.vector()[:]=np.random.rand(f.vector().size())
BC.apply(f.vector())

# check error in periodicity
periodicityError = np.zeros(len(periodicDomain.mappedMaster))
for bndPt in range(0,len(periodicDomain.mappedMaster)):
    periodicityError[bndPt]=f(periodicDomain.mappedSlave[bndPt])-f(periodicDomain.mappedMaster[bndPt])


###########################
# Plots for visualization #
###########################
# show original mapping in plot
master=np.asarray(periodicDomain.master)                                        # coordinates identified by inside method
mappedMaster=np.asarray(periodicDomain.mappedMaster)                            # coordinates the slave coordinates are mapped to
mappedSlave=np.asarray(periodicDomain.mappedSlave)                              # slave coordinatesidentified by map method
fig1 = plt.figure()
ax = fig1.gca(projection='3d')
ax.plot(master[:,0],master[:,1],master[:,2],'xb')
ax.plot(mappedMaster[:,0],mappedMaster[:,1],mappedMaster[:,2],'or',markersize=9,markerfacecolor='none')
ax.plot(mappedSlave[:,0],mappedSlave[:,1],mappedSlave[:,2],'xg')

# show error in periodicity of master and slave coordinates
fig2 = plt.figure()
plt.plot(periodicityError)

plt.show()

Ok, I think I solved it myself and the mistake was a pretty stupid one: in my PeriodicDomain class I just forgot to pass the tolerance to the parent SubDomain object. This lead to a mapping tolerance which was way too low and therefore some of the points on the boundary - which were identical up to 1e-6 - were not associated. Passing the tolerance to the SubDomain class did the trick.

The arbitrary points that I mentioned came just from my logging within the map and inside methods: since I do not know for which purposes these methods are called, I cannot assume that only the boundary dofs are stored, when I activate the logging every time a point is identified to be succesfully mapped.