I am trying to debug some boundary conditions in a complex problem. I have
run into a problem with faceGradAverage or my understanding of it. It is
illustrated in a simple problem

     \Del^2 Psi =0 on a unit square
     normal derivative = 1 on x=0
     normal derivative = 0 on y=0
     normal derivative = 0 on y=1
     Psi =1 on x=1

The analytical solution to this problem is

     Psi = x

The code is in the attached file; skip until "if __name__ == "__main__":" ,
the earlier code converts a Delaunay triangulation to a fipy mesh. I am
doing this to match my more complex case.

Anyway, when the code is run, I get the analytical solution, and so
gradient \Del\Psi should be (1,0) everywhere. It is not. Note also that
since \Del\Psi is spatially uniform, how the gradient is averaged in space
should not make a difference.

How am I confused?

So that you don't have to open the attachment, the code that solves the
problem and prints the face gradients is included as text here:

    #setup fipy solution for Del^2 Psi = 1 with BC of 0 everywhere
    #first, make grid variable
    psi=fipy.CellVariable(name='psi',mesh=mesh,value=0.0)

    #apply boundary of Del\Psi \dot \nhat=-1 on x=0 boundary (equivalent to
d Psi/dx=1)
    #                  Del\Psi \dot \nhat=0 on y=0,1 boundary
    #and Psi=1 on x=1

    xFace,yFace=mesh.faceCenters
    faceNorm=mesh.faceNormals

    #y=0 boundary
    yeq0bound=yFace==0.0
    psi.faceGrad.constrain(0.0*faceNorm,where=yeq0bound)

    #y=1.0 boundary
    yeq1bound=yFace==1.0
    psi.faceGrad.constrain(0.0*faceNorm,where=yeq1bound)

    #x=0 boundary
    xeq0bound=xFace==0.0
    psi.faceGrad.constrain(-1.0*faceNorm,where=xeq0bound)

    #x=1 boundary
    xeq1bound=xFace==1.0
    psi.constrain(0.0,where=xeq1bound)


    #make equatiion and solve
    eq=(fipy.DiffusionTerm(var=psi,coeff=1.0))
    eq.solve(var=psi)

    #now plot solution
    subplot(2,1,2)
    tripcolor(psi.mesh.vertexCoords[0,:],psi.mesh.vertexCoords[1,:],
              tri.simplices,psi.numericValue)
    colorbar()
    title('solution')

    #now print faceGradients
    print 'Face Gradients on x=0, should be
1,0',psi.faceGradAverage[:,xeq0bound]
    print ' '
    print 'Face Gradients on y=0, should be
1,0',psi.faceGradAverage[:,yeq0bound]

Thanks,
Jamie
# These modules takes the output of scipy.spatial.Delaunay's
# triangulation of data points, and returns both the FiPy mesh for the
# points, and a list of the boundary edges so that boundary conditions
# can be set for the points.
#
# This code handles the case where triangles/simplices have been
# removed from output of the Delaunay triangulation after the
# triangulation.  This triangle removal can be useful to create
# interior holes in the domain, or to handle concave boundaries of the
# domain.
#
# The documentation for the routines can be found in there docstrings,
# and examples of there use can be found in __main__

from numpy import *
import fipy
from collections import defaultdict
import copy as Copy

def ScipyDelaunay2mesh(points,simplices,debug=False):
    '''ScipyDelaunay2mesh(points,simplices):

    Creates a FiPy 2Dmesh object from the output of a
    scipy.spatial.Delaunay triangulation, 

    Also returns a list of border faces for the application of
    boundary conditions. The list of border conditions is computed
    within this routine; it will handle the case where
    triangles/simplices have been removed from the output of the
    Delaunay() triangulation.


    Parameters
    ----------
    points : array

       an (N,2) array of the location of N points that made the
       triangulation. If Delaunay is called with
       tri=scipy.spatial.Delaunay(), points is tri.points

    simplices : array

       an (M,3) array of the location of M triangles returned by
       Delaunay. If Delaunay is called with
       tri=scipy.spatial.Delaunay(), simplices is tri.simplices

    debug=False : Boolean

       if this is set to true, the mesh will be drawn in blue,
       boundaries will be drawn in red, boundary point numbers will be
       written at vertices, and numbers for triangles will be drawn in
       the triangle. This output is useful for finding where triangles
       have been removed incorrectly. 

    Returns
    -------

     mesh: a FiPy mesh2D object

     borderList: a list of lists
      
       Each list in borderList is a list of the indices of the faces
       as indexed in mesh.cellFaceIDs. Each list is for a single
       contiguous border of the domain. These will be used to set the
       boundary conditions on the mesh.

    '''

    #Create the arrays that fipy.mesh.mesh2D needs.  vertexCoords is
    #of shape (2,N), where N is the number of points, and contains the
    #coordinates of the vertices. It is equivalent to points in
    #content, but reshaped.
    print 'Entering ScipyDelaunay2Mesh; Making vertexCoords'
    vertexCoords=points.T

    #faceVertexID is of shape (2,M) where M is the number of faces/edges
    #(faces is the fipy terminology, edges is the scipy.spatial.Delaunay
    #terminology). faceVertexID contains the points (as indices into
    #vertexCoords) which make up each face. This data can be extracted
    #from simplices from Delaunay, which contains the faces of each
    #triangle. HOWEVER, simplices contains many repeated faces, since each
    #triangle will border on others. Only one copy of each face should be
    #inserted into faceVertexID.

    print '   Making faceVertexIDs'
    faceIn={} #this is a dictionary of all faces that have already been
              #inserted into the faceVertexID, with the key a tuple of
              #indices, sorted
    faceVertexIDs_list=[] #structure to build

    for ntri in xrange(simplices.shape[0]):
        for nface in xrange(3):
            if nface==0:
                face=simplices[ntri,[0,1]]
            elif nface==1:
                face=simplices[ntri,[1,2]]
            else:
                face=simplices[ntri,[2,0]]
            faceKey=tuple(sort(face))
            if not (faceKey in faceIn):
                faceIn[faceKey]=True
                faceVertexIDs_list.append(face)

    faceVertexIDs=array(faceVertexIDs_list).T

    #now create cellFaceIDs of shape (3,P) where P is the number of cells
    #in the domain. It contains the faces (as indices into faceVertexIDs)
    #that make up each cell.

    #first create dictionary with a key made up of a sorted tuple of vertex
    #indices which maps from a face to its location in faceVertexIDs
    print '   Making cellFaceIDs'
    faceMap={}
    for nface in xrange(faceVertexIDs.shape[1]):
        faceKey=tuple(sort(faceVertexIDs[:,nface]))
        faceMap[faceKey]=nface

    #now loop over simplices, find each edge/face, and map from points to
    #faces
    cellFaceIDs=simplices.T*0
    for ntri in xrange(simplices.shape[0]):
        for nface in xrange(3):
            if nface==0:
                face=simplices[ntri,[0,1]]
            elif nface==1:
                face=simplices[ntri,[1,2]]
            else:
                face=simplices[ntri,[2,0]]
            faceKey=tuple(sort(face))
            cellFaceIDs[nface,ntri]=faceMap[faceKey]

    #ok, now instantiate a mesh2D object with this data
    print '   Making mesh'
    mesh=fipy.meshes.mesh2D.Mesh2D(vertexCoords,faceVertexIDs,cellFaceIDs)

    #now make borderList. We cannot assume the neighbors property of
    #the Delaunay triangulation is valid because some triangles may
    #have been removed.

    #first make vertexPathList, a list of all points that are on
    #borders

    #first, redo neighbors.  Create a defaultdict that when called
    #with a new key returns an empty list
    whichTriangles=defaultdict(list);

    #the entry into the dictionary are the edges of the triangle, sorted
    #so smaller vertex is first
    print '   Making new neighbor data'
    for n in range(simplices.shape[0]):
        edge1=tuple(sort(simplices[n,[0,1]]))
        edge2=tuple(sort(simplices[n,[1,2]]))
        edge3=tuple(sort(simplices[n,[2,0]]))
        whichTriangles[edge1].append(n)
        whichTriangles[edge2].append(n)
        whichTriangles[edge3].append(n)

    if debug:
        triplot(points[:,0],points[:,1],triangles=simplices)
        
        #to debug, plot over all triangles and plot there number in the centers
        for n in range(simplices.shape[0]):
            xave=0.0
            yave=0.0
            for ne in range(3):
                xave=xave+points[simplices[n,ne],0]/3.0
                yave=yave+points[simplices[n,ne],1]/3.0
            text(xave,yave,str(n),fontsize=6)
            
        #to debug, iterate over all edges in whichTriangle, and plot number of neighbors
        for edge in whichTriangles.keys():
            xedge=0.5*(points[edge[0],0]+points[edge[1],0])
            yedge=0.5*(points[edge[0],1]+points[edge[1],1])
            if len(whichTriangles[edge])==1:
                plot(xedge,yedge,'r*',mec='none')
                text(points[edge[0],0],points[edge[0],1],str(edge[0]),color='r',fontsize=9)
                text(points[edge[1],0],points[edge[1],1],str(edge[1]),color='r',fontsize=9)
            elif len(whichTriangles[edge])==2:
                plot(xedge,yedge,'g*',mec='none')
            else:
                assert False, 'wrong number of neighbors'

    #now, for each edge/face, see if it has any neighbors
    neighbors=simplices*0
    for n in range(simplices.shape[0]):
        edge1=tuple(sort(simplices[n,[0,1]]))
        edge2=tuple(sort(simplices[n,[1,2]]))
        edge3=tuple(sort(simplices[n,[2,0]]))
        edges=[edge1,edge2,edge3]
        for ne in range(3):
            edge=edges[ne]
            if len(whichTriangles[edge])==1:
                neighbors[n,ne]=-1
            elif len(whichTriangles[edge])==2:
                if whichTriangles[edge][0]==n:
                    neighbors[n,ne]=whichTriangles[edge][1]
                else:
                    neighbors[n,ne]=whichTriangles[edge][0]
                    assert whichTriangles[edge][1]==n,'Umm, how can this be true?!?!'
            else:
                assert False, 'I am confused, simplex has other than 0 or 1 neighbor...'

    #identify border edges/face; they are the ones where
    #neighbor=-1. Collect edgePoints, a list of tuples that define
    #points that connect along the edges of the domain
    edgePoints=[]
    for n in range(simplices.shape[0]):
        edge1=sort(simplices[n,[0,1]])
        edge2=sort(simplices[n,[1,2]])
        edge3=sort(simplices[n,[2,0]])
        if neighbors[n,0]==-1:
            edgePoints.append((edge1[0],edge1[1]))
            if debug:
                plot(points[[edge1[0],edge1[1]],0],
                     points[[edge1[0],edge1[1]],1],'r-',linewidth=4,alpha=0.2)
        if neighbors[n,1]==-1:
            edgePoints.append((edge2[0],edge2[1]))
            if debug:
                plot(points[[edge2[0],edge2[1]],0],
                     points[[edge2[0],edge2[1]],1],'r-',linewidth=4,alpha=0.2)
        if neighbors[n,2]==-1:
            edgePoints.append((edge3[0],edge3[1]))
            if debug:
                plot(points[[edge3[0],edge3[1]],0],
                     points[[edge3[0],edge3[1]],1],'r-',linewidth=4,alpha=0.2)

    #ok, now make vertexPathList. Make a dictionary of paths so
    #paths(npoint) returns list of points the path is connected to.
    print '   Now make vertexPathList'
    paths=defaultdict(list)
    for np in xrange(len(edgePoints)):
        edge=edgePoints[np]
        paths[edge[0]].append(edge[1])
        paths[edge[1]].append(edge[0])
    
    #now follow each path to find a cycle; store each cycle in vertexPathList,
    #which is a list of lists. Be sure to remove each path after taking it.
    vertexPathList=[]
    keys=paths.keys()
    for key in keys:
        edges=paths[key]
        if len(edges)==0:
            assert True,'nothing to do here'
        else:
            thispath=[key] #start of path
            nhere=paths[key].pop()
            nfrom=key+0
            cols='rgbcmyk'
            while (nhere != key):
                if False:
                    plot([points[nhere,0],points[nfrom,0]]
                         ,[points[nhere,1],points[nfrom,1]],cols[remainder(key,7)]+'*-',
                         mec='none',markersize=12,linewidth=2)
                
                #remove path whence we came
                paths[nhere].remove(nfrom)

                #add this point to path
                thispath.append(nhere)

                #now move to next point, also remove that path:
                nfrom=nhere+0
                nhere=paths[nhere].pop()

            vertexPathList.append(thispath)


    #vertexPathList contains a list of lists of vertex points that
    #surround all islands and (at item nOcean) the model domain
    #boundary. All of paths are closed. I need to convert this into a
    #list of faces, as defined in faceMap and faceVertexIDs. I also
    #need to make sure to include the face that connects the first and
    #last element of the list.

    #remember faceMap is a dictionary with a key made up of a sorted tuple of vertex
    #indices which maps from a face to its location in faceVertexIDs

    #first, make a copy of vertexPathList, since all lists will be the same size
    borderList=Copy.deepcopy(vertexPathList)

    #now fill fipy BoundaryList with good data
    for ncycle in xrange(len(vertexPathList)):
        for npoint in xrange(len(vertexPathList[ncycle])):
            nOtherPoint=remainder(npoint+1,len(vertexPathList[ncycle]))
            faceKey=tuple(sort((vertexPathList[ncycle][npoint],
                                vertexPathList[ncycle][nOtherPoint])))
            borderList[ncycle][npoint]=faceMap[faceKey]
    

       
    return (mesh,borderList)

if __name__ == "__main__":

    from pylab import *
    from scipy.spatial import Delaunay
    
    print ' '
    print 'Test case -- square domain with Del^2 Psi=0 and various BC'
    
    #set up figure
    figure(1)
    clf()

    #make points in square domain of size 1
    points=[]
    xvec=linspace(0.0,1.0,30)
    xmat,ymat=meshgrid(xvec,xvec)
    points=zip(xmat.flatten(),ymat.flatten())
    
    #to debug shuffle points
    shuffle(points)
    
    
    #now make triangulation, and convert to mesh
    tri=Delaunay(points)
    mesh,borderList=ScipyDelaunay2mesh(tri.points,tri.simplices)

    #now plot mesh
    figure(1)
    clf()
    subplot(2,1,1)
    triplot(mesh.vertexCoords[0,:],mesh.vertexCoords[1,:],triangles=tri.simplices)
    title('simple square mesh')
    draw()
    show()

    #setup fipy solution for Del^2 Psi = 1 with BC of 0 everywhere
    #first, make grid variable
    psi=fipy.CellVariable(name='psi',mesh=mesh,value=0.0)

    #apply boundary of Del\Psi \dot \nhat=1 on x=0 boundary
    #                  Del\Psi \dot \nhat=0 on y=0,1 boundary
    #and Psi=0 on x=1

    xFace,yFace=mesh.faceCenters
    faceNorm=mesh.faceNormals

    #y=0 boundary
    yeq0bound=yFace==0.0
    psi.faceGrad.constrain(0.0*faceNorm,where=yeq0bound)
    
    #y=1.0 boundary
    yeq1bound=yFace==1.0
    psi.faceGrad.constrain(0.0*faceNorm,where=yeq1bound)
    
    #x=0 boundary
    xeq0bound=xFace==0.0
    psi.faceGrad.constrain(-1.0*faceNorm,where=xeq0bound)
    
    #x=1 boundary
    xeq1bound=xFace==1.0
    psi.constrain(0.0,where=xeq1bound)
            

    #make equatiion and solve
    eq=(fipy.DiffusionTerm(var=psi,coeff=1.0))
    eq.solve(var=psi)

    #now plot solution
    subplot(2,1,2)
    tripcolor(psi.mesh.vertexCoords[0,:],psi.mesh.vertexCoords[1,:],
              tri.simplices,psi.numericValue)
    colorbar()
    title('solution')

    #now print faceGradients
    print 'Face Gradients on x=0, should be 1,0',psi.faceGradAverage[:,xeq0bound]
    print ' '
    print 'Face Gradients on y=0, should be 1,0',psi.faceGradAverage[:,yeq0bound]

  
_______________________________________________
fipy mailing list
fipy@nist.gov
http://www.ctcms.nist.gov/fipy
  [ NIST internal ONLY: https://email.nist.gov/mailman/listinfo/fipy ]

Reply via email to