scikit-FEM-mesh

# -*- coding: utf-8 -*-
"""
“Mesh”模块包含了有限元网格的不同类型.

See the following implementations:

    * MeshTri,三角剖分网格
    * MeshTet, 四面体剖分网格
    * MeshQuad, 矩形剖分网格
    * MeshHex, 六面体剖分网格
"""
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
import numpy as np
import warnings
from scipy.sparse import coo_matrix

import skfem.mapping
import skfem.element

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

class Mesh():
    """A finite element mesh.
    
    This is an abstract superclass. See the following implementations:

        * MeshTri, triangular mesh
        * MeshTet, tetrahedral mesh
        * MeshQuad, quadrilateral mesh
        * MeshHex, hexahedral mesh
    """

    refdom = "none"  
    brefdom = "none" 
    meshio_type = "none"

    p = np.array([]) 
    t = np.array([]) 

1.1

    def __init__(self):
        """Check that p and t are C_CONTIGUOUS as this leads
        to better performance."""
        if self.p is not None:
            if self.p.flags['F_CONTIGUOUS']:
                if self.p.shape[1]>1000:
                    warnings.warn("Mesh.__init__(): Transforming " +
                            "over 100 vertices to C_CONTIGUOUS.")
                self.p = np.ascontiguousarray(self.p)
        if self.t is not None:
            if self.t.flags['F_CONTIGUOUS']:
                if self.t.shape[1]>1000:
                    warnings.warn("Mesh.__init__(): Transforming " +
                            "over 100 elements to C_CONTIGUOUS.")
                self.t = np.ascontiguousarray(self.t)

1.2

    def __str__(self):
        return self.__repr__()

1.3

    def __repr__(self):
        return "Mesh of type '" + str(type(self)) + "' "
               "with " + str(self.p.shape) + " vertices " 
               "and " + str(self.t.shape) + " elements."

1.4

    def show(self):
        """A wrapper包装纸 for matplotlib.pyplot.show()."""
        plt.show()

1.5

    def dim(self):
        """Return the spatial空间的 dimension of the mesh."""
        return int(self.p.shape[0])

1.6

    def mapping(self):
        """Default不履行 local-to-global mapping for the mesh."""
        raise NotImplementedError("Default mapping not implemented!")

1.7

    def _uniform_refine(self):
        """Perform执行 a single uniform mesh refinement一致网格加密."""
        raise NotImplementedError("Single refine not implemented " +
                                  "for this mesh type!")

1.8

    def refine(self, N=None):
        """Refine the mesh.
        
        Parameters
        ----------
        N : int (optional)
            Perform N refinements.
        """
        if N is None:
            return self._uniform_refine()
        else:
            for itr in range(N):
                self._uniform_refine()

1.9

    def remove_elements(self, element_indices):
        """Construct new mesh with elements removed
        based on their indices.

        Parameters
        ----------
        element_indices : numpy array
            List of element indices to remove.

        Returns
        -------
        skfem.Mesh
            A new mesh object with elements removed as per requested.
        """
        keep = np.setdiff1d(np.arange(self.t.shape[1]), element_indices)
        newt = self.t[:, keep]
        ptix = np.unique(newt)
        reverse = np.zeros(self.p.shape[1])
        reverse[ptix] = np.arange(len(ptix))
        newt = reverse[newt]
        newp = self.p[:, ptix]
        meshclass = type(self)
        return meshclass(newp, newt.astype(np.intp))

1.10

    def scale(self, scale):
        """Scale the mesh.

        Parameters
        ----------
        scale : float OR tuple of size dim
            Scale each dimension by a factor. If a floating
            point number is provided, same scale is used
            for each dimension.
        """
        for itr in range(int(self.dim())):
            if isinstance(scale, tuple):
                self.p[itr, :] *= scale[itr]
            else:
                self.p[itr, :] *= scale

1.11

    def translate(self, vec):
        """Translate the mesh.

        Parameters
        ----------
        vec : tuple of size dim
            Translate the mesh by a vector.
        """
        for itr in range(int(self.dim())):
            self.p[itr, :] += vec[itr]

1.12

    def _validate(self):
        """Perform mesh validity checks."""
        # check that element connectivity contains integers
        # NOTE: this is neccessary for some plotting functionality
        if not np.issubdtype(self.t[0, 0], np.signedinteger):
            msg = ("Mesh._validate(): Element connectivity "
                   "must consist of integers.")
            raise Exception(msg)
        # check that vertex matrix has "correct" size
        if self.p.shape[0] > 3:
            msg = ("Mesh._validate(): We do not allow meshes "
                   "embedded into larger than 3-dimensional "
                   "Euclidean space! Please check that "
                   "the given vertex matrix is of size Ndim x Nvertices.")
            raise Exception(msg)
        # check that element connectivity matrix has correct size
        nvertices = {'line': 2, 'tri': 3, 'quad': 4, 'tet': 4, 'hex': 8}
        if self.t.shape[0] != nvertices[self.refdom]:
            msg = ("Mesh._validate(): The given connectivity "
                   "matrix has wrong shape!")
            raise Exception(msg)
        # check that there are no duplicate points
        tmp = np.ascontiguousarray(self.p.T)
        if self.p.shape[1] != np.unique(tmp.view([('', tmp.dtype)]
                                                 * tmp.shape[1])).shape[0]:
            msg = "Mesh._validate(): Mesh contains duplicate vertices."
            warnings.warn(msg)
        # check that all points are at least in some element
        if len(np.setdiff1d(np.arange(self.p.shape[1]), np.unique(self.t))):
            msg = ("Mesh._validate(): Mesh contains a vertex "
                   "not belonging to any element.")
            raise Exception(msg)

1.13

    def save(self, filename, pointData=None, cellData=None):
        """Export the mesh and fields using meshio.

        Parameters
        ----------
        filename : string
            The filename for vtk-file.
        pointData : (optional) numpy array for one output or dict for multiple
        cellData : (optional) numpy array for one output or dict for multiple
        """
        import meshio

        if pointData is not None:
            if type(pointData) != dict:
                pointData = {'0':pointData}

        if cellData is not None:
            if type(cellData) != dict:
                cellData = {'0':cellData}

        cells = { self.meshio_type : self.t.T }
        mesh = meshio.Mesh(self.p.T, cells, pointData, cellData)
        meshio.write(filename, mesh)

1.14

    @classmethod
    def load(cls, filename):
        """Load a mesh from file using meshio.
        
        Parameters
        ----------
        filename : string
            The filename for the mesh.
        """
        import meshio
        mesh = meshio.read(filename)
        if issubclass(cls, Mesh2D):
            return cls(mesh.points[:, :2].T, mesh.cells[cls.meshio_type].T)
        else:
            return cls(mesh.points.T, mesh.cells[cls.meshio_type].T)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

三维网格

class Mesh3D(Mesh):
    """Three dimensional meshes, common methods.

    See the following implementations:

        * MeshTet, tetrahedral mesh
        * MeshHex, hexahedral mesh
    """

2.1

    def nodes_satisfying(self, test):
        """Return nodes that satisfy some condition.

        Parameters
        ----------
        test : lambda function (3 params)
            Evaluates to 1 or True for nodes belonging
            to the output set.
        """
        return np.nonzero(test(self.p[0, :], self.p[1, :], self.p[2, :]))[0]

2.2

    def facets_satisfying(self, test):
        """Return facets whose midpoints satisfy some condition.
        
        Parameters
        ----------
        test : lambda functions (3 params)
            Evaluates to 1 or True for facet midpoints
            of the facets belonging to the output set.
        """
        mx = np.sum(self.p[0, self.facets], axis=0)/self.facets.shape[0]
        my = np.sum(self.p[1, self.facets], axis=0)/self.facets.shape[0]
        mz = np.sum(self.p[2, self.facets], axis=0)/self.facets.shape[0]
        return np.nonzero(test(mx, my, mz))[0]

2.3

    def edges_satisfying(self, test):
        """Return edges whose midpoints satisfy some condition.

        Parameters
        ----------
        test : lambda functions (3 params)
            Evaluates to 1 or True for edge midpoints
            of the edges belonging to the output set.
        """
        mx = 0.5*(self.p[0, self.edges[0, :]] + self.p[0, self.edges[1, :]])
        my = 0.5*(self.p[1, self.edges[0, :]] + self.p[1, self.edges[1, :]])
        mz = 0.5*(self.p[2, self.edges[0, :]] + self.p[2, self.edges[1, :]])
        return np.nonzero(test(mx, my, mz))[0]

2.4

    def boundary_nodes(self):
        """Return an array of boundary node indices."""
        return np.unique(self.facets[:, self.boundary_facets()])

2.5

    def boundary_facets(self):
        """Return an array of boundary facet indices."""
        return np.nonzero(self.f2t[1, :] == -1)[0]

2.6

    def interior_facets(self):
        """Return an array of interior facet indices."""
        return np.nonzero(self.f2t[1, :] >= 0)[0]

2.7

    def boundary_edges(self):
        """Return an array of boundary edge indices."""
        bnodes = self.boundary_nodes()[:, None]
        return np.nonzero(np.sum(self.edges[0, :] == bnodes, axis=0) *
                          np.sum(self.edges[1, :] == bnodes, axis=0))[0]

2.8

    def interior_nodes(self):
        """Return an array of interior node indices."""
        return np.setdiff1d(np.arange(0, self.p.shape[1]), self.boundary_nodes())

2.9

    def draw_vertices(self):
        """Draw all vertices using mplot3d."""
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
        ax.scatter(self.p[0, :], self.p[1, :], self.p[2, :])
        return fig

2.10

    def param(self):
        """Return (maximum) mesh parameter."""
        return np.max(np.sqrt(np.sum((self.p[:, self.edges[0, :]] -
                                      self.p[:, self.edges[1, :]])**2, axis=0)))

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

二维网格

class Mesh2D(Mesh):
    """Two dimensional meshes, common methods.
    
    See the following implementations:

        * MeshTri, triangular mesh
        * MeshQuad, quadrilateral mesh
    """

3.1

    def jiggle(self, z=0.2):
        """Jiggle the interior nodes of the mesh.

        Parameters
        ----------
        z : (optional, default=0.2) float
            Mesh parameter is multiplied by this number. The resulting number
            corresponds to the standard deviation of the jiggle.
        """
        y = z*self.param()
        I = self.interior_nodes()
        self.p[0, I] = self.p[0, I] + y*np.random.rand(len(I))
        self.p[1, I] = self.p[1, I] + y*np.random.rand(len(I))

3.2

    def boundary_nodes(self):
        """Return an array of boundary node indices."""
        return np.unique(self.facets[:, self.boundary_facets()])

3.3

    def nodes_satisfying(self, test):
        """Return nodes that satisfy some condition.

        Parameters
        ----------
        test : lambda
            An anonymous function with two parameters (x and y) and which returns True for the set of nodes
            that are to be included in the return set.

        Returns
        -------
        numpy array
            An array of node indices.
        """
        return np.nonzero(test(self.p[0, :], self.p[1, :]))[0]

3.4

    def draw_nodes(self, nodes, mark='bo'):
        """Highlight some nodes.

        Parameters
        ----------
        nodes : numpy array
            The indices of the nodes to highlight.
        mark : (optional, default='bo') string
            A standard matplotlib string to define the highlight style.
        """
        plt.plot(self.p[0, nodes], self.p[1, nodes], mark)

3.5

    def param(self):
        """Return mesh parameter."""
        return np.max(np.sqrt(np.sum((self.p[:, self.facets[0, :]] -
                                      self.p[:, self.facets[1, :]])**2, axis=0)))

3.6

    def interior_nodes(self):
        """Return an array of interior node indices."""
        return np.setdiff1d(np.arange(0, self.p.shape[1]), self.boundary_nodes())

3.7

    def facets_satisfying(self, test):
        """Return facets whose midpoints satisfy some condition.

        Parameters
        ----------
        test : lambda function (2 params)
            An anonymous function with two parameters (x and y) and which
            returns True for the midpoints of the set of facets that are
            to be included in the return set.
        """
        mx = 0.5*(self.p[0, self.facets[0, :]] + self.p[0, self.facets[1, :]])
        my = 0.5*(self.p[1, self.facets[0, :]] + self.p[1, self.facets[1, :]])
        return np.nonzero(test(mx, my))[0]

3.8

    def elements_satisfying(self, test):
        """Return elements whose midpoints satisfy some condition.

        Parameters
        ----------
        test : lambda function (2 params)
            An anonymous function with two parameters (x and y) and which
            returns True for the midpoints of the set of elements that
            are to be included in the return set.
        """
        mx = np.sum(self.p[0, self.t], axis=0)/self.t.shape[0]
        my = np.sum(self.p[1, self.t], axis=0)/self.t.shape[0]
        return np.nonzero(test(mx, my))[0]

3.9

    def interior_facets(self):
        """Return an array of interior facet indices."""
        return np.nonzero(self.f2t[1, :] >= 0)[0]

3.10

    def boundary_facets(self):
        """Return an array of boundary facet indices."""
        return np.nonzero(self.f2t[1, :] == -1)[0]

3.11

    def draw(self, ax=None, node_numbering=False, facet_numbering=False, element_numbering=False):
        """Draw the mesh.

        Parameters
        ----------
        ax : (optional, default=None) matplotlib axis
            Use a predefined axis for plotting.
        node_numbering : (optional, default=False)
            Draw node numbering.
        facet_numbering: (optional, default=False)
            Draw facet numbering.
        element_numbering : (optional, default=False)
            Draw element numbering.
        """
        if ax is None:
            # create new figure
            fig = plt.figure()
            ax = fig.add_subplot(111)
        # visualize the mesh faster plotting is achieved through
        # None insertion trick.
        xs = []
        ys = []
        for s, t, u, v in zip(self.p[0, self.facets[0, :]],
                              self.p[1, self.facets[0, :]],
                              self.p[0, self.facets[1, :]],
                              self.p[1, self.facets[1, :]]):
            xs.append(s)
            xs.append(u)
            xs.append(None)
            ys.append(t)
            ys.append(v)
            ys.append(None)
        ax.plot(xs, ys, 'k', linewidth='0.5')

        if node_numbering:
            for itr in range(self.p.shape[1]):
                ax.text(self.p[0, itr], self.p[1, itr], str(itr))

        if facet_numbering:
            mx = .5*(self.p[0, self.facets[0, :]] + self.p[0, self.facets[1, :]])
            my = .5*(self.p[1, self.facets[0, :]] + self.p[1, self.facets[1, :]])
            for itr in range(self.facets.shape[1]):
                ax.text(mx[itr], my[itr], str(itr))

        if element_numbering:
            mx = np.sum(self.p[0, self.t], axis=0)/self.t.shape[0]
            my = np.sum(self.p[1, self.t], axis=0)/self.t.shape[0]
            for itr in range(self.t.shape[1]):
                ax.text(mx[itr], my[itr], str(itr))

        return ax

3.12

    def mirror_mesh(self, a, b, c):
        """Mirror a mesh by the line ax + by + c = 0.
        
        Parameters
        ----------
        a : float
        b : float
        c : float
        """
        tmp = -2.0*(a*self.p[0, :] + b*self.p[1, :] + c)/(a**2 + b**2)
        newx = a*tmp + self.p[0, :]
        newy = b*tmp + self.p[1, :]
        newpoints = np.vstack((newx, newy))
        points = np.hstack((self.p, newpoints))
        tris = np.hstack((self.t, self.t + self.p.shape[1]))

        # remove duplicates
        tmp = np.ascontiguousarray(points.T)
        tmp, ixa, ixb = np.unique(tmp.view([('', tmp.dtype)]*tmp.shape[1]), return_index=True, return_inverse=True)
        points = points[:, ixa]
        tris = ixb[tris]

        meshclass = type(self)

        return meshclass(points, tris)

3.13

    def save(self, filename, pointData=None, cellData=None):
        """Export the mesh and fields using meshio. (2D version.)

        Parameters
        ----------
        filename : string
            The filename for vtk-file.
        pointData : (optional) numpy array for one output or dict for multiple
        cellData : (optional) numpy array for one output or dict for multiple
        """
        import meshio

        # a row of zeros required in 2D
        p = np.vstack((np.zeros(self.p.shape[1]), self.p))

        if pointData is not None:
            if type(pointData) != dict:
                pointData = {'0':pointData}

        if cellData is not None:
            if type(cellData) != dict:
                cellData = {'0':cellData}

        cells = { self.meshio_type : self.t.T }
        mesh = meshio.Mesh(p.T, cells, pointData, cellData)
        meshio.write(filename, mesh)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

class InterfaceMesh1D(Mesh):
    """An interface mesh for mortar methods."""

4.1

    def __init__(self, mesh1, mesh2, rule, param, debug_plot=False):
        self.brefdom = mesh1.brefdom

        p1_ix = mesh1.nodes_satisfying(rule)
        p2_ix = mesh2.nodes_satisfying(rule)

        p1 = mesh1.p[:, p1_ix]
        p2 = mesh2.p[:, p2_ix]
        _, ix = np.unique(np.concatenate((param(p1[0, :], p1[1, :]), param(p2[0, :], p2[1, :]))), return_index=True)

        np1 = mesh1.p.shape[1]
        nt1 = mesh1.t.shape[1]
        ixorig = np.concatenate((p1_ix, p2_ix + np1))[ix]

        self.p = np.hstack((mesh1.p, mesh2.p))
        self.t = np.hstack((mesh1.t, mesh2.t + np1))
        self.facets = np.array([ixorig[:-1], ixorig[1:]])
        self.t2f = -1 + 0*np.hstack((mesh1.t2f, mesh2.t2f))

        # construct normals
        tangent_x = self.p[0, self.facets[0, :]] - self.p[0, self.facets[1, :]]
        tangent_y = self.p[1, self.facets[0, :]] - self.p[1, self.facets[1, :]]
        tangent_lengths = np.sqrt(tangent_x**2 + tangent_y**2)

        self.normals = np.array([-tangent_y/tangent_lengths, tangent_x/tangent_lengths])

        if debug_plot:
            ax = mesh1.draw()
            mesh2.draw(ax=ax)
            xs = np.array([self.p[0, self.facets[0, :]], self.p[0, self.facets[1, :]]])
            midx = np.sum(xs, axis=0)/2.0
            ys = np.array([self.p[1, self.facets[0, :]], self.p[1, self.facets[1, :]]])
            midy = np.sum(ys, axis=0)/2.0
            xs = 0.9*(xs - midx) + midx
            ys = 0.9*(ys - midy) + midy
            ax.plot(xs, ys, 'x-')

        # mappings from facets to the original triangles
        # TODO vectorize
        self.f2t = self.facets*0-1
        for itr in range(self.facets.shape[1]):
            mx = .5*(self.p[0, self.facets[0, itr]] + self.p[0, self.facets[1, itr]])
            my = .5*(self.p[1, self.facets[0, itr]] + self.p[1, self.facets[1, itr]])
            val = param(mx, my)
            for jtr in mesh1.boundary_facets():
                fix1 = mesh1.facets[0, jtr]
                x1 = mesh1.p[0, fix1]
                y1 = mesh1.p[1, fix1]
                fix2 = mesh1.facets[1, jtr]
                x2 = mesh1.p[0, fix2]
                y2 = mesh1.p[1, fix2]
                if rule(x1, y1) > 0 or rule(x2, y2) > 0:
                    if val > param(x1, y1) and val < param(x2, y2):
                        # OK
                        self.f2t[0, itr] = mesh1.f2t[0, jtr]
                        break
                    elif val < param(x1, y1) and val > param(x2, y2): # ye olde
                        # OK
                        self.f2t[0, itr] = mesh1.f2t[0, jtr]
                        break
                    elif val >= param(x1, y1) and val < param(x2, y2):
                        # OK
                        self.f2t[0, itr] = mesh1.f2t[0, jtr]
                        break
                    elif val > param(x1, y1) and val <= param(x2, y2):
                        # OK
                        self.f2t[0, itr] = mesh1.f2t[0, jtr]
                        break
                    elif val <= param(x1, y1) and val > param(x2, y2):
                        # OK
                        self.f2t[0, itr] = mesh1.f2t[0, jtr]
                        break
                    elif val < param(x1, y1) and val >= param(x2, y2):
                        # OK
                        self.f2t[0, itr] = mesh1.f2t[0, jtr]
                        break
            for jtr in mesh2.boundary_facets():
                fix1 = mesh2.facets[0, jtr]
                x1 = mesh2.p[0, fix1]
                y1 = mesh2.p[1, fix1]
                fix2 = mesh2.facets[1, jtr]
                x2 = mesh2.p[0, fix2]
                y2 = mesh2.p[1, fix2]
                if rule(x1, y1) > 0 or rule(x2, y2) > 0:
                    if val > param(x1, y1) and val < param(x2, y2):
                        # OK
                        self.f2t[1, itr] = mesh2.f2t[0, jtr] + nt1
                        break
                    elif val < param(x1, y1) and val > param(x2, y2):
                        # OK
                        self.f2t[1, itr] = mesh2.f2t[0, jtr] + nt1
                        break
                    elif val >= param(x1, y1) and val < param(x2, y2):
                        # OK
                        self.f2t[1, itr] = mesh2.f2t[0, jtr] + nt1
                        break
                    elif val > param(x1, y1) and val <= param(x2, y2):
                        # OK
                        self.f2t[1, itr] = mesh2.f2t[0, jtr] + nt1
                        break
                    elif val <= param(x1, y1) and val > param(x2, y2):
                        # OK
                        self.f2t[1, itr] = mesh2.f2t[0, jtr] + nt1
                        break
                    elif val < param(x1, y1) and val >= param(x2, y2):
                        # OK
                        self.f2t[1, itr] = mesh2.f2t[0, jtr] + nt1
                        break
        if (self.f2t>-1).all():
            self.f2t[0, :]
            return
        else:
            print(self.f2t)
            raise Exception("All mesh facets corresponding to mortar facets not found!")

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一维网格

class MeshLine(Mesh):
    """One-dimensional mesh."""

    refdom = "line"
    brefdom = "point"

    p = np.array([])
    t = np.array([])

5.1

    def __init__(self, p=None, t=None, validate=True):
        if p is None and t is None:
            p = np.array([[0, 1]])
            t = np.array([[0], [1]])
        elif p is not None and t is None:
            t = np.array([np.arange(np.max(p.shape)-1), np.arange(np.max(p.shape)-1)+1])
        if len(p.shape)==1:
            p = np.array([p]) 
        self.p = p
        self.t = t
        if validate:
            self._validate()
        super(MeshLine, self).__init__()

5.2

    @classmethod
    def init_refdom(cls):
        """Initialize a mesh constisting of the reference interval [0,1]."""
        return cls()

5.3

    def adaptive_refine(self, marked):
        """Perform an adaptive refine which splits each marked element into two."""
        t = self.t
        p = self.p

        mid = range(len(marked)) + np.max(t) + 1

        nonmarked = np.setdiff1d(np.arange(t.shape[1]), marked)

        newp = np.hstack((p, 0.5*(p[:, self.t[0, marked]] + p[:, self.t[1, marked]])))
        newt = np.vstack((t[0, marked], mid))
        newt = np.hstack((t[:, nonmarked], newt, np.vstack((mid, t[1, marked]))))
        # update fields
        self.p = newp
        self.t = newt

5.4

    def _uniform_refine(self):
        """Perform a single mesh refine that halves 'h'."""
        # rename variables
        t = self.t
        p = self.p

        mid = range(self.t.shape[1]) + np.max(t) + 1
        # new vertices and elements
        newp = np.hstack((p, 0.5*(p[:, self.t[0, :]] + p[:, self.t[1, :]])))
        newt = np.vstack((t[0, :], mid))
        newt = np.hstack((newt, np.vstack((mid, t[1, :]))))
        # update fields
        self.p = newp
        self.t = newt

5.5

        # TODO implement prolongation

    def boundary_nodes(self):
        """Find the boundary nodes of the mesh."""
        _, counts = np.unique(self.t.flatten(), return_counts=True)
        return np.nonzero(counts == 1)[0]

5.6

    def interior_nodes(self):
        """Find the interior nodes of the mesh."""
        _, counts = np.unique(self.t.flatten(), return_counts=True)
        return np.nonzero(counts == 2)[0]

5.7

    def plot(self, u, ax=None, color='ko-'):
        """Plot a function defined at the nodes of the mesh."""
        if ax is None:
            # create new figure
            fig = plt.figure()
            ax = fig.add_subplot(111)
        xs = []
        ys = []
        for y1, y2, s, t in zip(u[self.t[0, :]],
                                u[self.t[1, :]],
                                self.p[0, self.t[0, :]],
                                self.p[0, self.t[1, :]]):
            xs.append(s)
            xs.append(t)
            xs.append(None)
            ys.append(y1)
            ys.append(y2)
            ys.append(None)
        ax.plot(xs, ys, color)

        return ax

5.8

    def __mul__(self, other):
        """Tensor product mesh."""
        return MeshQuad.init_tensor(self.p[0, :], other.p[0, :])

5.9

    def param(self):
        return np.max(np.abs(self.p[0, self.t[1, :]] - self.p[0, self.t[0, :]]))

5.10

    def mapping(self):
        return skfem.mapping.MappingAffine(self)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

矩形剖分

class MeshQuad(Mesh2D):
    """A mesh consisting of quadrilateral elements.
    
    Attributes
    ----------
    p : numpy array of size 2 x Nvertices
        The vertices of the mesh
    t : numpy array of size 4 x Nelements
        The element connectivity
    facets : numpy array of size 2 x Nfacets
        Each column contains a pair of indices to p.
    f2t : numpy array of size 2 x Nfacets
        Each column contains a pair of indices to t
        or -1 on the second row if the facet is on
        the boundary.
    t2f : numpy array of size 4 x Nelements
        Each column contains four indices to facets.
    """

    refdom = "quad"
    brefdom = "line"
    meshio_type = "quad"

    p = np.array([])
    t = np.array([])
    facets = np.array([])
    f2t = np.array([])
    t2f = np.array([])

6.1

    def __init__(self, p=None, t=None, validate=True):
        """Initialize a quadrilateral mesh.

        Parameters
        ----------
        p : (optional) numpy array of size 2 x Nvertices
            The points of the mesh.
        t : (optional) numpy array of size 4 x Nelements
            The element connectivity, i.e. indices to p.
            These should be in counter-clockwise order.
        """
        if p is None and t is None:
            p = np.array([[0., 0.], [1., 0.], [1., 1.], [0., 1.]]).T
            t = np.array([[0, 1, 2, 3]]).T
        elif p is None or t is None:
            raise Exception("Must provide p AND t or neither")
        self.p = p
        self.t = t
        if validate:
            self._validate()
        self._build_mappings()
        super(MeshQuad, self).__init__()

6.2

    @classmethod
    def init_tensor(cls, x, y):
        """Initialize a tensor product mesh.

        Parameters
        ----------
        x : numpy array (1d)
            The nodal coordinates in dimension x
        y : numpy array (1d)
            The nodal coordinates in dimension y
        """
        npx = len(x)
        npy = len(y)
        X, Y = np.meshgrid(np.sort(x), np.sort(y))   
        p = np.vstack((X.flatten('F'), Y.flatten('F')))
        ix = np.arange(npx*npy)
        ne = (npx-1)*(npy-1)
        t = np.zeros((4, ne))
        ix = ix.reshape(npy, npx, order='F').copy()
        t[0, :] = ix[0:(npy-1), 0:(npx-1)].reshape(ne, 1, order='F').copy().flatten()
        t[1, :] = ix[1:npy, 0:(npx-1)].reshape(ne, 1, order='F').copy().flatten()
        t[2, :] = ix[1:npy, 1:npx].reshape(ne, 1, order='F').copy().flatten()
        t[3, :] = ix[0:(npy-1), 1:npx].reshape(ne, 1, order='F').copy().flatten()
        return cls(p, t.astype(np.int64))

6.3

    @classmethod
    def init_refdom(cls):
        """Initialize a mesh that includes only the reference quad.
        
        The mesh topology is as follows:
         (-1,1) *-------------* (1,1)
                |             |
                |             |
                |             |
                |             | 
                |             | 
                |             |
                |             |  
        (-1,-1) *-------------* (1,-1)
        """
        p = np.array([[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]]).T
        t = np.array([[0, 1, 2, 3]]).T
        return cls(p, t)

6.4

    def _build_mappings(self):
        # do not sort since order defines counterclockwise order
        # self.t=np.sort(self.t,axis=0)

        # define facets: in the order (0,1) (1,2) (2,3) (0,3)
        self.facets = np.sort(np.vstack((self.t[0, :], self.t[1, :])), axis=0)
        self.facets = np.hstack((self.facets,
                                 np.sort(np.vstack((self.t[1, :],
                                                    self.t[2, :])), axis=0)))
        self.facets = np.hstack((self.facets,
                                 np.sort(np.vstack((self.t[2, :],
                                                    self.t[3, :])), axis=0)))
        self.facets = np.hstack((self.facets,
                                 np.sort(np.vstack((self.t[0, :],
                                                    self.t[3, :])), axis=0)))

        # get unique facets and build quad-to-facet mapping: 4 (edges) x Nquads
        tmp = np.ascontiguousarray(self.facets.T)
        tmp, ixa, ixb = np.unique(tmp.view([('', tmp.dtype)]*tmp.shape[1]),
                                  return_index=True, return_inverse=True)
        self.facets = self.facets[:, ixa]
        self.t2f = ixb.reshape((4, self.t.shape[1]))

        # build facet-to-quadrilateral mapping: 2 (quads) x Nedges
        e_tmp = np.hstack((self.t2f[0, :],
                           self.t2f[1, :],
                           self.t2f[2, :],
                           self.t2f[3, :]))
        t_tmp = np.tile(np.arange(self.t.shape[1]), (1, 4))[0]

        e_first, ix_first = np.unique(e_tmp, return_index=True)
        # this emulates matlab unique(e_tmp,'last')
        e_last, ix_last = np.unique(e_tmp[::-1], return_index=True)
        ix_last = e_tmp.shape[0] - ix_last - 1

        self.f2t = np.zeros((2, self.facets.shape[1]), dtype=np.int64)
        self.f2t[0, e_first] = t_tmp[ix_first]
        self.f2t[1, e_last] = t_tmp[ix_last]

        # second row to -1 if repeated (i.e., on boundary)
        self.f2t[1, np.nonzero(self.f2t[0, :] == self.f2t[1, :])[0]] = -1

6.5

    def _uniform_refine(self):
        """Perform a single mesh refine that halves 'h'.

        Each quadrilateral is split into four.
        """
        # rename variables
        t = self.t
        p = self.p
        e = self.facets
        sz = p.shape[1]
        t2f = self.t2f + sz
        # quadrilateral middle point
        mid = range(self.t.shape[1]) + np.max(t2f) + 1
        # new vertices are the midpoints of edges ...
        newp1 = 0.5*np.vstack((p[0, e[0, :]] + p[0, e[1, :]],
                               p[1, e[0, :]] + p[1, e[1, :]]))
        # ... and element middle points
        newp2 = 0.25*np.vstack((p[0, t[0, :]] + p[0, t[1, :]] +
                                p[0, t[2, :]] + p[0, t[3, :]],
                                p[1, t[0, :]] + p[1, t[1, :]] +
                                p[1, t[2, :]] + p[1, t[3, :]]))
        newp = np.hstack((p, newp1, newp2))
        # build new quadrilateral definitions
        newt = np.vstack((t[0, :],
                          t2f[0, :],
                          mid,
                          t2f[3, :]))
        newt = np.hstack((newt, np.vstack((t2f[0, :],
                                           t[1, :],
                                           t2f[1, :],
                                           mid))))
        newt = np.hstack((newt, np.vstack((mid,
                                           t2f[1, :],
                                           t[2, :],
                                           t2f[2, :]))))
        newt = np.hstack((newt, np.vstack((t2f[3, :],
                                           mid,
                                           t2f[2, :],
                                           t[3, :]))))
        # update fields
        self.p = newp
        self.t = newt

        self._build_mappings()

6.6

        # TODO implement prolongation

    def _splitquads(self, x=None):
        """Split each quad into two triangles and return MeshTri."""
        t = self.t[[0, 1, 3], :]
        t = np.hstack((t, self.t[[1, 2, 3]]))

        if x is not None:
            if len(x) == self.t.shape[1]:
                # preserve elemental constant functions
                X = np.concatenate((x, x))
            else:
                raise Exception("The parameter x must have one value per element.")
            return MeshTri(self.p, t, validate=False), X
        else:
            return MeshTri(self.p, t, validate=False)

6.7

    def _splitquads_symmetric(self):
        """Split quads into four triangles."""
        t = np.vstack((self.t, np.arange(self.t.shape[1]) + self.p.shape[1]))
        newt = t[[0, 1, 4], :]
        newt = np.hstack((newt, t[[1, 2, 4], :]))
        newt = np.hstack((newt, t[[2, 3, 4], :]))
        newt = np.hstack((newt, t[[3, 0, 4], :]))
        mx = np.sum(self.p[0, self.t], axis=0)/self.t.shape[0]
        my = np.sum(self.p[1, self.t], axis=0)/self.t.shape[0]
        return MeshTri(np.hstack((self.p, np.vstack((mx, my)))), newt, validate=False)

6.8

    def plot(self, z, smooth=False, edgecolors=None, ax=None, zlim=None):
        """Visualize nodal or elemental function (2d).

        The quadrilateral mesh is split into triangular mesh (MeshTri) and
        the respective plotting function for the triangular mesh is used.
        """
        m, z = self._splitquads(z)
        return m.plot(z, smooth, ax=ax, zlim=zlim, edgecolors=edgecolors)

6.9

    def plot3(self, z, smooth=False, ax=None):
        """Visualize nodal function (3d i.e. three axes).

        The quadrilateral mesh is split into triangular mesh (MeshTri) and
        the respective plotting function for the triangular mesh is used.
        """
        m, z = self._splitquads(z)
        return m.plot3(z, smooth, ax=ax)

6.10

    def mapping(self):
        return skfem.mapping.MappingIsoparametric(self, skfem.element.ElementQ1())

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

六面体剖分

class MeshHex(Mesh3D):
    """A mesh consisting of hexahedral elements.

    Attributes
    ----------
    p : numpy array of size 3 x Nvertices
        The vertices of the mesh. Each column corresponds to a point.
    t : numpy array of size 8 x Nelements
        The element connectivity. Each column corresponds to a element
        and contains eight column indices to MeshHex.p.
    facets : numpy array of size 4 x Nfacets
        Each column contains four column indices to MeshHex.p.
    f2t : numpy array of size 2 x Nfacets
        Each column contains a pair of column indices to MeshHex.t
        or -1 on the second row if the corresponding
        facet is located on the boundary.
    t2f : numpy array of size 6 x Nelements
        Each column contains four indices to MeshHex.facets.
    edges : numpy array of size 2 x Nedges
        Each column corresponds to an edge and contains two indices to
        MeshHex.p.
    t2e : numpy array of size 12 x Nelements
        Each column contains twelve column indices of MeshHex.edges.
    """

    refdom = "hex"
    brefdom = "quad"
    meshio_type = "hexahedron"

7.1

    def __init__(self, p=None, t=None, validate=True):
        if p is None and t is None:
            p = np.array([[0., 0., 0.], [0., 0., 1.], [0., 1., 0.], [1., 0., 0.],
                          [0., 1., 1.], [1., 0., 1.], [1., 1., 0.], [1., 1., 1.]]).T
            t = np.array([[0, 1, 2, 3, 4, 5, 6, 7]]).T
        elif p is None or t is None:
            raise Exception("Must provide p AND t or neither")
        #
        # TODO fix orientation if p and t is provided. refer to
        # the default mesh for correct orientation
        #
        #   2---6
        #  /   /|
        # 4---7 3
        # |   |/
        # 1---5
        #
        # The hidden node is 0.
        #
        self.p = p
        self.t = t
        if validate:
            self._validate()
        self._build_mappings()
        super(MeshHex, self).__init__()

7.2

    @classmethod
    def init_tensor(cls, x, y, z):
        """Initialize a tensor product mesh.

        Parameters
        ----------
        x : numpy array (1d)
            The nodal coordinates in dimension x
        y : numpy array (1d)
            The nodal coordinates in dimension y
        z : numpy array (1d)
            The nodal coordinates in dimension z
        """
        npx = len(x)
        npy = len(y)
        npz = len(z)
        X, Y, Z = np.meshgrid(np.sort(x), np.sort(y), np.sort(z))   
        p = np.vstack((X.flatten('F'), Y.flatten('F'), Z.flatten('F')))
        ix = np.arange(npx*npy*npz)
        ne = (npx-1)*(npy-1)*(npz-1)
        t = np.zeros((8, ne))
        ix = ix.reshape(npy, npx, npz, order='F').copy()
        t[0, :] = ix[0:(npy-1), 0:(npx-1), 0:(npz-1)].reshape(ne, 1, order='F').copy().flatten()
        t[1, :] = ix[1:npy, 0:(npx-1), 0:(npz-1)].reshape(ne, 1, order='F').copy().flatten()
        t[2, :] = ix[0:(npy-1), 1:npx, 0:(npz-1)].reshape(ne, 1, order='F').copy().flatten()
        t[3, :] = ix[0:(npy-1), 0:(npx-1), 1:npz].reshape(ne, 1, order='F').copy().flatten()
        t[4, :] = ix[1:npy, 1:npx, 0:(npz-1)].reshape(ne, 1, order='F').copy().flatten()
        t[5, :] = ix[1:npy, 0:(npx-1), 1:npz].reshape(ne, 1, order='F').copy().flatten()
        t[6, :] = ix[0:(npy-1), 1:npx, 1:npz].reshape(ne, 1, order='F').copy().flatten()
        t[7, :] = ix[1:npy, 1:npx, 1:npz].reshape(ne, 1, order='F').copy().flatten()
        return cls(p, t.astype(np.int64))

7.3

    def _build_mappings(self):
        """Build element-to-facet, element-to-edges, etc. mappings."""
        self.edges = np.sort(np.vstack((self.t[0, :], self.t[1, :])), axis=0)
        e = np.array([0, 2,
                      0, 3,
                      1, 4,
                      1, 5,
                      2, 4,
                      2, 6,
                      3, 5,
                      3, 6,
                      4, 7,
                      5, 7,
                      6, 7]) # see the picture in init
        for i in range(11):
            self.edges = np.hstack((self.edges, np.sort(np.vstack((self.t[e[2*i], :],
                                                                   self.t[e[2*i+1], :])), axis=0)))

        # unique edges
        self.edges, ixa, ixb = np.unique(self.edges, axis=1, return_index=True, return_inverse=True)
        self.edges = np.ascontiguousarray(self.edges)

        self.t2e = ixb.reshape((12, self.t.shape[1]))

        # define facets
        self.facets = np.sort(np.vstack((self.t[0, :],
                                         self.t[1, :],
                                         self.t[4, :],
                                         self.t[2, :])), axis=0)
        f = np.array([0, 3, 6, 2,
                      0, 1, 5, 3,
                      2, 6, 7, 4,
                      1, 5, 7, 4,
                      3, 5, 7, 6])
        for i in range(5):
            self.facets = np.hstack((self.facets, np.sort(np.vstack((self.t[f[4*i], :],
                                                                     self.t[f[4*i+1], :],
                                                                     self.t[f[4*i+2], :],
                                                                     self.t[f[4*i+3], :])), axis=0)))


        # unique facets
        self.facets, ixa, ixb = np.unique(self.facets, axis=1, return_index=True, return_inverse=True)
        self.facets = np.ascontiguousarray(self.facets)

        self.t2f = ixb.reshape((6, self.t.shape[1]))

        # build facet-to-hexa mapping: 2 (hexes) x Nfacets
        e_tmp = np.hstack((self.t2f[0, :], self.t2f[1, :],
                           self.t2f[2, :], self.t2f[3, :],
                           self.t2f[4, :], self.t2f[5, :]))
        t_tmp = np.tile(np.arange(self.t.shape[1]), (1, 6))[0]

        e_first, ix_first = np.unique(e_tmp, return_index=True)
        e_last, ix_last = np.unique(e_tmp[::-1], return_index=True)
        ix_last = e_tmp.shape[0] - ix_last - 1

        self.f2t = np.zeros((2, self.facets.shape[1]), dtype=np.int64)
        self.f2t[0, e_first] = t_tmp[ix_first]
        self.f2t[1, e_last] = t_tmp[ix_last]

        ## second row to zero if repeated (i.e., on boundary)
        self.f2t[1, np.nonzero(self.f2t[0, :] == self.f2t[1, :])[0]] = -1

7.4

    def _uniform_refine(self):
        """Perform a single mesh refine that halves 'h'.

        Each hex is split into 8."""
        # rename variables
        t = self.t
        p = self.p
        e = self.edges
        f = self.facets
        sz = p.shape[1]
        t2e = self.t2e + sz
        t2f = self.t2f + np.max(t2e) + 1
        # hex middle point
        mid = range(self.t.shape[1]) + np.max(t2f) + 1
        # new vertices are the midpoints of edges ...
        newp1 = 0.5*np.vstack((p[0, e[0, :]] + p[0, e[1, :]],
                               p[1, e[0, :]] + p[1, e[1, :]],
                               p[2, e[0, :]] + p[2, e[1, :]]))
        # ... midpoints of facets ...
        newp2 = 0.25*np.vstack((p[0, f[0, :]] + p[0, f[1, :]] +
                                p[0, f[2, :]] + p[0, f[3, :]],
                                p[1, f[0, :]] + p[1, f[1, :]] +
                                p[1, f[2, :]] + p[1, f[3, :]],
                                p[2, f[0, :]] + p[2, f[1, :]] +
                                p[2, f[2, :]] + p[2, f[3, :]]))
        # ... and element middle points
        newp3 = 0.125*np.vstack((p[0, t[0, :]] + p[0, t[1, :]] +
                                 p[0, t[2, :]] + p[0, t[3, :]] +
                                 p[0, t[4, :]] + p[0, t[5, :]] +
                                 p[0, t[6, :]] + p[0, t[7, :]],
                                 p[1, t[0, :]] + p[1, t[1, :]] +
                                 p[1, t[2, :]] + p[1, t[3, :]] +
                                 p[1, t[4, :]] + p[1, t[5, :]] +
                                 p[1, t[6, :]] + p[1, t[7, :]],
                                 p[2, t[0, :]] + p[2, t[1, :]] +
                                 p[2, t[2, :]] + p[2, t[3, :]] +
                                 p[2, t[4, :]] + p[2, t[5, :]] +
                                 p[2, t[6, :]] + p[2, t[7, :]]))
        newp = np.hstack((p, newp1, newp2, newp3))
        # build new hex indexing (this requires some serious meditation)
        newt = np.vstack((t[0, :],
                          t2e[0, :],
                          t2e[1, :],
                          t2e[2, :],
                          t2f[0, :],
                          t2f[2, :],
                          t2f[1, :],
                          mid))
        newt = np.hstack((newt, np.vstack((t2e[0, :],
                                           t[1, :],
                                           t2f[0, :],
                                           t2f[2, :],
                                           t2e[3, :],
                                           t2e[4, :],
                                           mid,
                                           t2f[4, :]))))
        newt = np.hstack((newt, np.vstack((t2e[1, :],
                                           t2f[0, :],
                                           t[2, :],
                                           t2f[1, :],
                                           t2e[5, :],
                                           mid,
                                           t2e[6, :],
                                           t2f[3, :]
                                           ))))
        newt = np.hstack((newt, np.vstack((t2e[2, :],
                                           t2f[2, :],
                                           t2f[1, :],
                                           t[3, :],
                                           mid,
                                           t2e[7, :],
                                           t2e[8, :],
                                           t2f[5, :]
                                           ))))
        newt = np.hstack((newt, np.vstack((t2f[0, :],
                                           t2e[3, :],
                                           t2e[5, :],
                                           mid,
                                           t[4, :],
                                           t2f[4, :],
                                           t2f[3, :],
                                           t2e[9, :]
                                           ))))
        newt = np.hstack((newt, np.vstack((t2f[2, :],
                                           t2e[4, :],
                                           mid,
                                           t2e[7, :],
                                           t2f[4, :],
                                           t[5, :],
                                           t2f[5, :],
                                           t2e[10, :],
                                           ))))
        newt = np.hstack((newt, np.vstack((t2f[1, :],
                                           mid,
                                           t2e[6, :],
                                           t2e[8, :],
                                           t2f[3, :],
                                           t2f[5, :],
                                           t[6, :],
                                           t2e[11, :]
                                           ))))
        newt = np.hstack((newt, np.vstack((mid,
                                           t2f[4, :],
                                           t2f[3, :],
                                           t2f[5, :],
                                           t2e[9, :],
                                           t2e[10, :],
                                           t2e[11, :],
                                           t[7, :]
                                           ))))
        # update fields
        self.p = newp
        self.t = newt

        self._build_mappings()

7.5

        # TODO implement prolongation

    def save(self, filename, pointData=None, cellData=None):
        """Export the mesh and fields using meshio. (Hexahedron version.)

        Parameters
        ----------
        filename : string
            The filename for vtk-file.
        pointData : (optional) numpy array for one output or dict for multiple
        cellData : (optional) numpy array for one output or dict for multiple
        """
        import meshio

        # vtk requires a different ordering
        t = self.t[[0, 3, 6, 2, 1, 5, 7, 4], :]

        if pointData is not None:
            if type(pointData) != dict:
                pointData = {'0':pointData}

        if cellData is not None:
            if type(cellData) != dict:
                cellData = {'0':cellData}

        cells = { 'hexahedron' : t.T }
        mesh = meshio.Mesh(self.p.T, cells, pointData, cellData)
        meshio.write(filename, mesh)

7.6

    def mapping(self):
        return skfem.mapping.MappingIsoparametric(self, skfem.element.ElementHex1())

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

四边体剖分

class MeshTet(Mesh3D):
    """A mesh consisting of tetrahedral elements.

    Attributes
    ----------
    p : numpy array of size 3 x Nvertices
        The vertices of the mesh. Each column corresponds to a point.
    t : numpy array of size 4 x Nelements
        The element connectivity. Each column corresponds to a element
        and contains four column indices to MeshTet.p.
    facets : numpy array of size 3 x Nfacets
        Each column contains a triplet of column indices to MeshTet.p.
        Order: (0, 1, 2) (0, 1, 3) (0, 2, 3) (1, 2, 3)
    f2t : numpy array of size 2 x Nfacets
        Each column contains a pair of column indices to MeshTet.t
        or -1 on the second row if the facet is located on the boundary.
    t2f : numpy array of size 4 x Nelements
        Each column contains four indices to MeshTet.facets.
    edges : numpy array of size 2 x Nedges
        Each column corresponds to an edge and contains two indices to
        MeshTet.p.
        Order: (0, 1) (1, 2) (0, 2) (0, 3) (1, 3) (2, 3)
    t2e : numpy array of size 6 x Nelements
        Each column contains six indices to MeshTet.edges.

    Examples
    --------

    Read a tetrahedral mesh from gmsh-format using meshio.

    >>> m = MeshTet.load('examples/box.msh')
    >>> m.p.shape
    (3, 358)
    """

    refdom = "tet"
    brefdom = "tri"
    meshio_type = "tetra"

8.1

    def __init__(self, p=None, t=None, validate=True):
        if p is None and t is None:
            p = np.array([[0., 0., 0.], [0., 0., 1.], [0., 1., 0.], [1., 0., 0.],
                          [0., 1., 1.], [1., 0., 1.], [1., 1., 0.], [1., 1., 1.]]).T
            t = np.array([[0, 1, 2, 3], [3, 5, 1, 7], [2, 3, 6, 7],
                          [2, 3, 1, 7], [1, 2, 4, 7]]).T
        elif p is None or t is None:
            raise Exception("Must provide p AND t or neither")
        self.p = p
        self.t = t
        if validate:
            self._validate()
        self.enable_facets = True
        self._build_mappings()
        super(MeshTet, self).__init__()

8.2

    def _build_mappings(self):
        """Build element-to-facet, element-to-edges, etc. mappings."""
        # define edges: in the order (0,1) (1,2) (0,2) (0,3) (1,3) (2,3)
        self.edges = np.sort(np.vstack((self.t[0, :], self.t[1, :])), axis=0)
        e = np.array([1, 2,
                      0, 2,
                      0, 3,
                      1, 3,
                      2, 3])
        for i in range(5):
            self.edges = np.hstack((self.edges,
                                    np.sort(np.vstack((self.t[e[2*i], :],
                                                       self.t[e[2*i+1], :])),
                                            axis=0)))

        # unique edges
        self.edges, ixa, ixb = np.unique(self.edges, axis=1, return_index=True, return_inverse=True)
        self.edges = np.ascontiguousarray(self.edges)

        self.t2e = ixb.reshape((6, self.t.shape[1]))

        # define facets
        if self.enable_facets:
            self.facets = np.sort(np.vstack((self.t[0, :],
                                             self.t[1, :],
                                             self.t[2, :])), axis=0)
            f = np.array([0, 1, 3,
                          0, 2, 3,
                          1, 2, 3])
            for i in range(3):
                self.facets = np.hstack((self.facets,
                                         np.sort(np.vstack((self.t[f[2*i], :],
                                                            self.t[f[2*i+1], :],
                                                            self.t[f[2*i+2]])),
                                                 axis=0)))

            # unique facets
            self.facets, ixa, ixb = np.unique(self.facets, axis=1, return_index=True, return_inverse=True)
            self.facets = np.ascontiguousarray(self.facets)

            self.t2f = ixb.reshape((4, self.t.shape[1]))

            # build facet-to-tetra mapping: 2 (tets) x Nfacets
            e_tmp = np.hstack((self.t2f[0, :], self.t2f[1, :],
                               self.t2f[2, :], self.t2f[3, :]))
            t_tmp = np.tile(np.arange(self.t.shape[1]), (1, 4))[0]

            e_first, ix_first = np.unique(e_tmp, return_index=True)
            # this emulates matlab unique(e_tmp,'last')
            e_last, ix_last = np.unique(e_tmp[::-1], return_index=True)
            ix_last = e_tmp.shape[0] - ix_last-1

            self.f2t = np.zeros((2, self.facets.shape[1]), dtype=np.int64)
            self.f2t[0, e_first] = t_tmp[ix_first]
            self.f2t[1, e_last] = t_tmp[ix_last]

            # second row to zero if repeated (i.e., on boundary)
            self.f2t[1, np.nonzero(self.f2t[0, :] == self.f2t[1, :])[0]] = -1

8.3

    def refine(self, N=None):
        """Refine the mesh, tetrahedral optimization.
        
        Parameters
        ----------
        N : (optional) int
            Perform N refinements.
        """
        if N is None:
            return self._uniform_refine()
        else:
            self.enable_facets = False
            for itr in range(N-1):
                self._uniform_refine()
            self.enable_facets = True
            self._uniform_refine()

8.4

    def _uniform_refine(self):
        """Perform a single mesh refine.

        Let the nodes of a tetrahedron be numbered as 0, 1, 2 and 3.
        It is assumed that the edges in self.t2e are given in the order

          I=(0,1), II=(1,2), III=(0,2), IV=(0,3), V=(1,3), VI=(2,3)

        by self._build_mappings(). Let I denote the midpoint of the edge
        (0,1), II denote the midpoint of the edge (1,2), etc. Then each
        tetrahedron is split into eight smaller subtetrahedra as follows.

        The first four subtetrahedra have the following nodes:

          1. (0,I,III,IV)
          2. (1,I,II,V)
          3. (2,II,III,VI)
          4. (3,IV,V,VI)

        The remaining middle-portion of the original tetrahedron consists
        of a union of two mirrored pyramids. This bi-pyramid can be splitted
        into four tetrahedra in a three different ways by connecting the
        midpoints of two opposing edges (there are three different pairs
        of opposite edges).

        For each tetrahedra in the original mesh, we split the bi-pyramid
        in such a way that the connection between the opposite edges
        is shortest. This minimizes the shape-regularity constant of
        the resulting mesh family.
        """
        # rename variables
        t = self.t
        p = self.p
        e = self.edges
        sz = p.shape[1]
        t2e = self.t2e + sz
        # new vertices are the midpoints of edges
        newp = 0.5*np.vstack((p[0, e[0, :]] + p[0, e[1, :]],
                              p[1, e[0, :]] + p[1, e[1, :]],
                              p[2, e[0, :]] + p[2, e[1, :]]))
        newp = np.hstack((p, newp))
        # new tets
        newt = np.vstack((t[0, :], t2e[0, :], t2e[2, :], t2e[3, :]))
        newt = np.hstack((newt, np.vstack((t[1, :], t2e[0, :], t2e[1, :], t2e[4, :]))))
        newt = np.hstack((newt, np.vstack((t[2, :], t2e[1, :], t2e[2, :], t2e[5, :]))))
        newt = np.hstack((newt, np.vstack((t[3, :], t2e[3, :], t2e[4, :], t2e[5, :]))))
        # compute middle pyramid diagonal lengths and choose shortest
        d1 = ((newp[0, t2e[2, :]] - newp[0, t2e[4, :]])**2 +
              (newp[1, t2e[2, :]] - newp[1, t2e[4, :]])**2)
        d2 = ((newp[0, t2e[1, :]] - newp[0, t2e[3, :]])**2 +
              (newp[1, t2e[1, :]] - newp[1, t2e[3, :]])**2)
        d3 = ((newp[0, t2e[0, :]] - newp[0, t2e[5, :]])**2 +
              (newp[1, t2e[0, :]] - newp[1, t2e[5, :]])**2)
        I1 = d1 < d2
        I2 = d1 < d3
        I3 = d2 < d3
        c1 = I1*I2
        c2 = (~I1)*I3
        c3 = (~I2)*(~I3)
        # splitting the pyramid in the middle.
        # diagonals are [2,4], [1,3] and [0,5]
        # CASE 1: diagonal [2,4]
        newt = np.hstack((newt, np.vstack((t2e[2, c1], t2e[4, c1],
                                           t2e[0, c1], t2e[1, c1]))))
        newt = np.hstack((newt, np.vstack((t2e[2, c1], t2e[4, c1],
                                           t2e[0, c1], t2e[3, c1]))))
        newt = np.hstack((newt, np.vstack((t2e[2, c1], t2e[4, c1],
                                           t2e[1, c1], t2e[5, c1]))))
        newt = np.hstack((newt, np.vstack((t2e[2, c1], t2e[4, c1],
                                           t2e[3, c1], t2e[5, c1]))))
        # CASE 2: diagonal [1,3]
        newt = np.hstack((newt, np.vstack((t2e[1, c2], t2e[3, c2],
                                           t2e[0, c2], t2e[4, c2]))))
        newt = np.hstack((newt, np.vstack((t2e[1, c2], t2e[3, c2],
                                           t2e[4, c2], t2e[5, c2]))))
        newt = np.hstack((newt, np.vstack((t2e[1, c2], t2e[3, c2],
                                           t2e[5, c2], t2e[2, c2]))))
        newt = np.hstack((newt, np.vstack((t2e[1, c2], t2e[3, c2],
                                           t2e[2, c2], t2e[0, c2]))))
        # CASE 3: diagonal [0,5]
        newt = np.hstack((newt, np.vstack((t2e[0, c3], t2e[5, c3],
                                           t2e[1, c3], t2e[4, c3]))))
        newt = np.hstack((newt, np.vstack((t2e[0, c3], t2e[5, c3],
                                           t2e[4, c3], t2e[3, c3]))))
        newt = np.hstack((newt, np.vstack((t2e[0, c3], t2e[5, c3],
                                           t2e[3, c3], t2e[2, c3]))))
        newt = np.hstack((newt, np.vstack((t2e[0, c3], t2e[5, c3],
                                           t2e[2, c3], t2e[1, c3]))))
        # update fields
        self.p = newp
        self.t = newt

        self._build_mappings()

8.5

        # TODO implement prolongation matrix

    def shapereg(self):
        """Return the largest shape-regularity constant."""
        def edgelen(n):
            return np.sqrt(np.sum((self.p[:, self.edges[0, self.t2e[n, :]]] -
                                   self.p[:, self.edges[1, self.t2e[n, :]]])**2,
                                  axis=0))
        edgelenmat = np.vstack(tuple(edgelen(i) for i in range(6)))
        return np.max(np.max(edgelenmat, axis=0)/np.min(edgelenmat, axis=0))

8.6

    def mapping(self):
        return skfem.mapping.MappingAffine(self)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

三角剖分

class MeshTri(Mesh2D):
    """A mesh consisting of triangular elements.
    
    Attributes
    ----------
    p : numpy array of size 2 x Nvertices
        The vertices of the mesh
    t : numpy array of size 3 x Nelements
        The element connectivity
    facets : numpy array of size 2 x Nfacets
        Each column contains a pair of indices to p.
    f2t : numpy array of size 2 x Nfacets
        Each column contains a pair of indices to t
        or -1 on the second row if the facet is on
        the boundary.
    t2f : numpy array of size 3 x Nelements
        Each column contains three indices to facets.

    Examples
    --------

    Initialize a symmetric mesh of the unit square.

    >>> m = MeshTri.init_sqsymmetric()
    >>> m.t.shape
    (3, 8)

    The different constructors are:
        * load (requires meshio)
        * init_symmetric
        * init_sqsymmetric
        * init_refdom

    Facets (edges) and mappings from triangles to facets and vice versa are
    automatically constructed. In the following example we have 5 facets
    (edges).

    >>> m = MeshTri()
    >>> m.facets
    array([[0, 0, 1, 1, 2],
           [1, 2, 2, 3, 3]])
    >>> m.t2f
    array([[0, 2],
           [2, 4],
           [1, 3]])
    >>> m.f2t
    array([[ 0,  0,  1,  1,  1],
           [-1, -1,  0, -1, -1]])

    The value -1 implies that the facet (the edge) is on the boundary.

    Refine the triangular mesh of the unit square three times.

    >>> m = MeshTri()
    >>> m.refine(3)
    >>> m.p.shape
    (2, 81)
    """

    refdom = "tri"
    brefdom = "line"
    meshio_type = "triangle"

    p = np.array([])
    t = np.array([])
    facets = np.array([])
    f2t = np.array([])
    t2f = np.array([])

9.1

    def __init__(self, p=None, t=None, validate=True, sort_t=True):
        """Initialize a triangular mesh.

        Parameters
        ----------
        p : (optional) numpy array of size 2 x Nvertices
            The points of the mesh.
        t : (optional) numpy array of size 3 x Nelements
            The element connectivity, i.e. indices to p.
        validate : (optional, default=True) bool
            Whether to run mesh validity checks or not.
        sort_t : (optional, default=True) bool
            Sort the element connectivity matrix before building mappings.
        """
        if p is None and t is None:
            p = np.array([[0., 1., 0., 1.], [0., 0., 1., 1.]], dtype=np.float_)
            t = np.array([[0, 1, 2], [1, 3, 2]], dtype=np.intp).T
        elif p is None or t is None:
            raise Exception("Must provide p AND t or neither")
        self.p = p
        self.t = t
        if validate:
            self._validate()
        self._build_mappings(sort_t=sort_t)
        super(MeshTri, self).__init__()

9.2

    @classmethod
    def init_tensor(cls, x, y):
        """Initialize a tensor product mesh.

        Parameters
        ----------
        x : numpy array (1d)
            The nodal coordinates in dimension x
        y : numpy array (1d)
            The nodal coordinates in dimension y
        """
        return MeshQuad.init_tensor(x, y)._splitquads()

9.3

    @classmethod
    def init_symmetric(cls):
        """Initialize a symmetric mesh of the unit square.
        
        The mesh topology is as follows:
        *------------*
        |          /|
        |        /  |
        |      /    |
        |     *      |
        |    /      |
        |  /        |
        |/          |
        *------------*
        """
        p = np.array([[0, 1, 1, 0, 0.5],
                      [0, 0, 1, 1, 0.5]], dtype=np.float_)
        t = np.array([[0, 1, 4],
                      [1, 2, 4],
                      [2, 3, 4],
                      [0, 3, 4]], dtype=np.intp).T
        return cls(p, t)

9.4

    @classmethod
    def init_sqsymmetric(cls):
        """Initialize a symmetric mesh of the unit square.
        
        The mesh topology is as follows:
        *------------*
        |    |     /|
        |    |   /  |
        |    | /    |
        |-----*------|
        |    /|     |
        |  /  |     |
        |/    |     |
        *------------*
        """
        p = np.array([[0, 0.5, 1,   0, 0.5,   1, 0, 0.5, 1],
                      [0, 0,   0, 0.5, 0.5, 0.5, 1,   1, 1]], dtype=np.float_)
        t = np.array([[0, 1, 4],
                      [1, 2, 4],
                      [2, 4, 5],
                      [0, 3, 4],
                      [3, 4, 6],
                      [4, 6, 7],
                      [4, 7, 8],
                      [4, 5, 8]], dtype=np.intp).T
        return cls(p, t)

9.5

    @classmethod
    def init_refdom(cls):
        """Initialize a mesh that includes only the reference triangle.
        
        The mesh topology is as follows:
        *
        |           
        |           
        |           
        |            
        |            
        |            
        |             
        *-------------*
        """
        p = np.array([[0., 1., 0.],
                      [0., 0., 1.]], dtype=np.float_)
        t = np.array([[0, 1, 2]], dtype=np.intp).T
        return cls(p, t)

9.6

    def _build_mappings(self, sort_t=True):
        # sort to preserve orientations etc.
        if sort_t:
            self.t = np.sort(self.t, axis=0)

        # define facets: in the order (0,1) (1,2) (0,2)
        self.facets = np.sort(np.vstack((self.t[0, :], self.t[1, :])), axis=0)
        self.facets = np.hstack((self.facets,
                                 np.sort(np.vstack((self.t[1, :], self.t[2, :])),
                                         axis=0)))
        self.facets = np.hstack((self.facets,
                                 np.sort(np.vstack((self.t[0, :], self.t[2, :])),
                                         axis=0)))

        # get unique facets and build triangle-to-facet
        # mapping: 3 (edges) x Ntris
        tmp = np.ascontiguousarray(self.facets.T)
        tmp, ixa, ixb = np.unique(tmp.view([('', tmp.dtype)] * tmp.shape[1]),
                                  return_index=True, return_inverse=True)
        self.facets = self.facets[:, ixa]
        self.t2f = ixb.reshape((3, self.t.shape[1]))

        # build facet-to-triangle mapping: 2 (triangles) x Nedges
        e_tmp = np.hstack((self.t2f[0, :], self.t2f[1, :], self.t2f[2, :]))
        t_tmp = np.tile(np.arange(self.t.shape[1]), (1, 3))[0]

        e_first, ix_first = np.unique(e_tmp, return_index=True)
        # this emulates matlab unique(e_tmp,'last')
        e_last, ix_last = np.unique(e_tmp[::-1], return_index=True)
        ix_last = e_tmp.shape[0] - ix_last - 1

        self.f2t = np.zeros((2, self.facets.shape[1]), dtype=np.int64)
        self.f2t[0, e_first] = t_tmp[ix_first]
        self.f2t[1, e_last] = t_tmp[ix_last]

        # second row to zero if repeated (i.e., on boundary)
        self.f2t[1, np.nonzero(self.f2t[0, :] == self.f2t[1, :])[0]] = -1

9.7

    def interpolator(self, x):
        """Return a function which interpolates values with P1 basis."""
        triang = mtri.Triangulation(self.p[0, :], self.p[1, :], self.t.T)
        interpf = mtri.LinearTriInterpolator(triang, x)
        # contruct an interpolator handle
        def handle(X, Y):
            return interpf(X, Y).data
        return handle

9.8

    def const_interpolator(self, x):
        """Return a function which interpolates values with P0 basis."""
        triang = mtri.Triangulation(self.p[0, :], self.p[1, :], self.t.T)
        finder = triang.get_trifinder()
        # construct an interpolator handle
        def handle(X, Y):
            return x[finder(X, Y)]
        return handle

9.9

    def smooth(self, k=1.0):
        """Apply smoothing to interior nodes."""
        from skfem.assembly import InteriorBasis, asm
        from skfem.element import ElementTriP1
        from skfem.utils import solve
        from skfem.models.poisson import laplace, mass

        e = ElementTriP1()
        ib = InteriorBasis(self, e)

        K = asm(laplace, ib)
        M = asm(mass, ib)

        I = self.interior_nodes()
        dx = - k*solve(M, K.dot(self.p[0, :]))
        dy = - k*solve(M, K.dot(self.p[1, :]))

        self.p[0, I] += dx[I]
        self.p[1, I] += dy[I]

9.10

    def draw_debug(self):
        """Draw without mesh.facets. For debugging self.draw()."""
        fig = plt.figure()
        plt.hold('on')
        for itr in range(self.t.shape[1]):
            plt.plot(self.p[0,self.t[[0,1],itr]], self.p[1,self.t[[0,1],itr]], 'k-')
            plt.plot(self.p[0,self.t[[1,2],itr]], self.p[1,self.t[[1,2],itr]], 'k-')
            plt.plot(self.p[0,self.t[[0,2],itr]], self.p[1,self.t[[0,2],itr]], 'k-')
        return fig

9.11

    def plot(self, z, smooth=False, ax=None, zlim=None, edgecolors=None):
        """Visualize nodal or elemental function (2d)."""
        if ax is None:
            fig = plt.figure()
            ax = fig.add_subplot(111)
        if edgecolors is None:
            edgecolors = 'k'
        if zlim == None:
            if smooth:
                ax.tripcolor(self.p[0, :], self.p[1, :], self.t.T, z,
                              shading='gouraud', edgecolors=edgecolors)
            else:
                ax.tripcolor(self.p[0, :], self.p[1, :], self.t.T, z, edgecolors=edgecolors)
        else:
            if smooth:
                ax.tripcolor(self.p[0, :], self.p[1, :], self.t.T, z,
                              shading='gouraud', vmin=zlim[0], vmax=zlim[1], edgecolors=edgecolors)
            else:
                ax.tripcolor(self.p[0, :], self.p[1, :], self.t.T, z,
                              vmin=zlim[0], vmax=zlim[1], edgecolors=edgecolors)
        return ax

9.12

    def plot3(self, z, smooth=False, ax=None):
        """Visualize nodal function (3d i.e. three axes)."""
        from mpl_toolkits.mplot3d import Axes3D
        if ax is None:
            fig = plt.figure()
            ax = Axes3D(fig)
        if len(z) == self.p.shape[1]:
            # use matplotlib
            ts = mtri.Triangulation(self.p[0, :], self.p[1, :], self.t.T)
            ax.plot_trisurf(self.p[0, :], self.p[1, :], z,
                            triangles=ts.triangles,
                            cmap=plt.cm.Spectral)
        elif len(z) == self.t.shape[1]:
            # one value per element (piecewise const)
            nt = self.t.shape[1]
            newt = np.arange(3*nt, dtype=np.int64).reshape((nt, 3))
            newpx = self.p[0, self.t].flatten(order='F')
            newpy = self.p[1, self.t].flatten(order='F')
            newz = np.vstack((z, z, z)).flatten(order='F')
            ts = mtri.Triangulation(newpx, newpx, newt)
            ax.plot_trisurf(newpx, newpy, newz,
                            triangles=ts.triangles,
                            cmap=plt.cm.Spectral)
        elif len(z) == 3*self.t.shape[1]:
            # three values per element (piecewise linear)
            nt = self.t.shape[1]
            newt = np.arange(3*nt, dtype=np.int64).reshape((nt, 3))
            newpx = self.p[0, self.t].flatten(order='F')
            newpy = self.p[1, self.t].flatten(order='F')
            ts = mtri.Triangulation(newpx, newpx, newt)
            ax.plot_trisurf(newpx, newpy, z,
                            triangles=ts.triangles,
                            cmap=plt.cm.Spectral)
        else:
            raise NotImplementedError("MeshTri.plot3: not implemented for "
                                      "the given shape of input vector!")
        return ax

9.13

    def _uniform_refine(self):
        """Perform a single mesh refine."""
        # rename variables
        t = self.t
        p = self.p
        e = self.facets
        sz = p.shape[1]
        t2f = self.t2f + sz
        # new vertices are the midpoints of edges
        newp = 0.5*np.vstack((p[0, e[0, :]] + p[0, e[1, :]],
                              p[1, e[0, :]] + p[1, e[1, :]]))
        newp = np.hstack((p, newp))
        # build new triangle definitions
        newt = np.vstack((t[0, :], t2f[0, :], t2f[2, :]))
        newt = np.hstack((newt, np.vstack((t[1, :], t2f[0, :], t2f[1, :]))))
        newt = np.hstack((newt, np.vstack((t[2, :], t2f[2, :], t2f[1, :]))))
        newt = np.hstack((newt, np.vstack((t2f[0, :], t2f[1, :], t2f[2, :]))))
        # update fields
        self.p = newp
        self.t = newt

        self._build_mappings()

        # prolongation matrix
        nsz = newp.shape[1]
        return coo_matrix(
            (np.hstack((np.ones(sz), .5 * np.ones(2 * (nsz - sz)))),
             (np.hstack((np.arange(sz), np.arange(nsz - sz) + sz, np.arange(nsz - sz) + sz)),
              np.hstack((np.arange(sz), e[0, :], e[1, :])))),
            shape=(nsz, sz)).tocsr()

9.14

    def _uniform_refine(self):
        """Perform a single mesh refine."""
        # rename variables
        t = self.t
        p = self.p
        e = self.facets
        sz = p.shape[1]
        t2f = self.t2f + sz
        # new vertices are the midpoints of edges
        newp = 0.5*np.vstack((p[0, e[0, :]] + p[0, e[1, :]],
                              p[1, e[0, :]] + p[1, e[1, :]]))
        newp = np.hstack((p, newp))
        # build new triangle definitions
        newt = np.vstack((t[0, :], t2f[0, :], t2f[2, :]))
        newt = np.hstack((newt, np.vstack((t[1, :], t2f[0, :], t2f[1, :]))))
        newt = np.hstack((newt, np.vstack((t[2, :], t2f[2, :], t2f[1, :]))))
        newt = np.hstack((newt, np.vstack((t2f[0, :], t2f[1, :], t2f[2, :]))))
        # update fields
        self.p = newp
        self.t = newt

        self._build_mappings()

        # prolongation matrix
        nsz = newp.shape[1]
        return coo_matrix(
            (np.hstack((np.ones(sz), .5 * np.ones(2 * (nsz - sz)))),
             (np.hstack((np.arange(sz), np.arange(nsz - sz) + sz, np.arange(nsz - sz) + sz)),
              np.hstack((np.arange(sz), e[0, :], e[1, :])))),
            shape=(nsz, sz)).tocsr()

9.15

    def mapping(self):
        return skfem.mapping.MappingAffine(self)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

if __name__ == "__main__":
    import doctest
    doctest.testmod()
原文地址:https://www.cnblogs.com/wangshixi12/p/9438656.html