Beispiel #1
0
 def _calcGeomCoeff(self, var):
     self._checkCoeff(var)
     if var.rank != 0 and not isinstance(self.coeff, CellVariable):
         return self.coeff[..., numerix.newaxis] * numerix.resize(
             var.mesh.cellVolumes, var.shape)
     else:
         return self.coeff * numerix.resize(var.mesh.cellVolumes, var.shape)
Beispiel #2
0
    def _createVertices(self):

        x = numerix.arange(self.nx + 1) * self.dx
        y = numerix.arange(self.ny + 1) * self.dy
        x = numerix.resize(x, (self.numberOfCornerVertices, ))
        y = numerix.repeat(y, self.nx + 1)
        boxCorners = numerix.array((x, y))
        x = numerix.arange(0.5, self.nx + 0.5) * self.dx
        y = numerix.arange(0.5, self.ny + 0.5) * self.dy
        x = numerix.resize(x, (self.numberOfCenterVertices, ))
        y = numerix.repeat(y, self.nx)
        boxCenters = numerix.array((x, y))
        return numerix.concatenate((boxCorners, boxCenters), axis=1)
Beispiel #3
0
 def _createVertices(self):
     x = self._calcVertexCoordinates(self.dx, self.nx)
     x = numerix.resize(x, (self.numberOfVertices,))
     
     y = self._calcVertexCoordinates(self.dy, self.ny)
     y = numerix.repeat(y, self.numberOfVerticalColumns)
     y = numerix.resize(y, (self.numberOfVertices,))
     
     z = self._calcVertexCoordinates(self.dz, self.nz)
     z = numerix.repeat(z, self.numberOfHorizontalRows * self.numberOfVerticalColumns)
     z = numerix.resize(z, (self.numberOfVertices,))
     
     return numerix.array((x, y, z))
Beispiel #4
0
    def createVertices(dx, dy, dz, nx, ny, nz, numVertices, numHorizRows, numVertCols):
        x = _AbstractGridBuilder.calcVertexCoordinates(dx, nx)
        x = numerix.resize(x, (numVertices,))

        y = _AbstractGridBuilder.calcVertexCoordinates(dy, ny)
        y = numerix.repeat(y, numVertCols)
        y = numerix.resize(y, (numVertices,))

        z = _AbstractGridBuilder.calcVertexCoordinates(dz, nz)
        z = numerix.repeat(z, numHorizRows * numVertCols)
        z = numerix.resize(z, (numVertices,))

        return numerix.array((x, y, z))
Beispiel #5
0
    def _createVertices(self):

        x = numerix.arange(self.nx + 1) * self.dx
        y = numerix.arange(self.ny + 1) * self.dy
        x = numerix.resize(x, (self.numberOfCornerVertices,))
        y = numerix.repeat(y, self.nx + 1)
        boxCorners = numerix.array((x, y))
        x = numerix.arange(0.5, self.nx + 0.5) * self.dx
        y = numerix.arange(0.5, self.ny + 0.5) * self.dy
        x = numerix.resize(x, (self.numberOfCenterVertices,))
        y = numerix.repeat(y, self.nx)
        boxCenters = numerix.array((x, y))
        return numerix.concatenate((boxCorners, boxCenters), axis=1)
    def createVertices(dx, dy, dz, nx, ny, nz, numVertices, numHorizRows,
                       numVertCols):
        x = _AbstractGridBuilder.calcVertexCoordinates(dx, nx)
        x = numerix.resize(x, (numVertices, ))

        y = _AbstractGridBuilder.calcVertexCoordinates(dy, ny)
        y = numerix.repeat(y, numVertCols)
        y = numerix.resize(y, (numVertices, ))

        z = _AbstractGridBuilder.calcVertexCoordinates(dz, nz)
        z = numerix.repeat(z, numHorizRows * numVertCols)
        z = numerix.resize(z, (numVertices, ))

        return numerix.array((x, y, z))
Beispiel #7
0
 def _reshapeIDs(self, var, ids):
     shape = (self._vectorSize(var), self._vectorSize(var), ids.shape[-1])
     ids = numerix.resize(ids, shape)
     X, Y =  numerix.indices(shape[:-1])
     X *= var.mesh.numberOfCells
     ids += X[...,numerix.newaxis]
     return ids
Beispiel #8
0
    def __mul__(self, factor):
        if numerix.shape(factor) is ():
            factor = numerix.resize(factor, (2, 1))

        return UniformGrid2D(dx=self.args['dx'] * numerix.array(factor[0]), nx=self.args['nx'],
                             dy=self.args['dy'] * numerix.array(factor[1]), ny=self.args['ny'],
                             origin=numerix.array(self.args['origin']) * factor, overlap=self.args['overlap'])
Beispiel #9
0
    def _getRotationTensor(self, mesh):
        if not hasattr(self, 'rotationTensor'):

            from fipy.variables.faceVariable import FaceVariable
            rotationTensor = FaceVariable(mesh=mesh, rank=2)
            
            rotationTensor[:, 0] = self._getNormals(mesh)

            if mesh.getDim() == 2:
                rotationTensor[:,1] = rotationTensor[:,0].dot((((0, 1), (-1, 0))))
            elif mesh.getDim() ==3:
                epsilon = 1e-20

                div = numerix.sqrt(1 - rotationTensor[2,0]**2)
                flag = numerix.resize(div > epsilon, (mesh.getDim(), mesh._getNumberOfFaces()))

                rotationTensor[0, 1] = 1
                rotationTensor[:, 1] = numerix.where(flag,
                                                     rotationTensor[:,0].dot((((0, 1, 0), (-1, 0, 0), (0, 0, 0)))) / div,
                                                     rotationTensor[:, 1])

                rotationTensor[1, 2] = 1
                rotationTensor[:, 2] = numerix.where(flag,
                                                     rotationTensor[:,0] * rotationTensor[2,0] / div,
                                                     rotationTensor[:, 2])
                rotationTensor[2, 2] = -div
                
            self.rotationTensor = rotationTensor

        return self.rotationTensor
Beispiel #10
0
    def __getRotationTensor(self, mesh):
        if not hasattr(self, 'rotationTensor'):

            rotationTensor = FaceVariable(mesh=mesh, rank=2)

            rotationTensor[:, 0] = self._getNormals(mesh)

            if mesh.dim == 2:
                rotationTensor[:, 1] = rotationTensor[:, 0].dot(
                    (((0, 1), (-1, 0))))
            elif mesh.dim == 3:
                epsilon = 1e-20

                div = numerix.sqrt(1 - rotationTensor[2, 0]**2)
                flag = numerix.resize(div > epsilon,
                                      (mesh.dim, mesh.numberOfFaces))

                rotationTensor[0, 1] = 1
                rotationTensor[:, 1] = numerix.where(
                    flag, rotationTensor[:, 0].dot(
                        (((0, 1, 0), (-1, 0, 0), (0, 0, 0)))) / div,
                    rotationTensor[:, 1])

                rotationTensor[1, 2] = 1
                rotationTensor[:, 2] = numerix.where(
                    flag, rotationTensor[:, 0] * rotationTensor[2, 0] / div,
                    rotationTensor[:, 2])
                rotationTensor[2, 2] = -div

            self.rotationTensor = rotationTensor

        return self.rotationTensor
Beispiel #11
0
 def _reshapeIDs(self, var, ids):
     shape = (self._vectorSize(var), self._vectorSize(var), ids.shape[-1])
     ids = numerix.resize(ids, shape)
     X, Y = numerix.indices(shape[:-1])
     X *= var.mesh.numberOfCells
     ids += X[..., numerix.newaxis]
     return ids
Beispiel #12
0
 def _createVertices(self):
     x = self._calcVertexCoordinates(self.dx, self.nx)
     x = numerix.resize(x, (self.numberOfVertices,))
         
     y = self._calcVertexCoordinates(self.dy, self.ny)
     y = numerix.repeat(y, self.numberOfVerticalColumns)
     
     return numerix.array((x, y))
Beispiel #13
0
 def createVertices(nx, ny, dx, dy, numVerts, numVertCols):
     x = _AbstractGridBuilder.calcVertexCoordinates(dx, nx)
     x = numerix.resize(x, (numVerts,))
         
     y = _AbstractGridBuilder.calcVertexCoordinates(dy, ny)
     y = numerix.repeat(y, numVertCols)
     
     return numerix.array((x, y))
Beispiel #14
0
    def createVertices(nx, ny, dx, dy, numVerts, numVertCols):
        x = _AbstractGridBuilder.calcVertexCoordinates(dx, nx)
        x = numerix.resize(x, (numVerts, ))

        y = _AbstractGridBuilder.calcVertexCoordinates(dy, ny)
        y = numerix.repeat(y, numVertCols)

        return numerix.array((x, y))
Beispiel #15
0
    def _calcFaceCellToCellNormals(self):
        faceCellCentersUp = numerix.take(self._cellCenters, self.faceCellIDs[1], axis=1)
        faceCellCentersDown = numerix.take(self._cellCenters, self.faceCellIDs[0], axis=1)
        faceCellCentersUp = numerix.where(MA.getmaskarray(faceCellCentersUp),
                                          self._faceCenters,
                                          faceCellCentersUp)

        diff = faceCellCentersDown - faceCellCentersUp
        mag = numerix.sqrt(numerix.sum(diff**2))
        faceCellToCellNormals = diff / numerix.resize(mag, (self.dim, len(mag)))

        orientation = 1 - 2 * (numerix.dot(self.faceNormals, faceCellToCellNormals) < 0)
        return faceCellToCellNormals * orientation
Beispiel #16
0
    def _makeValue(self, value, unit=None, array=None):

        ## --inline code often returns spurious results with noncontiguous
        ## arrays. A test case was put in _execInline(). The best fix turned out to
        ## be here.
        
        if (inline.doInline 
            and hasattr(value, 'iscontiguous') and not value.iscontiguous()):
            value = value.copy()
            
        if isinstance(value, Variable):
            value = value.getValue()
            
        PF = physicalField.PhysicalField

        if not isinstance(value, PF):
            
            if getattr(self, 'value', None) is not None:
                v = self.value
                if isinstance(v, PF):
                    v = self.value.value
                if type(value) in (type(1), type(1.)):
                    if type(v) is type(numerix.array(1)):
                        if v.shape is not ():
##                        if len(v) > 1:
                            value = numerix.resize(value, v.shape).astype(v.dtype)
                    
            if unit is not None or type(value) in [type(''), type(()), type([])]:
                value = PF(value=value, unit=unit, array=array)
            elif array is not None:
                array[:] = value
                value = array
            elif type(value) not in (type(None), type(numerix.array(1)), type(numerix.MA.array(1))):
                value = numerix.array(value)
##                 # numerix does strange things with really large integers.
##                 # Even though Python knows how to do arithmetic with them,
##                 # Numeric converts them to 'O' objects that it then doesn't understand.
##                 if value.typecode() == 'O':
##                     value = numerix.array(float(value))

        if isinstance(value, PF) and value.getUnit().isDimensionless():
            value = value.getNumericValue()
            
        return value
Beispiel #17
0
    def _getNearestCellID(self, points):
        """
        Test cases

           >>> from fipy import *
           >>> m0 = Grid2D(dx=(.1, 1., 10.), dy=(.1, 1., 10.))
           >>> m1 = Grid2D(nx=2, ny=2, dx=5., dy=5.)
           >>> print m0._getNearestCellID(m1.getCellCenters().getGlobalValue())
           [4 5 7 8]
           
        """
        if self.globalNumberOfCells == 0:
            return numerix.arange(0)
            
        points = numerix.resize(points, (self.globalNumberOfCells, len(points), len(points[0]))).swapaxes(0,1)

        centers = self.getCellCenters().getGlobalValue()[...,numerix.newaxis]
        try:
            tmp = centers - points
        except TypeError:
            tmp = centers - PhysicalField(points)

        return numerix.argmin(numerix.dot(tmp, tmp, axis = 0), axis=0)
Beispiel #18
0
 def _calcGeomCoeff(self, var):
     self._checkCoeff(var)
     if var.rank != 0 and not isinstance(self.coeff, CellVariable):
         return self.coeff[..., numerix.newaxis] * numerix.resize(var.mesh.cellVolumes, var.shape)
     else:
         return self.coeff * numerix.resize(var.mesh.cellVolumes, var.shape)
Beispiel #19
0
    def _extrude(self, mesh, extrudeFunc, layers):
        ## should extrude cnahe self rather than creating a new mesh?

        ## the following allows the 2D mesh to be in 3D space, this can be the case for a
        ## Gmsh2DIn3DSpace which would then be extruded.
        oldVertices = mesh.vertexCoords
        if oldVertices.shape[0] == 2:
            oldVertices = numerix.resize(oldVertices, (3, len(oldVertices[0])))
            oldVertices[2] = 0

        NCells = mesh.numberOfCells
        NFac = mesh.numberOfFaces
        NFacPerCell =  mesh._maxFacesPerCell

        ## set up the initial data arrays
        new_shape = (max(NFacPerCell, 4), (1 + layers)*NCells + layers*NFac)
        faces = numerix.MA.masked_values(-numerix.ones(new_shape, 'l'), value = -1)
        orderedVertices = mesh._orderedCellVertexIDs
        faces[:NFacPerCell, :NCells] = orderedVertices
        vertices = oldVertices
        vert0 = mesh.faceVertexIDs
        faceCount = NCells
        
        for layer in range(layers):

            ## need this later
            initialFaceCount = faceCount

            ## build the vertices
            newVertices = extrudeFunc(oldVertices)
            vertices = numerix.concatenate((vertices, newVertices), axis=1)

            ## build the faces along the layers
            faces[:NFacPerCell, faceCount: faceCount + NCells] = orderedVertices + len(oldVertices[0]) * (layer + 1)
            try:
                # numpy 1.1 doesn't copy right side before assigning slice
                # See: http://www.mail-archive.com/[email protected]/msg09843.html
                faces[:NFacPerCell, faceCount: faceCount + NCells] = faces[:NFacPerCell, faceCount: faceCount + NCells][::-1,:].copy()
            except:
                faces[:NFacPerCell, faceCount: faceCount + NCells] = faces[:NFacPerCell, faceCount: faceCount + NCells][::-1,:]

            faceCount = faceCount + NCells

            vert1 = (vert0 + len(oldVertices[0]))[::-1,:]

            ## build the faces between the layers
            faces[:4, faceCount: faceCount + NFac] = numerix.concatenate((vert0, vert1), axis = 0)[::-1,:]

            vert0 = vert0 + len(oldVertices[0])

            NCells = mesh.numberOfCells

            
            ## build the cells, the first layer has slightly different ordering
            if layer == 0:
                c0 =  numerix.reshape(numerix.arange(NCells), (1, NCells))
                cells = numerix.concatenate((c0, c0 + NCells, mesh.cellFaceIDs + 2 * NCells), axis = 0)
            else:
                newCells = numerix.concatenate((c0, c0 + initialFaceCount, mesh.cellFaceIDs + faceCount), axis=0)
                newCells[0] = cells[1,-NCells:]
                cells = numerix.concatenate((cells, newCells), axis=1)

            ## keep a count of things for the next layer
            faceCount = faceCount + NFac
            oldVertices = newVertices

        ## return a new mesh, extrude could just as easily act on self
        return Mesh(vertices, faces, cells, communicator=mesh.communicator)
Beispiel #20
0
    def _extrude(self, mesh, extrudeFunc, layers):
        ## should extrude self rather than creating a new mesh?

        ## the following allows the 2D mesh to be in 3D space, this can be the case for a
        ## Gmsh2DIn3DSpace which would then be extruded.
        oldVertices = mesh.vertexCoords
        if oldVertices.shape[0] == 2:
            oldVertices = numerix.resize(oldVertices, (3, len(oldVertices[0])))
            oldVertices[2] = 0

        NCells = mesh.numberOfCells
        NFac = mesh.numberOfFaces
        NFacPerCell =  mesh._maxFacesPerCell

        ## set up the initial data arrays
        new_shape = (max(NFacPerCell, 4), (1 + layers)*NCells + layers*NFac)
        faces = numerix.MA.masked_values(-numerix.ones(new_shape, 'l'), value = -1)
        orderedVertices = mesh._orderedCellVertexIDs
        faces[:NFacPerCell, :NCells] = orderedVertices
        vertices = oldVertices
        vert0 = mesh.faceVertexIDs
        faceCount = NCells

        for layer in range(layers):

            ## need this later
            initialFaceCount = faceCount

            ## build the vertices
            newVertices = extrudeFunc(oldVertices)
            vertices = numerix.concatenate((vertices, newVertices), axis=1)

            ## build the faces along the layers
            faces[:NFacPerCell, faceCount: faceCount + NCells] = orderedVertices + len(oldVertices[0]) * (layer + 1)
            try:
                # numpy 1.1 doesn't copy right side before assigning slice
                # See: http://www.mail-archive.com/[email protected]/msg09843.html
                faces[:NFacPerCell, faceCount: faceCount + NCells] = faces[:NFacPerCell, faceCount: faceCount + NCells][::-1,:].copy()
            except:
                faces[:NFacPerCell, faceCount: faceCount + NCells] = faces[:NFacPerCell, faceCount: faceCount + NCells][::-1,:]

            faceCount = faceCount + NCells

            vert1 = (vert0 + len(oldVertices[0]))[::-1,:]

            ## build the faces between the layers
            faces[:4, faceCount: faceCount + NFac] = numerix.concatenate((vert0, vert1), axis = 0)[::-1,:]

            vert0 = vert0 + len(oldVertices[0])

            NCells = mesh.numberOfCells


            ## build the cells, the first layer has slightly different ordering
            if layer == 0:
                c0 =  numerix.reshape(numerix.arange(NCells), (1, NCells))
                cells = numerix.concatenate((c0, c0 + NCells, mesh.cellFaceIDs + 2 * NCells), axis = 0)
            else:
                newCells = numerix.concatenate((c0, c0 + initialFaceCount, mesh.cellFaceIDs + faceCount), axis=0)
                newCells[0] = cells[1, -NCells:]
                cells = numerix.concatenate((cells, newCells), axis=1)

            ## keep a count of things for the next layer
            faceCount = faceCount + NFac
            oldVertices = newVertices

        ## return a new mesh, extrude could just as easily act on self
        return Mesh(vertices, faces, cells, communicator=mesh.communicator)
Beispiel #21
0
    def _getAddedMeshValues(self, other, resolution=1e-2):
        """Calculate the parameters to define a concatenation of `other` with `self`
        
        :Parameters:
          - `other`: The :class:`~fipy.meshes.numMesh.Mesh` to concatenate with `self`
          - `resolution`: How close vertices have to be (relative to the smallest 
            cell-to-cell distance in either mesh) to be considered the same

        :Returns:
          A `dict` with 3 elements: the new mesh vertexCoords, faceVertexIDs, and cellFaceIDs.
        """
        
        selfc = self._getConcatenableMesh()
        other = other._getConcatenableMesh()

        selfNumFaces = selfc.faceVertexIDs.shape[-1]
        selfNumVertices = selfc.vertexCoords.shape[-1]
        otherNumFaces = other.faceVertexIDs.shape[-1]
        otherNumVertices = other.vertexCoords.shape[-1]
        ## check dimensions
        if(selfc.vertexCoords.shape[0] != other.vertexCoords.shape[0]):
            raise MeshAdditionError, "Dimensions do not match"
            
        ## compute vertex correlates

        ## only try to match exterior (X) vertices
        self_Xvertices = numerix.unique(selfc._getFaceVertexIDs().filled()[..., selfc.getExteriorFaces().getValue()].flatten())
        other_Xvertices = numerix.unique(other._getFaceVertexIDs().filled()[..., other.getExteriorFaces().getValue()].flatten())

        self_XvertexCoords = selfc.vertexCoords[..., self_Xvertices]
        other_XvertexCoords = other.vertexCoords[..., other_Xvertices]
        
        # lifted from Mesh._getNearestCellID()
        other_vertexCoordMap = numerix.resize(other_XvertexCoords, 
                                              (self_XvertexCoords.shape[-1], 
                                               other_XvertexCoords.shape[0], 
                                               other_XvertexCoords.shape[-1])).swapaxes(0,1)
        tmp = self_XvertexCoords[..., numerix.newaxis] - other_vertexCoordMap
        closest = numerix.argmin(numerix.dot(tmp, tmp), axis=0)
        
        # just because they're closest, doesn't mean they're close
        tmp = self_XvertexCoords[..., closest] - other_XvertexCoords
        distance = numerix.sqrtDot(tmp, tmp)
        # only want vertex pairs that are 100x closer than the smallest 
        # cell-to-cell distance
        close = distance < resolution * min(selfc._getCellToCellDistances().min(), 
                                            other._getCellToCellDistances().min())
        vertexCorrelates = numerix.array((self_Xvertices[closest[close]],
                                          other_Xvertices[close]))
        
        # warn if meshes don't touch, but allow it
        if (selfc._getNumberOfVertices() > 0 
            and other._getNumberOfVertices() > 0 
            and vertexCorrelates.shape[-1] == 0):
            import warnings
            warnings.warn("Vertices are not aligned", UserWarning, stacklevel=4)

        ## compute face correlates

        # ensure that both sets of faceVertexIDs have the same maximum number of (masked) elements
        self_faceVertexIDs = selfc.faceVertexIDs
        other_faceVertexIDs = other.faceVertexIDs

        diff = self_faceVertexIDs.shape[0] - other_faceVertexIDs.shape[0]
        if diff > 0:
            other_faceVertexIDs = numerix.append(other_faceVertexIDs, 
                                                 -1 * numerix.ones((diff,) 
                                                                   + other_faceVertexIDs.shape[1:]),
                                                 axis=0)
            other_faceVertexIDs = MA.masked_values(other_faceVertexIDs, -1)
        elif diff < 0:
            self_faceVertexIDs = numerix.append(self_faceVertexIDs, 
                                                -1 * numerix.ones((-diff,) 
                                                                  + self_faceVertexIDs.shape[1:]),
                                                axis=0)
            self_faceVertexIDs = MA.masked_values(self_faceVertexIDs, -1)

        # want self's Faces for which all faceVertexIDs are in vertexCorrelates
        self_matchingFaces = numerix.in1d(self_faceVertexIDs, 
                                          vertexCorrelates[0]).reshape(self_faceVertexIDs.shape).all(axis=0).nonzero()[0]

        # want other's Faces for which all faceVertexIDs are in vertexCorrelates
        other_matchingFaces = numerix.in1d(other_faceVertexIDs, 
                                           vertexCorrelates[1]).reshape(other_faceVertexIDs.shape).all(axis=0).nonzero()[0]
                                           
        # map other's Vertex IDs to new Vertex IDs, 
        # accounting for overlaps with self's Vertex IDs
        vertex_map = numerix.empty(otherNumVertices, dtype=int)
        verticesToAdd = numerix.delete(numerix.arange(otherNumVertices), vertexCorrelates[1])
        vertex_map[verticesToAdd] = numerix.arange(otherNumVertices - len(vertexCorrelates[1])) + selfNumVertices
        vertex_map[vertexCorrelates[1]] = vertexCorrelates[0]

        # calculate hashes of faceVertexIDs for comparing Faces
        
        if self_matchingFaces.shape[-1] == 0:
            self_faceHash = numerix.empty(self_matchingFaces.shape[:-1] + (0,), dtype="str")
        else:
            # sort each of self's Face's vertexIDs for canonical comparison
            self_faceHash = numerix.sort(self_faceVertexIDs[..., self_matchingFaces], axis=0)
            # then hash the Faces for comparison (NumPy set operations are only for 1D arrays)
            self_faceHash = numerix.apply_along_axis(str, axis=0, arr=self_faceHash)
            
        face_sort = numerix.argsort(self_faceHash)
        self_faceHash = self_faceHash[face_sort]
        self_matchingFaces = self_matchingFaces[face_sort]

        if other_matchingFaces.shape[-1] == 0:
            other_faceHash = numerix.empty(other_matchingFaces.shape[:-1] + (0,), dtype="str")
        else:
            # convert each of other's Face's vertexIDs to new IDs
            other_faceHash = vertex_map[other_faceVertexIDs[..., other_matchingFaces]]
            # sort each of other's Face's vertexIDs for canonical comparison
            other_faceHash = numerix.sort(other_faceHash, axis=0)
            # then hash the Faces for comparison (NumPy set operations are only for 1D arrays)
            other_faceHash = numerix.apply_along_axis(str, axis=0, arr=other_faceHash)

        face_sort = numerix.argsort(other_faceHash)
        other_faceHash = other_faceHash[face_sort]
        other_matchingFaces = other_matchingFaces[face_sort]

        self_matchingFaces = self_matchingFaces[numerix.in1d(self_faceHash, 
                                                             other_faceHash)]
        other_matchingFaces = other_matchingFaces[numerix.in1d(other_faceHash, 
                                                               self_faceHash)]
        
        faceCorrelates = numerix.array((self_matchingFaces,
                                        other_matchingFaces))

        # warn if meshes don't touch, but allow it
        if (selfc._getNumberOfFaces() > 0 
            and other._getNumberOfFaces() > 0 
            and faceCorrelates.shape[-1] == 0):
            import warnings
            warnings.warn("Faces are not aligned", UserWarning, stacklevel=4)

        # map other's Face IDs to new Face IDs, 
        # accounting for overlaps with self's Face IDs
        face_map = numerix.empty(otherNumFaces, dtype=int)
        facesToAdd = numerix.delete(numerix.arange(otherNumFaces), faceCorrelates[1])
        face_map[facesToAdd] = numerix.arange(otherNumFaces - len(faceCorrelates[1])) + selfNumFaces
        face_map[faceCorrelates[1]] = faceCorrelates[0]
        
        other_faceVertexIDs = vertex_map[other.faceVertexIDs[..., facesToAdd]]
        
        # ensure that both sets of cellFaceIDs have the same maximum number of (masked) elements
        self_cellFaceIDs = selfc.cellFaceIDs
        other_cellFaceIDs = face_map[other.cellFaceIDs]
        diff = self_cellFaceIDs.shape[0] - other_cellFaceIDs.shape[0]
        if diff > 0:
            other_cellFaceIDs = numerix.append(other_cellFaceIDs, 
                                               -1 * numerix.ones((diff,) 
                                                                 + other_cellFaceIDs.shape[1:]),
                                               axis=0)
            other_cellFaceIDs = MA.masked_values(other_cellFaceIDs, -1)
        elif diff < 0:
            self_cellFaceIDs = numerix.append(self_cellFaceIDs, 
                                              -1 * numerix.ones((-diff,) 
                                                                + self_cellFaceIDs.shape[1:]),
                                              axis=0)
            self_cellFaceIDs = MA.masked_values(self_cellFaceIDs, -1)

        # concatenate everything and return
        return {
            'vertexCoords': numerix.concatenate((selfc.vertexCoords, 
                                                 other.vertexCoords[..., verticesToAdd]), axis=1), 
            'faceVertexIDs': numerix.concatenate((self_faceVertexIDs, 
                                                  other_faceVertexIDs), axis=1), 
            'cellFaceIDs': MA.concatenate((self_cellFaceIDs, 
                                           other_cellFaceIDs), axis=1)
            }