示例#1
0
class TreeMeshIO(object):
    @classmethod
    def read_UBC(TreeMesh, meshFile, directory=""):
        """Read UBC 3D OcTree mesh file
        Input:
        :param str meshFile: path to the UBC GIF OcTree mesh file to read
        :rtype: discretize.TreeMesh
        :return: The octree mesh
        """
        fname = os.path.join(directory, meshFile)
        fileLines = np.genfromtxt(fname,
                                  dtype=str,
                                  delimiter="\n",
                                  comments="!")
        nCunderMesh = np.array(fileLines[0].split("!")[0].split(), dtype=int)
        tswCorn = np.array(fileLines[1].split("!")[0].split(), dtype=float)
        smallCell = np.array(fileLines[2].split("!")[0].split(), dtype=float)
        # Read the index array
        indArr = np.genfromtxt(
            (line.encode("utf8") for line in fileLines[4::]), dtype=np.int)
        nCunderMesh = nCunderMesh[:len(
            tswCorn)]  # remove information related to core

        hs = [np.ones(nr) * sz for nr, sz in zip(nCunderMesh, smallCell)]
        origin = tswCorn
        origin[-1] -= np.sum(hs[-1])

        ls = np.log2(nCunderMesh).astype(int)
        # if all ls are equal
        if min(ls) == max(ls):
            max_level = ls[0]
        else:
            max_level = min(ls) + 1

        mesh = TreeMesh(hs, origin=origin)
        levels = indArr[:, -1]
        indArr = indArr[:, :-1]

        indArr -= 1  # shift by 1....
        indArr = 2 * indArr + levels[:, None]  # get cell center index
        indArr[:,
               -1] = 2 * nCunderMesh[-1] - indArr[:,
                                                  -1]  # switch direction of iz
        levels = max_level - np.log2(levels)  # calculate level

        mesh.__setstate__((indArr, levels))
        return mesh

    def read_model_UBC(mesh, file_name):
        """Read UBC OcTree model and get vector
        :param string file_name: path to the UBC GIF model file to read
        :rtype: numpy.ndarray
        :return: OcTree model
        """

        if type(file_name) is list:
            out = {}
            for f in file_name:
                out[f] = mesh.read_model_UBC(f)
            return out

        modArr = np.loadtxt(file_name)

        ubc_order = mesh._ubc_order
        # order_ubc will re-order from treemesh ordering to UBC ordering
        # need the opposite operation
        un_order = np.empty_like(ubc_order)
        un_order[ubc_order] = np.arange(len(ubc_order))

        model = modArr[un_order].copy()  # ensure a contiguous array
        return model

    def write_UBC(mesh, file_name, models=None, directory=""):
        """Write UBC ocTree mesh and model files from a
        octree mesh and model.
        :param string file_name: File to write to
        :param dict models: Models in a dict, where each key is the file_name
        :param str directory: directory where to save model(s)
        """
        uniform_hs = np.array([np.allclose(h, h[0]) for h in mesh.h])
        if np.any(~uniform_hs):
            raise Exception("UBC form does not support variable cell widths")
        nCunderMesh = np.array([h.size for h in mesh.h], dtype=np.int64)

        tswCorn = mesh.origin.copy()
        tswCorn[-1] += np.sum(mesh.h[-1])

        smallCell = np.array([h[0] for h in mesh.h])
        nrCells = mesh.nC

        indArr, levels = mesh._ubc_indArr
        ubc_order = mesh._ubc_order

        indArr = indArr[ubc_order]
        levels = levels[ubc_order]

        # Write the UBC octree mesh file
        head = " ".join([f"{int(n)}" for n in nCunderMesh]) + " \n"
        head += " ".join([f"{v:.4f}" for v in tswCorn]) + " \n"
        head += " ".join([f"{v:.3f}" for v in smallCell]) + " \n"
        head += f"{int(nrCells)}"
        np.savetxt(file_name,
                   np.c_[indArr, levels],
                   fmt="%i",
                   header=head,
                   comments="")

        # Print the models
        if models is None:
            return
        if not isinstance(models, dict):
            raise TypeError("models must be a dict")
        for key in models:
            if not isinstance(key, str):
                raise TypeError(
                    "The dict key must be a string representing the file name")
            mesh.write_model_UBC(key, models[key], directory=directory)

    def write_model_UBC(mesh, file_name, model, directory=""):
        """Writes a model associated with a TreeMesh
        to a UBC-GIF format model file.

        Input:
        :param str file_name:  File to write to
        or just its name if directory is specified
        :param str directory: directory where the UBC GIF file lives
        :param numpy.ndarray model: The model
        """
        if type(file_name) is list:
            for f, m in zip(file_name, model):
                mesh.write_model_UBC(f, m)
        else:
            ubc_order = mesh._ubc_order
            fname = os.path.join(directory, file_name)
            m = model[ubc_order]
            np.savetxt(fname, m)

    # DEPRECATED
    @classmethod
    def readUBC(TreeMesh, file_name, directory=""):
        warnings.warn(
            "TensorMesh.readUBC has been deprecated and will be removed in"
            "discretize 1.0.0. please use TensorMesh.read_UBC",
            DeprecationWarning,
        )
        return TreeMesh.read_UBC(file_name, directory)

    readModelUBC = deprecate_method("read_model_UBC",
                                    "readModelUBC",
                                    removal_version="1.0.0")
    writeUBC = deprecate_method("write_UBC",
                                "writeUBC",
                                removal_version="1.0.0")
    writeModelUBC = deprecate_method("write_model_UBC",
                                     "writeModelUBC",
                                     removal_version="1.0.0")
示例#2
0
class BaseTensorMesh(BaseMesh):
    """Base class for tensor-product style meshes

    This class contains properites and methods that are common to Cartesian
    and cylindrical meshes. That is, meshes whose cell centers, nodes, faces
    and edges can be constructed with tensor-products of vectors.

    Do not use this class directly! Practical tensor meshes supported in
    discretize will inherit this class; i.e. :class:`discretize.TensorMesh`
    and :class:`~discretize.CylindricalMesh`. Inherit this class if you plan
    to develop a new tensor-style mesh class (e.g. a spherical mesh).

    Parameters
    ----------
    h : (dim) iterable of int, numpy.ndarray, or tuple
        Defines the cell widths along each axis. The length of the iterable object is
        equal to the dimension of the mesh (1, 2 or 3). For a 3D mesh, the list would
        have the form *[hx, hy, hz]* .

        Along each axis, the user has 3 choices for defining the cells widths:

        - :class:`int` -> A unit interval is equally discretized into `N` cells.
        - :class:`numpy.ndarray` -> The widths are explicity given for each cell
        - the widths are defined as a :class:`list` of :class:`tuple` of the form *(dh, nc, [npad])*
          where *dh* is the cell width, *nc* is the number of cells, and *npad* (optional)
          is a padding factor denoting exponential increase/decrease in the cell width
          for each cell; e.g. *[(2., 10, -1.3), (2., 50), (2., 10, 1.3)]*

    origin : (dim) iterable, default: 0
        Define the origin or 'anchor point' of the mesh; i.e. the bottom-left-frontmost
        corner. By default, the mesh is anchored such that its origin is at
        ``[0, 0, 0]``.

        For each dimension (x, y or z), The user may set the origin 2 ways:

        - a ``scalar`` which explicitly defines origin along that dimension.
        - **{'0', 'C', 'N'}** a :class:`str` specifying whether the zero coordinate along
          each axis is the first node location ('0'), in the center ('C') or the last
          node location ('N').

    See Also
    --------
    utils.unpack_widths :
        The function used to expand a ``list`` or ``tuple`` to generate widths.
    """

    _meshType = "BASETENSOR"
    _aliases = {
        **BaseMesh._aliases,
        **{
            "gridCC": "cell_centers",
            "gridN": "nodes",
            "gridFx": "faces_x",
            "gridFy": "faces_y",
            "gridFz": "faces_z",
            "gridEx": "edges_x",
            "gridEy": "edges_y",
            "gridEz": "edges_z",
        },
    }

    _unitDimensions = [1, 1, 1]
    _items = {"h"} | BaseMesh._items

    def __init__(self, h, origin=None, **kwargs):
        if "x0" in kwargs:
            origin = kwargs.pop("x0")

        try:
            h = list(h)  # ensure value is a list (and make a copy)
        except TypeError:
            raise TypeError("h must be an iterable object, not {}".format(type(h)))
        if len(h) == 0 or len(h) > 3:
            raise ValueError("h must be of dimension 1, 2, or 3 not {}".format(len(h)))
        # expand value
        for i, h_i in enumerate(h):
            if is_scalar(h_i) and not isinstance(h_i, np.ndarray):
                # This gives you something over the unit cube.
                h_i = self._unitDimensions[i] * np.ones(int(h_i)) / int(h_i)
            elif isinstance(h_i, (list, tuple)):
                h_i = unpack_widths(h_i)
            if not isinstance(h_i, np.ndarray):
                raise TypeError("h[{0:d}] is not a numpy array.".format(i))
            if len(h_i.shape) != 1:
                raise ValueError("h[{0:d}] must be a 1D numpy array.".format(i))
            h[i] = h_i[:]  # make a copy.
        self._h = tuple(h)

        shape_cells = tuple([len(h_i) for h_i in h])
        kwargs.pop("shape_cells", None)
        super().__init__(shape_cells=shape_cells, **kwargs)  # do not pass origin here
        if origin is not None:
            self.origin = origin

    @property
    def h(self):
        """Cell widths along each axis direction

        The widths of the cells along each axis direction are returned
        as a tuple of 1D arrays; e.g. (hx, hy, hz) for a 3D mesh.
        The lengths of the 1D arrays in the tuple are given by
        :py:attr:`~discretize.base.BaseMesh.shape_cells`. Ordering
        begins at the bottom southwest corner. These are the
        cell widths used when creating the mesh.

        Returns
        -------
        (dim) tuple of numpy.ndarray
            Cell widths along each axis direction. This depends on the mesh class:

                - :class:`~discretize.TensorMesh`: cell widths along the *x* , [*y* and *z* ] directions
                - :class:`~discretize.CylindricalMesh`: cell widths along the *r*, :math:`\\phi` and *z* directions
                - :class:`~discretize.TreeMesh`: cells widths of the *underlying tensor mesh* along the *x* , *y* [and *z* ] directions

        """
        return self._h

    @BaseMesh.origin.setter
    def origin(self, value):
        # ensure value is a 1D array at all times
        try:
            value = list(value)
        except:
            raise TypeError("origin must be iterable")
        if len(value) != self.dim:
            raise ValueError("Dimension mismatch. len(origin) != len(h)")
        for i, (val, h_i) in enumerate(zip(value, self.h)):
            if val == "C":
                value[i] = -h_i.sum() * 0.5
            elif val == "N":
                value[i] = -h_i.sum()
        value = np.asarray(value, dtype=np.float64)
        self._origin = value

    @property
    def nodes_x(self):
        """
        Return x-coordinates of the nodes along the x-direction

        This property returns a vector containing the x-coordinate values of
        the nodes along the x-direction. For instances of
        :class:`~discretize.TensorMesh` or :class:`~discretize.CylindricalMesh`,
        this is equivalent to the node positions which define the tensor along
        the x-axis. For instances of :class:`~discretize.TreeMesh` however, this
        property returns the x-coordinate values of the nodes along the x-direction
        for the underlying tensor mesh.

        Returns
        -------
        (n_nodes_x) numpy.ndarray of float
            A 1D array containing the x-coordinates of the nodes along
            the x-direction.

        """
        return np.r_[self.origin[0], self.h[0]].cumsum()

    @property
    def nodes_y(self):
        """
        Return y-coordinates of the nodes along the y-direction

        For 2D and 3D meshes, this property returns a vector
        containing the y-coordinate values of the nodes along the
        y-direction. For instances of :class:`~discretize.TensorMesh` or
        :class:`~discretize.CylindricalMesh`, this is equivalent to
        the node positions which define the tensor along the y-axis.
        For instances of :class:`~discretize.TreeMesh` however, this property
        returns the y-coordinate values of the nodes along the y-direction
        for the underlying tensor mesh.

        Returns
        -------
        (n_nodes_y) numpy.ndarray of float or None
            A 1D array containing the y-coordinates of the nodes along
            the y-direction. Returns *None* for 1D meshes.

        """
        return None if self.dim < 2 else np.r_[self.origin[1], self.h[1]].cumsum()

    @property
    def nodes_z(self):
        """
        Return z-coordinates of the nodes along the z-direction

        For 3D meshes, this property returns a 1D vector
        containing the z-coordinate values of the nodes along the
        z-direction. For instances of :class:`~discretize.TensorMesh` or
        :class:`~discretize.CylindricalMesh`, this is equivalent to
        the node positions which define the tensor along the z-axis.
        For instances of :class:`~discretize.TreeMesh` however, this property
        returns the z-coordinate values of the nodes along the z-direction
        for the underlying tensor mesh.

        Returns
        -------
        (n_nodes_z) numpy.ndarray of float or None
            A 1D array containing the z-coordinates of the nodes along
            the z-direction. Returns *None* for 1D and 2D meshes.

        """
        return None if self.dim < 3 else np.r_[self.origin[2], self.h[2]].cumsum()

    @property
    def cell_centers_x(self):
        """
        Return x-coordinates of the cell centers along the x-direction

        For 1D, 2D and 3D meshes, this property returns a 1D vector
        containing the x-coordinate values of the cell centers along the
        x-direction. For instances of :class:`~discretize.TensorMesh` or
        :class:`~discretize.CylindricalMesh`, this is equivalent to
        the cell center positions which define the tensor along the x-axis.
        For instances of :class:`~discretize.TreeMesh` however, this property
        returns the x-coordinate values of the cell centers along the x-direction
        for the underlying tensor mesh.

        Returns
        -------
        (n_cells_x) numpy.ndarray of float
            A 1D array containing the x-coordinates of the cell centers along
            the x-direction.
        """
        nodes = self.nodes_x
        return (nodes[1:] + nodes[:-1]) / 2

    @property
    def cell_centers_y(self):
        """
        Return y-coordinates of the cell centers along the y-direction

        For 2D and 3D meshes, this property returns a 1D vector
        containing the y-coordinate values of the cell centers along the
        y-direction. For instances of :class:`~discretize.TensorMesh` or
        :class:`~discretize.CylindricalMesh`, this is equivalent to
        the cell center positions which define the tensor along the y-axis.
        For instances of :class:`~discretize.TreeMesh` however, this property
        returns the y-coordinate values of the cell centers along the y-direction
        for the underlying tensor mesh .

        Returns
        -------
        (n_cells_y) numpy.ndarray of float or None
            A 1D array containing the y-coordinates of the cell centers along
            the y-direction. Returns *None* for 1D meshes.

        """
        if self.dim < 2:
            return None
        nodes = self.nodes_y
        return (nodes[1:] + nodes[:-1]) / 2

    @property
    def cell_centers_z(self):
        """
        Return z-coordinates of the cell centers along the z-direction

        For 3D meshes, this property returns a 1D vector
        containing the z-coordinate values of the cell centers along the
        z-direction. For instances of :class:`~discretize.TensorMesh` or
        :class:`~discretize.CylindricalMesh`, this is equivalent to
        the cell center positions which define the tensor along the z-axis.
        For instances of :class:`~discretize.TreeMesh` however, this property
        returns the z-coordinate values of the cell centers along the z-direction
        for the underlying tensor mesh .

        Returns
        -------
        (n_cells_z) numpy.ndarray of float or None
            A 1D array containing the z-coordinates of the cell centers along
            the z-direction. Returns *None* for 1D and 2D meshes.

        """
        if self.dim < 3:
            return None
        nodes = self.nodes_z
        return (nodes[1:] + nodes[:-1]) / 2

    @property
    def cell_centers(self):
        """Return gridded cell center locations

        This property returns a numpy array of shape (n_cells, dim)
        containing gridded cell center locations for all cells in the
        mesh. The cells are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_cells, dim) numpy.ndarray of float
            Gridded cell center locations

        Examples
        --------
        The following is a 1D example.

        >>> from discretize import TensorMesh
        >>> hx = np.ones(5)
        >>> mesh_1D = TensorMesh([hx], '0')
        >>> mesh_1D.cell_centers
        array([0.5, 1.5, 2.5, 3.5, 4.5])

        The following is a 3D example.

        >>> hx, hy, hz = np.ones(2), 2*np.ones(2), 3*np.ones(2)
        >>> mesh_3D = TensorMesh([hx, hy, hz], '000')
        >>> mesh_3D.cell_centers
        array([[0.5, 1. , 1.5],
               [1.5, 1. , 1.5],
               [0.5, 3. , 1.5],
               [1.5, 3. , 1.5],
               [0.5, 1. , 4.5],
               [1.5, 1. , 4.5],
               [0.5, 3. , 4.5],
               [1.5, 3. , 4.5]])

        """
        return self._getTensorGrid("cell_centers")

    @property
    def nodes(self):
        """Return gridded node locations

        This property returns a numpy array of shape (n_nodes, dim)
        containing gridded node locations for all nodes in the
        mesh. The nodes are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_nodes, dim) numpy.ndarray of float
            Gridded node locations

        Examples
        --------
        The following is a 1D example.

        >>> from discretize import TensorMesh
        >>> hx = np.ones(5)
        >>> mesh_1D = TensorMesh([hx], '0')
        >>> mesh_1D.nodes
        array([0., 1., 2., 3., 4., 5.])

        The following is a 3D example.

        >>> hx, hy, hz = np.ones(2), 2*np.ones(2), 3*np.ones(2)
        >>> mesh_3D = TensorMesh([hx, hy, hz], '000')
        >>> mesh_3D.nodes
        array([[0., 0., 0.],
               [1., 0., 0.],
               [2., 0., 0.],
               [0., 2., 0.],
               [1., 2., 0.],
               [2., 2., 0.],
               [0., 4., 0.],
               [1., 4., 0.],
               [2., 4., 0.],
               [0., 0., 3.],
               [1., 0., 3.],
               [2., 0., 3.],
               [0., 2., 3.],
               [1., 2., 3.],
               [2., 2., 3.],
               [0., 4., 3.],
               [1., 4., 3.],
               [2., 4., 3.],
               [0., 0., 6.],
               [1., 0., 6.],
               [2., 0., 6.],
               [0., 2., 6.],
               [1., 2., 6.],
               [2., 2., 6.],
               [0., 4., 6.],
               [1., 4., 6.],
               [2., 4., 6.]])

        """
        return self._getTensorGrid("nodes")

    @property
    def boundary_nodes(self):
        """Boundary node locations

        This property returns the locations of the nodes on
        the boundary of the mesh as a numpy array. The shape
        of the numpy array is the number of boundary nodes by
        the dimension of the mesh.

        Returns
        -------
        (n_boundary_nodes, dim) numpy.ndarray of float
            Boundary node locations
        """
        dim = self.dim
        if dim == 1:
            return self.nodes_x[[0, -1]]
        return self.nodes[make_boundary_bool(self.shape_nodes)]

    @property
    def h_gridded(self):
        """Return dimensions of all mesh cells as staggered grid.

        This property returns a numpy array of shape (n_cells, dim)
        containing gridded x, (y and z) dimensions for all cells in the mesh.
        The first row corresponds to the bottom-front-leftmost cell.
        The cells are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_cells, dim) numpy.ndarray of float
            Dimensions of all mesh cells as staggered grid

        Examples
        --------
        The following is a 1D example.

        >>> from discretize import TensorMesh
        >>> hx = np.ones(5)
        >>> mesh_1D = TensorMesh([hx])
        >>> mesh_1D.h_gridded
        array([[1.],
               [1.],
               [1.],
               [1.],
               [1.]])

        The following is a 3D example.

        >>> hx, hy, hz = np.ones(2), 2*np.ones(2), 3*np.ones(2)
        >>> mesh_3D = TensorMesh([hx, hy, hz])
        >>> mesh_3D.h_gridded
        array([[1., 2., 3.],
               [1., 2., 3.],
               [1., 2., 3.],
               [1., 2., 3.],
               [1., 2., 3.],
               [1., 2., 3.],
               [1., 2., 3.],
               [1., 2., 3.]])

        """
        if self.dim == 1:
            return self.h[0][:, None]
        return ndgrid(*self.h)

    @property
    def faces_x(self):
        """Gridded x-face locations

        This property returns a numpy array of shape (n_faces_x, dim)
        containing gridded locations for all x-faces in the
        mesh. The first row corresponds to the bottom-front-leftmost x-face.
        The x-faces are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_faces_x, dim) numpy.ndarray of float
            Gridded x-face locations
        """
        if self.nFx == 0:
            return
        return self._getTensorGrid("faces_x")

    @property
    def faces_y(self):
        """Gridded y-face locations

        This property returns a numpy array of shape (n_faces_y, dim)
        containing gridded locations for all y-faces in the
        mesh. The first row corresponds to the bottom-front-leftmost y-face.
        The y-faces are ordered along the x, then y, then z directions.

        Returns
        -------
        n_faces_y, dim) numpy.ndarray of float or None
            Gridded y-face locations for 2D and 3D mesh. Returns *None* for 1D meshes.
        """
        if self.nFy == 0 or self.dim < 2:
            return
        return self._getTensorGrid("faces_y")

    @property
    def faces_z(self):
        """Gridded z-face locations

        This property returns a numpy array of shape (n_faces_z, dim)
        containing gridded locations for all z-faces in the
        mesh. The first row corresponds to the bottom-front-leftmost z-face.
        The z-faces are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_faces_z, dim) numpy.ndarray of float or None
            Gridded z-face locations for 3D mesh. Returns *None* for 1D and 2D meshes.
        """
        if self.nFz == 0 or self.dim < 3:
            return
        return self._getTensorGrid("faces_z")

    @property
    def faces(self):
        """Gridded face locations

        This property returns a numpy array of shape (n_faces, dim)
        containing gridded locations for all faces in the mesh.
        The first row corresponds to the bottom-front-leftmost x-face.
        The output array returns the x-faces, then the y-faces, then
        the z-faces; i.e. *mesh.faces* is equivalent to *np.r_[mesh.faces_x, mesh.faces_y, mesh.face_z]* .
        For each face type, the locations are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_faces, dim) numpy.ndarray of float
            Gridded face locations

        """
        faces = self.faces_x
        if self.dim > 1:
            faces = np.r_[faces, self.faces_y]
        if self.dim > 2:
            faces = np.r_[faces, self.faces_z]
        return faces

    @property
    def boundary_faces(self):
        """Boundary face locations

        This property returns the locations of the faces on
        the boundary of the mesh as a numpy array. The shape
        of the numpy array is the number of boundary faces by
        the dimension of the mesh.

        Returns
        -------
        (n_boundary_faces, dim) numpy.ndarray of float
            Boundary faces locations
        """
        dim = self.dim
        if dim == 1:
            return self.nodes_x[[0, -1]]
        if dim == 2:
            fx = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y)
            fy = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]])
            return np.r_[fx, fy]
        if dim == 3:
            fx = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y, self.cell_centers_z)
            fy = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]], self.cell_centers_z)
            fz = ndgrid(self.cell_centers_x, self.cell_centers_y, self.nodes_z[[0, -1]])
            return np.r_[fx, fy, fz]

    @property
    def boundary_face_outward_normals(self):
        """Outward normal vectors of boundary faces

        This property returns the outward normal vectors of faces
        the boundary of the mesh as a numpy array. The shape
        of the numpy array is the number of boundary faces by
        the dimension of the mesh.

        Returns
        -------
        (n_boundary_faces, dim) numpy.ndarray of float
            Outward normal vectors of boundary faces
        """
        dim = self.dim
        if dim == 1:
            return np.array([-1, 1])
        if dim == 2:
            nx = ndgrid(np.r_[-1, 1], np.zeros(self.shape_cells[1]))
            ny = ndgrid(np.zeros(self.shape_cells[0]), np.r_[-1, 1])
            return np.r_[nx, ny]
        if dim == 3:
            nx = ndgrid(
                np.r_[-1, 1],
                np.zeros(self.shape_cells[1]),
                np.zeros(self.shape_cells[2]),
            )
            ny = ndgrid(
                np.zeros(self.shape_cells[0]),
                np.r_[-1, 1],
                np.zeros(self.shape_cells[2]),
            )
            nz = ndgrid(
                np.zeros(self.shape_cells[0]),
                np.zeros(self.shape_cells[1]),
                np.r_[-1, 1],
            )
            return np.r_[nx, ny, nz]

    @property
    def edges_x(self):
        """Gridded x-edge locations

        This property returns a numpy array of shape (n_edges_x, dim)
        containing gridded locations for all x-edges in the mesh.
        The first row corresponds to the bottom-front-leftmost x-edge.
        The x-edges are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_edges_x, dim) numpy.ndarray of float or None
            Gridded x-edge locations. Returns *None* if `shape_edges_x[0]` is 0.
        """
        if self.nEx == 0:
            return
        return self._getTensorGrid("edges_x")

    @property
    def edges_y(self):
        """Gridded y-edge locations

        This property returns a numpy array of shape (n_edges_y, dim)
        containing gridded locations for all y-edges in the mesh.
        The first row corresponds to the bottom-front-leftmost y-edge.
        The y-edges are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_edges_y, dim) numpy.ndarray of float
            Gridded y-edge locations. Returns *None* for 1D meshes.
        """
        if self.nEy == 0 or self.dim < 2:
            return
        return self._getTensorGrid("edges_y")

    @property
    def edges_z(self):
        """Gridded z-edge locations

        This property returns a numpy array of shape (n_edges_z, dim)
        containing gridded locations for all z-edges in the mesh.
        The first row corresponds to the bottom-front-leftmost z-edge.
        The z-edges are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_edges_z, dim) numpy.ndarray of float
            Gridded z-edge locations. Returns *None* for 1D and 2D meshes.
        """
        if self.nEz == 0 or self.dim < 3:
            return
        return self._getTensorGrid("edges_z")

    @property
    def edges(self):
        """Gridded edge locations

        This property returns a numpy array of shape (n_edges, dim)
        containing gridded locations for all edges in the mesh.
        The first row corresponds to the bottom-front-leftmost x-edge.
        The output array returns the x-edges, then the y-edges, then
        the z-edges; i.e. *mesh.edges* is equivalent to *np.r_[mesh.edges_x, mesh.edges_y, mesh.edges_z]* .
        For each edge type, the locations are ordered along the x, then y, then z directions.

        Returns
        -------
        (n_edges, dim) numpy.ndarray of float
            Gridded edge locations

        """
        edges = self.edges_x
        if self.dim > 1:
            edges = np.r_[edges, self.edges_y]
        if self.dim > 2:
            edges = np.r_[edges, self.edges_z]
        return edges

    @property
    def boundary_edges(self):
        """Boundary edge locations

        This property returns the locations of the edges on
        the boundary of the mesh as a numpy array. The shape
        of the numpy array is the number of boundary edges by
        the dimension of the mesh.

        Returns
        -------
        (n_boundary_edges, dim) numpy.ndarray of float
            Boundary edge locations
        """
        dim = self.dim
        if dim == 1:
            return None  # no boundary edges in 1D
        if dim == 2:
            ex = ndgrid(self.cell_centers_x, self.nodes_y[[0, -1]])
            ey = ndgrid(self.nodes_x[[0, -1]], self.cell_centers_y)
            return np.r_[ex, ey]
        if dim == 3:
            ex = self.edges_x[make_boundary_bool(self.shape_edges_x, dir="yz")]
            ey = self.edges_y[make_boundary_bool(self.shape_edges_y, dir="xz")]
            ez = self.edges_z[make_boundary_bool(self.shape_edges_z, dir="xy")]
            return np.r_[ex, ey, ez]

    def _getTensorGrid(self, key):
        if getattr(self, "_" + key, None) is None:
            setattr(self, "_" + key, ndgrid(self.get_tensor(key)))
        return getattr(self, "_" + key)

    def get_tensor(self, key):
        """Returns the base 1D arrays for a specified mesh tensor.

        The cell-centers, nodes, x-faces, z-edges, etc... of a tensor mesh
        can be constructed by applying tensor products to the set of base
        1D arrays; i.e. (vx, vy, vz). These 1D arrays define the gridded
        locations for the mesh tensor along each axis. For a given mesh tensor
        (i.e. cell centers, nodes, x/y/z faces or x/y/z edges),
        **get_tensor** returns a list containing the base 1D arrays.

        Parameters
        ----------
        key : str
            Specifies the tensor being returned. Please choose from::

                'CC', 'cell_centers' -> location of cell centers
                'N', 'nodes'         -> location of nodes
                'Fx', 'faces_x'      -> location of faces with an x normal
                'Fy', 'faces_y'      -> location of faces with an y normal
                'Fz', 'faces_z'      -> location of faces with an z normal
                'Ex', 'edges_x'      -> location of edges with an x tangent
                'Ey', 'edges_y'      -> location of edges with an y tangent
                'Ez', 'edges_z'      -> location of edges with an z tangent

        Returns
        -------
        (dim) list of 1D numpy.ndarray
            list of base 1D arrays for the tensor.

        """
        key = self._parse_location_type(key)

        if key == "faces_x":
            ten = [
                self.nodes_x,
                self.cell_centers_y,
                self.cell_centers_z,
            ]
        elif key == "faces_y":
            ten = [
                self.cell_centers_x,
                self.nodes_y,
                self.cell_centers_z,
            ]
        elif key == "faces_z":
            ten = [
                self.cell_centers_x,
                self.cell_centers_y,
                self.nodes_z,
            ]
        elif key == "edges_x":
            ten = [self.cell_centers_x, self.nodes_y, self.nodes_z]
        elif key == "edges_y":
            ten = [self.nodes_x, self.cell_centers_y, self.nodes_z]
        elif key == "edges_z":
            ten = [self.nodes_x, self.nodes_y, self.cell_centers_z]
        elif key == "cell_centers":
            ten = [
                self.cell_centers_x,
                self.cell_centers_y,
                self.cell_centers_z,
            ]
        elif key == "nodes":
            ten = [self.nodes_x, self.nodes_y, self.nodes_z]
        else:
            raise KeyError(r"Unrecognized key {key}")

        return [t for t in ten if t is not None]

    # --------------- Methods ---------------------

    def is_inside(self, pts, location_type="nodes", **kwargs):
        """Determine which points lie within the mesh

        For an arbitrary set of points, **is_indside** returns a
        boolean array identifying which points lie within the mesh.

        Parameters
        ----------
        pts : (n_pts, dim) numpy.ndarray
            Locations of input points. Must have same dimension as the mesh.
        location_type : str, optional
            Use *N* to determine points lying within the cluster of mesh
            nodes. Use *CC* to determine points lying within the cluster
            of mesh cell centers.

        Returns
        -------
        (n_pts) numpy.ndarray of bool
            Boolean array identifying points which lie within the mesh

        """
        if "locType" in kwargs:
            warnings.warn(
                "The locType keyword argument has been deprecated, please use location_type. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            location_type = kwargs["locType"]
        pts = as_array_n_by_dim(pts, self.dim)

        tensors = self.get_tensor(location_type)

        if location_type[0].lower() == "n" and self._meshType == "CYL":
            # NOTE: for a CYL mesh we add a node to check if we are inside in
            # the radial direction!
            tensors[0] = np.r_[0.0, tensors[0]]
            tensors[1] = np.r_[tensors[1], 2.0 * np.pi]

        inside = np.ones(pts.shape[0], dtype=bool)
        for i, tensor in enumerate(tensors):
            TOL = np.diff(tensor).min() * 1.0e-10
            inside = (
                inside
                & (pts[:, i] >= tensor.min() - TOL)
                & (pts[:, i] <= tensor.max() + TOL)
            )
        return inside

    def _getInterpolationMat(
        self, loc, location_type="cell_centers", zeros_outside=False
    ):
        """Produces interpolation matrix

        Parameters
        ----------
        loc : numpy.ndarray
            Location of points to interpolate to

        location_type: str, optional
            What to interpolate

            location_type can be::

                'Ex', 'edges_x'           -> x-component of field defined on x edges
                'Ey', 'edges_y'           -> y-component of field defined on y edges
                'Ez', 'edges_z'           -> z-component of field defined on z edges
                'Fx', 'faces_x'           -> x-component of field defined on x faces
                'Fy', 'faces_y'           -> y-component of field defined on y faces
                'Fz', 'faces_z'           -> z-component of field defined on z faces
                'N', 'nodes'              -> scalar field defined on nodes
                'CC', 'cell_centers'      -> scalar field defined on cell centers
                'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers
                'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers
                'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers

        Returns
        -------
        scipy.sparse.csr_matrix
            M, the interpolation matrix

        """

        loc = as_array_n_by_dim(loc, self.dim)

        if not zeros_outside:
            if not np.all(self.is_inside(loc)):
                raise ValueError("Points outside of mesh")
        else:
            indZeros = np.logical_not(self.is_inside(loc))
            loc[indZeros, :] = np.array([v.mean() for v in self.get_tensor("CC")])

        location_type = self._parse_location_type(location_type)

        if location_type in [
            "faces_x",
            "faces_y",
            "faces_z",
            "edges_x",
            "edges_y",
            "edges_z",
        ]:
            ind = {"x": 0, "y": 1, "z": 2}[location_type[-1]]
            if self.dim < ind:
                raise ValueError("mesh is not high enough dimension.")
            if "f" in location_type.lower():
                items = (self.nFx, self.nFy, self.nFz)[: self.dim]
            else:
                items = (self.nEx, self.nEy, self.nEz)[: self.dim]
            components = [spzeros(loc.shape[0], n) for n in items]
            components[ind] = interpolation_matrix(loc, *self.get_tensor(location_type))
            # remove any zero blocks (hstack complains)
            components = [comp for comp in components if comp.shape[1] > 0]
            Q = sp.hstack(components)

        elif location_type in ["cell_centers", "nodes"]:
            Q = interpolation_matrix(loc, *self.get_tensor(location_type))

        elif location_type in ["cell_centers_x", "cell_centers_y", "cell_centers_z"]:
            Q = interpolation_matrix(loc, *self.get_tensor("CC"))
            Z = spzeros(loc.shape[0], self.nC)
            if location_type[-1] == "x":
                Q = sp.hstack([Q, Z, Z])
            elif location_type[-1] == "y":
                Q = sp.hstack([Z, Q, Z])
            elif location_type[-1] == "z":
                Q = sp.hstack([Z, Z, Q])

        else:
            raise NotImplementedError(
                "getInterpolationMat: location_type=="
                + location_type
                + " and mesh.dim=="
                + str(self.dim)
            )

        if zeros_outside:
            Q[indZeros, :] = 0

        return Q.tocsr()

    def get_interpolation_matrix(
        self, loc, location_type="cell_centers", zeros_outside=False, **kwargs
    ):
        """Construct linear interpolation matrix from mesh

        This method constructs a linear interpolation matrix from tensor locations
        (nodes, cell-centers, faces, etc...) on the mesh to a set of arbitrary locations.

        Parameters
        ----------
        loc : (n_pts, dim) numpy.ndarray
            Location of points being to interpolate to. Must have same dimensions as the mesh.
        location_type : str, optional
            Tensor locations on the mesh being interpolated from. *location_type* must be one of:

            - 'Ex', 'edges_x'           -> x-component of field defined on x edges
            - 'Ey', 'edges_y'           -> y-component of field defined on y edges
            - 'Ez', 'edges_z'           -> z-component of field defined on z edges
            - 'Fx', 'faces_x'           -> x-component of field defined on x faces
            - 'Fy', 'faces_y'           -> y-component of field defined on y faces
            - 'Fz', 'faces_z'           -> z-component of field defined on z faces
            - 'N', 'nodes'              -> scalar field defined on nodes
            - 'CC', 'cell_centers'      -> scalar field defined on cell centers
            - 'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers
            - 'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers
            - 'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers
        zeros_outside : bool, optional
            If *False*, nearest neighbour is used to compute the interpolate value
            at locations outside the mesh. If *True* , values at locations outside
            the mesh will be zero.

        Returns
        -------
        (n_pts, n_loc_type) scipy.sparse.csr_matrix
            A sparse matrix which interpolates the specified tensor quantity on mesh to
            the set of specified locations.


        Examples
        --------
        Here is a 1D example where a function evaluated on the nodes
        is interpolated to a set of random locations. To compare the accuracy, the
        function is evaluated at the set of random locations.

        >>> from discretize import TensorMesh
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> np.random.seed(14)

        >>> locs = np.random.rand(50)*0.8+0.1
        >>> dense = np.linspace(0, 1, 200)
        >>> fun = lambda x: np.cos(2*np.pi*x)

        >>> hx = 0.125 * np.ones(8)
        >>> mesh1D = TensorMesh([hx])
        >>> Q = mesh1D.get_interpolation_matrix(locs, 'nodes')

        .. collapse:: Expand to see scripting for plot

            >>> plt.figure(figsize=(5, 3))
            >>> plt.plot(dense, fun(dense), ':', c="C0", lw=3, label="True Function")
            >>> plt.plot(mesh1D.nodes, fun(mesh1D.nodes), 's', c="C0", ms=8, label="True sampled")
            >>> plt.plot(locs, Q*fun(mesh1D.nodes), 'o', ms=4, label="Interpolated")
            >>> plt.legend()
            >>> plt.show()

        Here, demonstrate a similar example on a 2D mesh using a 2D Gaussian distribution.
        We interpolate the Gaussian from the nodes to cell centers and examine the relative
        error.

        >>> hx = np.ones(10)
        >>> hy = np.ones(10)
        >>> mesh2D = TensorMesh([hx, hy], x0='CC')
        >>> def fun(x, y):
        ...     return np.exp(-(x**2 + y**2)/2**2)

        >>> nodes = mesh2D.nodes
        >>> val_nodes = fun(nodes[:, 0], nodes[:, 1])
        >>> centers = mesh2D.cell_centers
        >>> val_centers = fun(centers[:, 0], centers[:, 1])
        >>> A = mesh2D.get_interpolation_matrix(centers, 'nodes')
        >>> val_interp = A.dot(val_nodes)

        .. collapse:: Expand to see scripting for plot

            >>> fig = plt.figure(figsize=(11,3.3))
            >>> clim = (0., 1.)
            >>> ax1 = fig.add_subplot(131)
            >>> ax2 = fig.add_subplot(132)
            >>> ax3 = fig.add_subplot(133)
            >>> mesh2D.plot_image(val_centers, ax=ax1, clim=clim)
            >>> mesh2D.plot_image(val_interp, ax=ax2, clim=clim)
            >>> mesh2D.plot_image(val_centers-val_interp, ax=ax3, clim=clim)
            >>> ax1.set_title('Analytic at Centers')
            >>> ax2.set_title('Interpolated from Nodes')
            >>> ax3.set_title('Relative Error')
            >>> plt.show()
        """
        if "locType" in kwargs:
            warnings.warn(
                "The locType keyword argument has been deprecated, please use location_type. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            location_type = kwargs["locType"]
        if "zerosOutside" in kwargs:
            warnings.warn(
                "The zerosOutside keyword argument has been deprecated, please use zeros_outside. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            zeros_outside = kwargs["zerosOutside"]
        return self._getInterpolationMat(loc, location_type, zeros_outside)

    def _fastInnerProduct(
        self, projection_type, model=None, invert_model=False, invert_matrix=False
    ):
        """Fast version of getFaceInnerProduct.
            This does not handle the case of a full tensor property.

        Parameters
        ----------
        model : numpy.ndarray
            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))

        projection_type : str
            'edges' or 'faces'

        returnP : bool
            returns the projection matrices

        invert_model : bool
            inverts the material property

        invert_matrix : bool
            inverts the matrix

        Returns
        -------
        (n_faces, n_faces) scipy.sparse.csr_matrix
            M, the inner product matrix

        """
        projection_type = projection_type[0].upper()
        if projection_type not in ["F", "E"]:
            raise ValueError("projection_type must be 'F' for faces or 'E' for edges")

        if model is None:
            model = np.ones(self.nC)

        if invert_model:
            model = 1.0 / model

        if is_scalar(model):
            model = model * np.ones(self.nC)

        # number of elements we are averaging (equals dim for regular
        # meshes, but for cyl, where we use symmetry, it is 1 for edge
        # variables and 2 for face variables)
        if self._meshType == "CYL":
            shape = getattr(self, "vn" + projection_type)
            n_elements = sum([1 if x != 0 else 0 for x in shape])
        else:
            n_elements = self.dim

        # Isotropic? or anisotropic?
        if model.size == self.nC:
            Av = getattr(self, "ave" + projection_type + "2CC")
            Vprop = self.cell_volumes * mkvc(model)
            M = n_elements * sdiag(Av.T * Vprop)

        elif model.size == self.nC * self.dim:
            Av = getattr(self, "ave" + projection_type + "2CCV")

            # if cyl, then only certain components are relevant due to symmetry
            # for faces, x, z matters, for edges, y (which is theta) matters
            if self._meshType == "CYL":
                if projection_type == "E":
                    model = model[:, 1]  # this is the action of a projection mat
                elif projection_type == "F":
                    model = model[:, [0, 2]]

            V = sp.kron(sp.identity(n_elements), sdiag(self.cell_volumes))
            M = sdiag(Av.T * V * mkvc(model))
        else:
            return None

        if invert_matrix:
            return sdinv(M)
        else:
            return M

    def _fastInnerProductDeriv(
        self, projection_type, model, invert_model=False, invert_matrix=False
    ):
        """

        Parameters
        ----------

        projection_type : str
            'E' or 'F'

        tensorType : TensorType
            type of the tensor

        invert_model : bool
            inverts the material property

        invert_matrix : bool
            inverts the matrix


        Returns
        -------
        function
            dMdmu, the derivative of the inner product matrix

        """

        projection_type = projection_type[0].upper()
        if projection_type not in ["F", "E"]:
            raise ValueError("projection_type must be 'F' for faces or 'E' for edges")

        tensorType = TensorType(self, model)

        dMdprop = None

        if invert_matrix or invert_model:
            MI = self._fastInnerProduct(
                projection_type,
                model,
                invert_model=invert_model,
                invert_matrix=invert_matrix,
            )

        # number of elements we are averaging (equals dim for regular
        # meshes, but for cyl, where we use symmetry, it is 1 for edge
        # variables and 2 for face variables)
        if self._meshType == "CYL":
            shape = getattr(self, "vn" + projection_type)
            n_elements = sum([1 if x != 0 else 0 for x in shape])
        else:
            n_elements = self.dim

        if tensorType == 0:  # isotropic, constant
            Av = getattr(self, "ave" + projection_type + "2CC")
            V = sdiag(self.cell_volumes)
            ones = sp.csr_matrix(
                (np.ones(self.nC), (range(self.nC), np.zeros(self.nC))),
                shape=(self.nC, 1),
            )
            if not invert_matrix and not invert_model:
                dMdprop = n_elements * Av.T * V * ones
            elif invert_matrix and invert_model:
                dMdprop = n_elements * (
                    sdiag(MI.diagonal() ** 2)
                    * Av.T
                    * V
                    * ones
                    * sdiag(1.0 / model ** 2)
                )
            elif invert_model:
                dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model ** 2)
            elif invert_matrix:
                dMdprop = n_elements * (sdiag(-MI.diagonal() ** 2) * Av.T * V)

        elif tensorType == 1:  # isotropic, variable in space
            Av = getattr(self, "ave" + projection_type + "2CC")
            V = sdiag(self.cell_volumes)
            if not invert_matrix and not invert_model:
                dMdprop = n_elements * Av.T * V
            elif invert_matrix and invert_model:
                dMdprop = n_elements * (
                    sdiag(MI.diagonal() ** 2) * Av.T * V * sdiag(1.0 / model ** 2)
                )
            elif invert_model:
                dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model ** 2)
            elif invert_matrix:
                dMdprop = n_elements * (sdiag(-MI.diagonal() ** 2) * Av.T * V)

        elif tensorType == 2:  # anisotropic
            Av = getattr(self, "ave" + projection_type + "2CCV")
            V = sp.kron(sp.identity(self.dim), sdiag(self.cell_volumes))

            if self._meshType == "CYL":
                Zero = sp.csr_matrix((self.nC, self.nC))
                Eye = sp.eye(self.nC)
                if projection_type == "E":
                    P = sp.hstack([Zero, Eye, Zero])
                    # print(P.todense())
                elif projection_type == "F":
                    P = sp.vstack(
                        [sp.hstack([Eye, Zero, Zero]), sp.hstack([Zero, Zero, Eye])]
                    )
                    # print(P.todense())
            else:
                P = sp.eye(self.nC * self.dim)

            if not invert_matrix and not invert_model:
                dMdprop = Av.T * P * V
            elif invert_matrix and invert_model:
                dMdprop = (
                    sdiag(MI.diagonal() ** 2) * Av.T * P * V * sdiag(1.0 / model ** 2)
                )
            elif invert_model:
                dMdprop = Av.T * P * V * sdiag(-1.0 / model ** 2)
            elif invert_matrix:
                dMdprop = sdiag(-MI.diagonal() ** 2) * Av.T * P * V

        if dMdprop is not None:

            def innerProductDeriv(v=None):
                if v is None:
                    warnings.warn(
                        "Depreciation Warning: TensorMesh.innerProductDeriv."
                        " You should be supplying a vector. "
                        "Use: sdiag(u)*dMdprop",
                        DeprecationWarning,
                    )
                    return dMdprop
                return sdiag(v) * dMdprop

            return innerProductDeriv
        else:
            return None

    # DEPRECATED
    @property
    def hx(self):
        """Width of cells in the x direction

        Returns
        -------
        numpy.ndarray

        .. deprecated:: 0.5.0
          `hx` will be removed in discretize 1.0.0 to reduce namespace clutter,
          please use `mesh.h[0]`.
        """
        warnings.warn(
            "hx has been deprecated, please access as mesh.h[0]", DeprecationWarning
        )
        return self.h[0]

    @property
    def hy(self):
        """Width of cells in the y direction

        Returns
        -------
        numpy.ndarray or None

        .. deprecated:: 0.5.0
          `hy` will be removed in discretize 1.0.0 to reduce namespace clutter,
          please use `mesh.h[1]`.
        """
        warnings.warn(
            "hy has been deprecated, please access as mesh.h[1]", DeprecationWarning
        )
        return None if self.dim < 2 else self.h[1]

    @property
    def hz(self):
        """Width of cells in the z direction

        Returns
        -------
        numpy.ndarray or None

        .. deprecated:: 0.5.0
          `hz` will be removed in discretize 1.0.0 to reduce namespace clutter,
          please use `mesh.h[2]`.
        """
        warnings.warn(
            "hz has been deprecated, please access as mesh.h[2]", DeprecationWarning
        )
        return None if self.dim < 3 else self.h[2]

    vectorNx = deprecate_property("nodes_x", "vectorNx", removal_version="1.0.0", future_warn=False)
    vectorNy = deprecate_property("nodes_y", "vectorNy", removal_version="1.0.0", future_warn=False)
    vectorNz = deprecate_property("nodes_z", "vectorNz", removal_version="1.0.0", future_warn=False)
    vectorCCx = deprecate_property(
        "cell_centers_x", "vectorCCx", removal_version="1.0.0", future_warn=False
    )
    vectorCCy = deprecate_property(
        "cell_centers_y", "vectorCCy", removal_version="1.0.0", future_warn=False
    )
    vectorCCz = deprecate_property(
        "cell_centers_z", "vectorCCz", removal_version="1.0.0", future_warn=False
    )
    getInterpolationMat = deprecate_method(
        "get_interpolation_matrix", "getInterpolationMat", removal_version="1.0.0", future_warn=False
    )
    isInside = deprecate_method("is_inside", "isInside", removal_version="1.0.0", future_warn=False)
    getTensor = deprecate_method("get_tensor", "getTensor", removal_version="1.0.0", future_warn=False)
示例#3
0
class TensorMeshIO(InterfaceTensorread_vtk):
    @classmethod
    def _readUBC_3DMesh(TensorMesh, file_name):
        """Read UBC GIF 3D tensor mesh and generate same dimension TensorMesh.

        Input:
        :param string file_name: path to the UBC GIF mesh file

        Output:
        :rtype: TensorMesh
        :return: The tensor mesh for the file_name.
        """

        # Interal function to read cell size lines for the UBC mesh files.
        def readCellLine(line):
            line_list = []
            for seg in line.split():
                if "*" in seg:
                    sp = seg.split("*")
                    seg_arr = np.ones((int(sp[0]), )) * float(sp[1])
                else:
                    seg_arr = np.array([float(seg)], float)
                line_list.append(seg_arr)
            return np.concatenate(line_list)

        # Read the file as line strings, remove lines with comment = !
        msh = np.genfromtxt(file_name,
                            delimiter="\n",
                            dtype=np.str,
                            comments="!")
        # Fist line is the size of the model
        sizeM = np.array(msh[0].split(), dtype=float)
        # Second line is the South-West-Top corner coordinates.
        origin = np.array(msh[1].split(), dtype=float)
        # Read the cell sizes
        h1 = readCellLine(msh[2])
        h2 = readCellLine(msh[3])
        h3temp = readCellLine(msh[4])
        # Invert the indexing of the vector to start from the bottom.
        h3 = h3temp[::-1]
        # Adjust the reference point to the bottom south west corner
        origin[2] = origin[2] - np.sum(h3)
        # Make the mesh
        tensMsh = TensorMesh([h1, h2, h3], origin=origin)
        return tensMsh

    @classmethod
    def _readUBC_2DMesh(TensorMesh, file_name):
        """Read UBC GIF 2DTensor mesh and generate 2D Tensor mesh in simpeg

        Input:
        :param string file_name: path to the UBC GIF mesh file

        Output:
        :rtype: TensorMesh
        :return: SimPEG TensorMesh 2D object
        """

        fopen = open(file_name, "r")

        # Read down the file and unpack dx vector
        def unpackdx(fid, nrows):
            for ii in range(nrows):
                line = fid.readline()
                var = np.array(line.split(), dtype=float)
                if ii == 0:
                    x0 = var[0]
                    xvec = np.ones(int(var[2])) * (var[1] - var[0]) / int(
                        var[2])
                    xend = var[1]
                else:
                    xvec = np.hstack(
                        (xvec,
                         np.ones(int(var[1])) * (var[0] - xend) / int(var[1])))
                    xend = var[0]
            return x0, xvec

        # Start with dx block
        # First line specifies the number of rows for x-cells
        line = fopen.readline()
        # Strip comments lines
        while line.startswith("!"):
            line = fopen.readline()
        nl = np.array(line.split(), dtype=int)
        [x0, dx] = unpackdx(fopen, nl[0])
        # Move down the file until reaching the z-block
        line = fopen.readline()
        if not line:
            line = fopen.readline()
        # End with dz block
        # First line specifies the number of rows for z-cells
        line = fopen.readline()
        nl = np.array(line.split(), dtype=int)
        [z0, dz] = unpackdx(fopen, nl[0])
        # Flip z0 to be the bottom of the mesh for SimPEG
        z0 = -(z0 + sum(dz))
        dz = dz[::-1]
        # Make the mesh
        tensMsh = TensorMesh([dx, dz], origin=(x0, z0))

        fopen.close()

        return tensMsh

    @classmethod
    def read_UBC(TensorMesh, file_name, directory=""):
        """Wrapper to Read UBC GIF 2D  and 3D tensor mesh and generate same dimension TensorMesh.

        Input:
        :param str file_name: path to the UBC GIF mesh file or just its name if directory is specified
        :param str directory: directory where the UBC GIF file lives

        Output:
        :rtype: TensorMesh
        :return: The tensor mesh for the file_name.
        """
        # Check the expected mesh dimensions
        fname = os.path.join(directory, file_name)
        # Read the file as line strings, remove lines with comment = !
        msh = np.genfromtxt(fname,
                            delimiter="\n",
                            dtype=np.str,
                            comments="!",
                            max_rows=1)
        # Fist line is the size of the model
        sizeM = np.array(msh.ravel()[0].split(), dtype=float)
        # Check if the mesh is a UBC 2D mesh
        if sizeM.shape[0] == 1:
            Tnsmsh = TensorMesh._readUBC_2DMesh(fname)
        # Check if the mesh is a UBC 3D mesh
        elif sizeM.shape[0] == 3:
            Tnsmsh = TensorMesh._readUBC_3DMesh(fname)
        else:
            raise Exception("File format not recognized")
        return Tnsmsh

    def _readModelUBC_2D(mesh, file_name):
        """
        Read UBC GIF 2DTensor model and generate 2D Tensor model in simpeg

        Input:
        :param string file_name: path to the UBC GIF 2D model file

        Output:
        :rtype: numpy.ndarray
        :return: model with TensorMesh ordered
        """

        # Open fileand skip header... assume that we know the mesh already
        obsfile = np.genfromtxt(file_name,
                                delimiter=" \n",
                                dtype=np.str,
                                comments="!")

        dim = tuple(np.array(obsfile[0].split(), dtype=int))
        if mesh.shape_cells != dim:
            raise Exception("Dimension of the model and mesh mismatch")

        model = []
        for line in obsfile[1:]:
            model.extend([float(val) for val in line.split()])
        model = np.asarray(model)
        if not len(model) == mesh.nC:
            raise Exception("""Something is not right, expected size is {:d}
                but unwrap vector is size {:d}""".format(mesh.nC, len(model)))

        return model.reshape(mesh.vnC, order="F")[:, ::-1].reshape(-1,
                                                                   order="F")

    def _readModelUBC_3D(mesh, file_name):
        """Read UBC 3DTensor mesh model and generate 3D Tensor mesh model

        Input:
        :param string file_name: path to the UBC GIF mesh file to read

        Output:
        :rtype: numpy.ndarray
        :return: model with TensorMesh ordered
        """
        f = open(file_name, "r")
        model = np.array(list(map(float, f.readlines())))
        f.close()
        nCx, nCy, nCz = mesh.shape_cells
        model = np.reshape(model, (nCz, nCx, nCy), order="F")
        model = model[::-1, :, :]
        model = np.transpose(model, (1, 2, 0))
        model = mkvc(model)
        return model

    def read_model_UBC(mesh, file_name, directory=""):
        """Read UBC 2D or 3D Tensor mesh model
            and generate Tensor mesh model

        Input:
        :param str file_name:  path to the UBC GIF mesh file to read
        or just its name if directory is specified
        :param str directory: directory where the UBC GIF file lives

        Output:
        :rtype: numpy.ndarray
        :return: model with TensorMesh ordered
        """
        fname = os.path.join(directory, file_name)
        if mesh.dim == 3:
            model = mesh._readModelUBC_3D(fname)
        elif mesh.dim == 2:
            model = mesh._readModelUBC_2D(fname)
        else:
            raise Exception("mesh must be a Tensor Mesh 2D or 3D")
        return model

    def write_model_UBC(mesh, file_name, model, directory=""):
        """Writes a model associated with a TensorMesh
        to a UBC-GIF format model file.

        Input:
        :param str file_name:  File to write to
        or just its name if directory is specified
        :param str directory: directory where the UBC GIF file lives
        :param numpy.ndarray model: The model
        """
        fname = os.path.join(directory, file_name)
        if mesh.dim == 3:
            # Reshape model to a matrix
            modelMat = mesh.reshape(model, "CC", "CC", "M")
            # Transpose the axes
            modelMatT = modelMat.transpose((2, 0, 1))
            # Flip z to positive down
            modelMatTR = mkvc(modelMatT[::-1, :, :])
            np.savetxt(fname, modelMatTR.ravel())

        elif mesh.dim == 2:
            modelMat = mesh.reshape(model, "CC", "CC", "M").T[::-1]
            f = open(fname, "w")
            f.write("{:d} {:d}\n".format(*mesh.shape_cells))
            f.close()
            f = open(fname, "ab")
            np.savetxt(f, modelMat)
            f.close()

        else:
            raise Exception("mesh must be a Tensor Mesh 2D or 3D")

    def _writeUBC_3DMesh(mesh, file_name, comment_lines=""):
        """Writes a TensorMesh to a UBC-GIF format mesh file.

        Input:
        :param string file_name: File to write to
        :param dict models: A dictionary of the models
        """
        if not mesh.dim == 3:
            raise Exception("Mesh must be 3D")

        s = comment_lines
        s += "{0:d} {1:d} {2:d}\n".format(*tuple(mesh.vnC))
        # Have to it in the same operation or use mesh.origin.copy(),
        # otherwise the mesh.origin is updated.
        origin = mesh.origin + np.array([0, 0, mesh.h[2].sum()])

        nCx, nCy, nCz = mesh.shape_cells
        s += "{0:.6f} {1:.6f} {2:.6f}\n".format(*tuple(origin))
        s += ("%.6f " * nCx + "\n") % tuple(mesh.h[0])
        s += ("%.6f " * nCy + "\n") % tuple(mesh.h[1])
        s += ("%.6f " * nCz + "\n") % tuple(mesh.h[2][::-1])
        f = open(file_name, "w")
        f.write(s)
        f.close()

    def _writeUBC_2DMesh(mesh, file_name, comment_lines=""):
        """Writes a TensorMesh to a UBC-GIF format mesh file.

        Input:
        :param string file_name: File to write to
        :param dict models: A dictionary of the models

        """
        if not mesh.dim == 2:
            raise Exception("Mesh must be 2D")

        def writeF(fx, outStr=""):
            # Init
            i = 0
            origin = True
            x0 = fx[i]
            f = fx[i]
            number_segment = 0
            auxStr = ""

            while True:
                i = i + 1
                if i >= fx.size:
                    break
                dx = -f + fx[i]
                f = fx[i]
                n = 1

                for j in range(i + 1, fx.size):
                    if -f + fx[j] == dx:
                        n += 1
                        i += 1
                        f = fx[j]
                    else:
                        break

                number_segment += 1
                if origin:
                    auxStr += "{:.10f} {:.10f} {:d} \n".format(x0, f, n)
                    origin = False
                else:
                    auxStr += "{:.10f} {:d} \n".format(f, n)

            auxStr = "{:d}\n".format(number_segment) + auxStr
            outStr += auxStr

            return outStr

        # Grab face coordinates
        fx = mesh.nodes_x
        fz = -mesh.nodes_y[::-1]

        # Create the string
        outStr = comment_lines
        outStr = writeF(fx, outStr=outStr)
        outStr += "\n"
        outStr = writeF(fz, outStr=outStr)

        # Write file
        f = open(file_name, "w")
        f.write(outStr)
        f.close()

    def write_UBC(mesh,
                  file_name,
                  models=None,
                  directory="",
                  comment_lines=""):
        """Writes a TensorMesh to a UBC-GIF format mesh file.

        Input:
        :param str file_name: File to write to
        :param str directory: directory where to save model
        :param dict models: A dictionary of the models
        :param str comment_lines: comment lines preceded with '!' to add
        """
        fname = os.path.join(directory, file_name)
        if mesh.dim == 3:
            mesh._writeUBC_3DMesh(fname, comment_lines=comment_lines)
        elif mesh.dim == 2:
            mesh._writeUBC_2DMesh(fname, comment_lines=comment_lines)
        else:
            raise Exception("mesh must be a Tensor Mesh 2D or 3D")

        if models is None:
            return
        if not isinstance(models, dict):
            raise TypeError("models must be a dict")
        for key in models:
            if not isinstance(key, str):
                raise TypeError(
                    "The dict key must be a string representing the file name")
            mesh.write_model_UBC(key, models[key], directory=directory)

    # DEPRECATED
    @classmethod
    def readUBC(TensorMesh, file_name, directory=""):
        warnings.warn(
            "TensorMesh.readUBC has been deprecated and will be removed in"
            "discretize 1.0.0. please use TensorMesh.read_UBC",
            DeprecationWarning,
        )
        return TensorMesh.read_UBC(file_name, directory)

    readModelUBC = deprecate_method("read_model_UBC",
                                    "readModelUBC",
                                    removal_version="1.0.0")
    writeUBC = deprecate_method("write_UBC",
                                "writeUBC",
                                removal_version="1.0.0")
    writeModelUBC = deprecate_method("write_model_UBC",
                                     "writeModelUBC",
                                     removal_version="1.0.0")
示例#4
0
class TreeMeshIO(object):
    """Class for managing the input/output of tree meshes and models.

    The ``TreeMeshIO`` class contains a set of class methods specifically
    for the :class:`~discretize.TreeMesh` class. These include:

        - Read/write tree meshes to file
        - Read/write models defined on tree meshes

    """
    @classmethod
    def read_UBC(TreeMesh, meshFile, directory=""):
        """Read 3D tree mesh (OcTree mesh) from UBC-GIF formatted file.

        Parameters
        ----------
        file_name : str or file name
            full path to the UBC-GIF formatted mesh file or just its name if directory is specified
        directory : str, optional
            directory where the UBC-GIF file lives

        Returns
        -------
        discretize.TreeMesh
            The tree mesh
        """
        fname = os.path.join(directory, meshFile)
        fileLines = np.genfromtxt(fname,
                                  dtype=str,
                                  delimiter="\n",
                                  comments="!")
        nCunderMesh = np.array(fileLines[0].split("!")[0].split(), dtype=int)
        tswCorn = np.array(fileLines[1].split("!")[0].split(), dtype=float)
        smallCell = np.array(fileLines[2].split("!")[0].split(), dtype=float)
        # Read the index array
        indArr = np.genfromtxt(
            (line.encode("utf8") for line in fileLines[4::]), dtype=np.int)
        nCunderMesh = nCunderMesh[:len(
            tswCorn)]  # remove information related to core

        hs = [np.ones(nr) * sz for nr, sz in zip(nCunderMesh, smallCell)]
        origin = tswCorn
        origin[-1] -= np.sum(hs[-1])

        ls = np.log2(nCunderMesh).astype(int)
        # if all ls are equal
        if min(ls) == max(ls):
            max_level = ls[0]
        else:
            max_level = min(ls) + 1

        mesh = TreeMesh(hs, origin=origin)
        levels = indArr[:, -1]
        indArr = indArr[:, :-1]

        indArr -= 1  # shift by 1....
        indArr = 2 * indArr + levels[:, None]  # get cell center index
        indArr[:,
               -1] = 2 * nCunderMesh[-1] - indArr[:,
                                                  -1]  # switch direction of iz
        levels = max_level - np.log2(levels)  # calculate level

        mesh.__setstate__((indArr, levels))
        return mesh

    def read_model_UBC(mesh, file_name):
        """Read UBC-GIF formatted file model file for 3D tree mesh (OcTree).

        Parameters
        ----------
        file_name : str or list of str
            full path to the UBC-GIF formatted model file or
            just its name if directory is specified. It can also be a list of file_names.
        directory : str
            directory where the UBC-GIF file lives (optional)

        Returns
        -------
        (n_cells) numpy.ndarray or dict of [str, (n_cells) numpy.ndarray]
            The model defined on the mesh. If **file_name** is a ``dict``, it is a
            dictionary of models indexed by the file names.
        """

        if type(file_name) is list:
            out = {}
            for f in file_name:
                out[f] = mesh.read_model_UBC(f)
            return out

        modArr = np.loadtxt(file_name)

        ubc_order = mesh._ubc_order
        # order_ubc will re-order from treemesh ordering to UBC ordering
        # need the opposite operation
        un_order = np.empty_like(ubc_order)
        un_order[ubc_order] = np.arange(len(ubc_order))

        model = modArr[un_order].copy()  # ensure a contiguous array
        return model

    def write_UBC(mesh, file_name, models=None, directory=""):
        """Write OcTree mesh (and models) to UBC-GIF formatted files.

        Parameters
        ----------
        file_name : str
            full path for the output mesh file or just its name if directory is specified
        models : dict of [str, (n_cells) numpy.ndarray], optional
            The dictionary key is a string representing the model's name.
            Each model is a 1D numpy array of size (n_cells).
        directory : str, optional
            output directory (optional)
        """
        uniform_hs = np.array([np.allclose(h, h[0]) for h in mesh.h])
        if np.any(~uniform_hs):
            raise Exception("UBC form does not support variable cell widths")
        nCunderMesh = np.array([h.size for h in mesh.h], dtype=np.int64)

        tswCorn = mesh.origin.copy()
        tswCorn[-1] += np.sum(mesh.h[-1])

        smallCell = np.array([h[0] for h in mesh.h])
        nrCells = mesh.nC

        indArr, levels = mesh._ubc_indArr
        ubc_order = mesh._ubc_order

        indArr = indArr[ubc_order]
        levels = levels[ubc_order]

        # Write the UBC octree mesh file
        head = " ".join([f"{int(n)}" for n in nCunderMesh]) + " \n"
        head += " ".join([f"{v:.4f}" for v in tswCorn]) + " \n"
        head += " ".join([f"{v:.3f}" for v in smallCell]) + " \n"
        head += f"{int(nrCells)}"
        np.savetxt(file_name,
                   np.c_[indArr, levels],
                   fmt="%i",
                   header=head,
                   comments="")

        # Print the models
        if models is None:
            return
        if not isinstance(models, dict):
            raise TypeError("models must be a dict")
        for key in models:
            if not isinstance(key, str):
                raise TypeError(
                    "The dict key must be a string representing the file name")
            mesh.write_model_UBC(key, models[key], directory=directory)

    def write_model_UBC(mesh, file_name, model, directory=""):
        """Write 3D tree model (OcTree) to UBC-GIF formatted file.

        Parameters
        ----------
        file_name : str
            full path for the output mesh file or just its name if directory is specified
        model : (n_cells) numpy.ndarray
            model values defined for each cell
        directory : str
            output directory (optional)
        """
        if type(file_name) is list:
            for f, m in zip(file_name, model):
                mesh.write_model_UBC(f, m)
        else:
            ubc_order = mesh._ubc_order
            fname = os.path.join(directory, file_name)
            m = model[ubc_order]
            np.savetxt(fname, m)

    # DEPRECATED
    @classmethod
    def readUBC(TreeMesh, file_name, directory=""):
        """*readUBC* has been deprecated and replaced by *read_UBC*"""
        warnings.warn(
            "TensorMesh.readUBC has been deprecated and will be removed in"
            "discretize 1.0.0. please use TensorMesh.read_UBC",
            FutureWarning,
        )
        return TreeMesh.read_UBC(file_name, directory)

    readModelUBC = deprecate_method("read_model_UBC",
                                    "readModelUBC",
                                    removal_version="1.0.0",
                                    future_warn=True)
    writeUBC = deprecate_method("write_UBC",
                                "writeUBC",
                                removal_version="1.0.0",
                                future_warn=True)
    writeModelUBC = deprecate_method("write_model_UBC",
                                     "writeModelUBC",
                                     removal_version="1.0.0",
                                     future_warn=True)
示例#5
0
class BaseRectangularMesh(BaseMesh):
    """
    BaseRectangularMesh
    """

    _aliases = {
        **BaseMesh._aliases,
        **{
            "vnC": "shape_cells",
            "vnN": "shape_nodes",
            "vnEx": "shape_edges_x",
            "vnEy": "shape_edges_y",
            "vnEz": "shape_edges_z",
            "vnFx": "shape_faces_x",
            "vnFy": "shape_faces_y",
            "vnFz": "shape_faces_z",
        },
    }

    def __init__(self, n=None, origin=None, **kwargs):
        BaseMesh.__init__(self, n=n, origin=origin, **kwargs)

    @property
    def shape_cells(self):
        """The number of cells in each direction

        Returns
        -------
        tuple of ints

        Notes
        -----
        Also accessible as `vnC`.
        """
        return tuple(self._n)

    @property
    def shape_nodes(self):
        """Number of nodes in each direction

        Returns
        -------
        tuple of int

        Notes
        -----
        Also accessible as `vnN`.
        """
        return tuple(x + 1 for x in self.shape_cells)

    @property
    def shape_edges_x(self):
        """Number of x-edges in each direction

        Returns
        -------
        tuple of int
            (nx_cells, ny_nodes, nz_nodes)

        Notes
        -----
        Also accessible as `vnEx`.
        """
        return self.shape_cells[:1] + self.shape_nodes[1:]

    @property
    def shape_edges_y(self):
        """Number of y-edges in each direction

        Returns
        -------
        tuple of int or None
            (nx_nodes, ny_cells, nz_nodes), None if dim < 2

        Notes
        -----
        Also accessible as `vnEy`.
        """
        if self.dim < 2:
            return None
        sc = self.shape_cells
        sn = self.shape_nodes
        return (sn[0], sc[1]) + sn[2:]  # conditionally added if dim == 3!

    @property
    def shape_edges_z(self):
        """Number of z-edges in each direction

        Returns
        -------
        tuple of int or None
            (nx_nodes, ny_nodes, nz_cells), None if dim < 3

        Notes
        -----
        Also accessible as `vnEz`.
        """
        if self.dim < 3:
            return None
        return self.shape_nodes[:2] + self.shape_cells[2:]

    @property
    def shape_faces_x(self):
        """Number of x-faces in each direction

        Returns
        -------
        tuple of int
            (nx_nodes, ny_cells, nz_cells)

        Notes
        -----
        Also accessible as `vnFx`.
        """
        return self.shape_nodes[:1] + self.shape_cells[1:]

    @property
    def shape_faces_y(self):
        """Number of y-faces in each direction

        Returns
        -------
        tuple of int or None
            (nx_cells, ny_nodes, nz_cells), None if dim < 2

        Notes
        -----
        Also accessible as `vnFy`.
        """
        if self.dim < 2:
            return None
        sc = self.shape_cells
        sn = self.shape_nodes
        return (sc[0], sn[1]) + sc[2:]

    @property
    def shape_faces_z(self):
        """Number of z-faces in each direction

        Returns
        -------
        tuple of int or None
            (nx_cells, ny_cells, nz_nodes), None if dim < 3

        Notes
        -----
        Also accessible as `vnFz`.
        """
        if self.dim < 3:
            return None
        return self.shape_cells[:2] + self.shape_nodes[2:]

    ##################################
    # Redo the numbering so they are dependent of the shape tuples
    # these should all inherit the parent's docstrings
    ##################################

    @property
    def n_cells(self):
        return int(np.prod(self.shape_cells))

    @property
    def n_nodes(self):
        return int(np.prod(self.shape_nodes))

    @property
    def n_edges_x(self):
        return int(np.prod(self.shape_edges_x))

    @property
    def n_edges_y(self):
        if self.dim < 2:
            return
        return int(np.prod(self.shape_edges_y))

    @property
    def n_edges_z(self):
        if self.dim < 3:
            return
        return int(np.prod(self.shape_edges_z))

    @property
    def n_faces_x(self):
        return int(np.prod(self.shape_faces_x))

    @property
    def n_faces_y(self):
        if self.dim < 2:
            return
        return int(np.prod(self.shape_faces_y))

    @property
    def n_faces_z(self):
        if self.dim < 3:
            return
        return int(np.prod(self.shape_faces_z))

    def reshape(self,
                x,
                x_type="cell_centers",
                out_type="cell_centers",
                format="V",
                **kwargs):
        """A quick reshape command that will do the best it
        can at giving you what you want.

        For example, you have a face variable, and you want the x
        component of it reshaped to a 3D matrix.

        `reshape` can fulfil your dreams::

            mesh.reshape(V, 'F', 'Fx', 'M')
                         |   |     |    |
                         |   |     |    {
                         |   |     |      How: 'M' or ['V'] for a matrix
                         |   |     |      (ndgrid style) or a vector (n x dim)
                         |   |     |    }
                         |   |     {
                         |   |       What you want: ['CC'], 'N',
                         |   |                       'F', 'Fx', 'Fy', 'Fz',
                         |   |                       'E', 'Ex', 'Ey', or 'Ez'
                         |   |     }
                         |   {
                         |     What is it: ['CC'], 'N',
                         |                  'F', 'Fx', 'Fy', 'Fz',
                         |                  'E', 'Ex', 'Ey', or 'Ez'
                         |   }
                         {
                           The input: as a list or ndarray
                         }


        For example::

            # Separates each component of the Ex grid into 3 matrices
            Xex, Yex, Zex = r(mesh.gridEx, 'Ex', 'Ex', 'M')

            # Given an edge vector, return just the x edges as a vector
            XedgeVector = r(edgeVector, 'E', 'Ex', 'V')

            # Separates each component of the edgeVector into 3 vectors
            eX, eY, eZ = r(edgeVector, 'E', 'E', 'V')
        """
        if "xType" in kwargs:
            warnings.warn(
                "The xType keyword argument has been deprecated, please use x_type. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            x_type = kwargs["xType"]
        if "outType" in kwargs:
            warnings.warn(
                "The outType keyword argument has been deprecated, please use out_type. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            out_type = kwargs["outType"]

        x_type = self._parse_location_type(x_type)
        out_type = self._parse_location_type(out_type)

        allowed_x_type = [
            "cell_centers", "nodes", "faces", "faces_x", "faces_y", "faces_z",
            "edges", "edges_x", "edges_y", "edges_z"
        ]
        if not (isinstance(x, list) or isinstance(x, np.ndarray)):
            raise Exception("x must be either a list or a ndarray")
        if x_type not in allowed_x_type:
            raise Exception("x_type must be either '" +
                            "', '".join(allowed_x_type) + "'")
        if out_type not in allowed_x_type:
            raise Exception("out_type must be either '" +
                            "', '".join(allowed_x_type) + "'")
        if format not in ["M", "V"]:
            raise Exception("format must be either 'M' or 'V'")
        if out_type[:len(x_type)] != x_type:
            raise Exception("You cannot change types when reshaping.")
        if x_type not in out_type:
            raise Exception("You cannot change type of components.")

        if isinstance(x, list):
            for i, xi in enumerate(x):
                if not isinstance(x, np.ndarray):
                    raise Exception("x[{0:d}] must be a numpy array".format(i))
                if xi.size != x[0].size:
                    raise Exception(
                        "Number of elements in list must not change.")

            x_array = np.ones((x.size, len(x)))
            # Unwrap it and put it in a np array
            for i, xi in enumerate(x):
                x_array[:, i] = mkvc(xi)
            x = x_array

        if not isinstance(x, np.ndarray):
            raise Exception("x must be a numpy array")

        x = x[:]  # make a copy.
        x_type_is_FE_xyz = (len(x_type) > 1 and x_type[0] in ["f", "e"]
                            and x_type[-1] in ["x", "y", "z"])

        def outKernal(xx, nn):
            """Returns xx as either a matrix (shape == nn) or a vector."""
            if format == "M":
                return xx.reshape(nn, order="F")
            elif format == "V":
                return mkvc(xx)

        def switchKernal(xx):
            """Switches over the different options."""
            if x_type in ["cell_centers", "nodes"]:
                nn = self.shape_cells if x_type == "cell_centers" else self.shape_nodes
                if xx.size != np.prod(nn):
                    raise Exception("Number of elements must not change.")
                return outKernal(xx, nn)
            elif x_type in ["faces", "edges"]:
                # This will only deal with components of fields,
                # not full 'F' or 'E'
                xx = mkvc(xx)  # unwrap it in case it is a matrix
                if x_type == "faces":
                    nn = (self.nFx, self.nFy, self.nFz)[:self.dim]
                else:
                    nn = (self.nEx, self.nEy, self.nEz)[:self.dim]
                nn = np.r_[0, nn]

                nx = [0, 0, 0]
                nx[0] = self.shape_faces_x if x_type == "faces" else self.shape_edges_x
                nx[1] = self.shape_faces_y if x_type == "faces" else self.shape_edges_y
                nx[2] = self.shape_faces_z if x_type == "faces" else self.shape_edges_z

                for dim, dimName in enumerate(["x", "y", "z"]):
                    if dimName in out_type:
                        if self.dim <= dim:
                            raise Exception(
                                "Dimensions of mesh not great enough for "
                                "{}_{}".format(x_type, dimName))
                        if xx.size != np.sum(nn):
                            raise Exception("Vector is not the right size.")
                        start = np.sum(nn[:dim + 1])
                        end = np.sum(nn[:dim + 2])
                        return outKernal(xx[start:end], nx[dim])

            elif x_type_is_FE_xyz:
                # This will deal with partial components (x, y or z)
                # lying on edges or faces
                if "x" in x_type:
                    nn = self.shape_faces_x if "f" in x_type else self.shape_edges_x
                elif "y" in x_type:
                    nn = self.shape_faces_y if "f" in x_type else self.shape_edges_y
                elif "z" in x_type:
                    nn = self.shape_faces_z if "f" in x_type else self.shape_edges_z
                if xx.size != np.prod(nn):
                    raise Exception(
                        f"Vector is not the right size. Expected {np.prod(nn)}, got {xx.size}"
                    )
                return outKernal(xx, nn)

        # Check if we are dealing with a vector quantity
        isVectorQuantity = len(x.shape) == 2 and x.shape[1] == self.dim

        if out_type in ["faces", "edges"]:
            if isVectorQuantity:
                raise Exception(
                    "Not sure what to do with a vector vector quantity..")
            outTypeCopy = out_type
            out = ()
            for ii, dirName in enumerate(["x", "y", "z"][:self.dim]):
                out_type = outTypeCopy + "_" + dirName
                out += (switchKernal(x), )
            return out
        elif isVectorQuantity:
            out = ()
            for ii in range(x.shape[1]):
                out += (switchKernal(x[:, ii]), )
            return out
        else:
            return switchKernal(x)

    # DEPRECATED
    r = deprecate_method("reshape", "r", removal_version="1.0.0")

    @property
    def nCx(self):
        """Number of cells in the x direction

        Returns
        -------
        int

        .. deprecated:: 0.5.0
          `nCx` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_cells[0]` to reduce namespace clutter.
        """

        warnings.warn(
            "nCx has been deprecated, please access as mesh.shape_cells[0]",
            DeprecationWarning,
        )
        return self.shape_cells[0]

    @property
    def nCy(self):
        """Number of cells in the y direction

        Returns
        -------
        int or None
            None if dim < 2

        .. deprecated:: 0.5.0
          `nCy` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_cells[1]` to reduce namespace clutter.
        """

        warnings.warn(
            "nCy has been deprecated, please access as mesh.shape_cells[1]",
            DeprecationWarning,
        )
        if self.dim < 2:
            return None
        return self.shape_cells[1]

    @property
    def nCz(self):
        """Number of cells in the z direction

        Returns
        -------
        int or None
            None if dim < 3

        .. deprecated:: 0.5.0
          `nCz` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_cells[2]` to reduce namespace clutter.
        """

        warnings.warn(
            "nCz has been deprecated, please access as mesh.shape_cells[2]",
            DeprecationWarning,
        )
        if self.dim < 3:
            return None
        return self.shape_cells[2]

    @property
    def nNx(self):
        """Number of nodes in the x-direction

        Returns
        -------
        int

        .. deprecated:: 0.5.0
          `nNx` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_nodes[0]` to reduce namespace clutter.
        """

        warnings.warn(
            "nNx has been deprecated, please access as mesh.shape_nodes[0]",
            DeprecationWarning,
        )
        return self.shape_nodes[0]

    @property
    def nNy(self):
        """Number of nodes in the y-direction

        Returns
        -------
        int or None
            None if dim < 2

        .. deprecated:: 0.5.0
          `nNy` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_nodes[1]` to reduce namespace clutter.
        """

        warnings.warn(
            "nNy has been deprecated, please access as mesh.shape_nodes[1]",
            DeprecationWarning,
        )
        if self.dim < 2:
            return None
        return self.shape_nodes[1]

    @property
    def nNz(self):
        """Number of nodes in the z-direction

        Returns
        -------
        int or None
            None if dim < 3

        .. deprecated:: 0.5.0
          `nNz` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_nodes[2]` to reduce namespace clutter.
        """

        warnings.warn(
            "nNz has been deprecated, please access as mesh.shape_nodes[2]",
            DeprecationWarning,
        )
        if self.dim < 3:
            return None
        return self.shape_nodes[2]
示例#6
0
class BaseMesh(properties.HasProperties, InterfaceMixins):
    """
    BaseMesh does all the counting you don't want to do.
    BaseMesh should be inherited by meshes with a regular structure.
    """

    _REGISTRY = {}
    _aliases = {
        "nC": "n_cells",
        "nN": "n_nodes",
        "nEx": "n_edges_x",
        "nEy": "n_edges_y",
        "nEz": "n_edges_z",
        "nE": "n_edges",
        "vnE": "n_edges_per_direction",
        "nFx": "n_faces_x",
        "nFy": "n_faces_y",
        "nFz": "n_faces_z",
        "nF": "n_faces",
        "vnF": "n_faces_per_direction",
    }

    # Properties
    _n = properties.Tuple(
        "Tuple of number of cells in each direction (dim, )",
        prop=properties.Integer("Number of cells along a particular direction",
                                cast=True,
                                min=1),
        min_length=1,
        max_length=3,
        coerce=True,
        required=True,
    )

    origin = properties.Array(
        "origin of the mesh (dim, )",
        dtype=(float, int),
        shape=("*", ),
        required=True,
    )

    # Instantiate the class
    def __init__(self, n=None, origin=None, **kwargs):
        if n is not None:
            self._n = n  # number of dimensions

        if "x0" in kwargs:
            origin = kwargs.pop('x0')
        if origin is None:
            self.origin = np.zeros(len(self._n))
        else:
            self.origin = origin

        super(BaseMesh, self).__init__(**kwargs)

    def __getattr__(self, name):
        if name == "_aliases":
            raise AttributeError  # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
        name = self._aliases.get(name, name)
        return object.__getattribute__(self, name)

    @property
    def x0(self):
        return self.origin

    @x0.setter
    def x0(self, val):
        self.origin = val

    @classmethod
    def deserialize(cls, value, **kwargs):
        if "x0" in value:
            value["origin"] = value.pop("x0")
        return super().deserialize(value, **kwargs)

    # Validators
    @properties.validator("_n")
    def _check_n_shape(self, change):
        if change["previous"] != properties.undefined:
            # _n can only be set once
            if change["previous"] != change["value"]:
                raise AttributeError(
                    "Cannot change n. Instead, create a new mesh")
        else:
            # check that if h has been set, sizes still agree
            if getattr(self, "h", None) is not None and len(self.h) > 0:
                for i in range(len(change["value"])):
                    if len(self.h[i]) != change["value"][i]:
                        raise properties.ValidationError(
                            "Mismatched shape of n. Expected {}, len(h[{}]), got "
                            "{}".format(len(self.h[i]), i, change["value"][i]))

            # check that if nodes have been set for curvi mesh, sizes still
            # agree
            if getattr(self, "node_list",
                       None) is not None and len(self.node_list) > 0:
                for i in range(len(change["value"])):
                    if self.node_list[0].shape[i] - 1 != change["value"][i]:
                        raise properties.ValidationError(
                            "Mismatched shape of n. Expected {}, len(node_list[{}]), "
                            "got {}".format(self.node_list[0].shape[i] - 1, i,
                                            change["value"][i]))

    @properties.validator("origin")
    def _check_origin(self, change):
        if not (not isinstance(change["value"], properties.utils.Sentinel)
                and change["value"] is not None):
            raise Exception("n must be set prior to setting origin")

        if len(self._n) != len(change["value"]):
            raise Exception(
                "Dimension mismatch. origin has length {} != len(n) which is "
                "{}".format(len(self.origin), len(self._n)))

    @property
    def dim(self):
        """The dimension of the mesh (1, 2, or 3).

        Returns
        -------
        int
            dimension of the mesh
        """
        return len(self._n)

    @property
    def n_cells(self):
        """Total number of cells in the mesh.

        Returns
        -------
        int
            number of cells in the mesh

        Notes
        -----
        Also accessible as `nC`.

        Examples
        --------
        >>> import discretize
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> mesh.plot_grid(centers=True, show_it=True)
        >>> print(mesh.n_cells)
        """
        return int(np.prod(self._n))

    def __len__(self):
        """The number of cells on the mesh."""
        return self.n_cells

    @property
    def n_nodes(self):
        """Total number of nodes

        Returns
        -------
        int
            number of nodes in the mesh

        Notes
        -----
        Also accessible as `nN`.

        Examples
        --------
        >>> import discretize
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> mesh.plot_grid(nodes=True, show_it=True)
        >>> print(mesh.n_nodes)
        """
        return int(np.prod(x + 1 for x in self._n))

    @property
    def n_edges_x(self):
        """Number of x-edges

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nEx`.

        """
        return int(np.prod(x + y for x, y in zip(self._n, (0, 1, 1))))

    @property
    def n_edges_y(self):
        """Number of y-edges

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nEy`.

        """
        if self.dim < 2:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (1, 0, 1))))

    @property
    def n_edges_z(self):
        """Number of z-edges

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nEz`.

        """
        if self.dim < 3:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (1, 1, 0))))

    @property
    def n_edges_per_direction(self):
        """The number of edges in each direction

        Returns
        -------
        n_edges_per_direction : tuple
            [n_edges_x, n_edges_y, n_edges_z], (dim, )

        Notes
        -----
        Also accessible as `vnE`.

        Examples
        --------
        >>> import discretize
        >>> import matplotlib.pyplot as plt
        >>> import numpy as np
        >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> M.plot_grid(edges=True, show_it=True)
        """
        return tuple(x
                     for x in [self.n_edges_x, self.n_edges_y, self.n_edges_z]
                     if x is not None)

    @property
    def n_edges(self):
        """Total number of edges.

        Returns
        -------
        int
            sum([n_edges_x, n_edges_y, n_edges_z])

        Notes
        -----
        Also accessible as `nE`.

        """
        n = self.n_edges_x
        if self.dim > 1:
            n += self.n_edges_y
        if self.dim > 2:
            n += self.n_edges_z
        return n

    @property
    def n_faces_x(self):
        """Number of x-faces

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nFx`.
        """
        return int(np.prod(x + y for x, y in zip(self._n, (1, 0, 0))))

    @property
    def n_faces_y(self):
        """Number of y-faces

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nFy`.
        """
        if self.dim < 2:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (0, 1, 0))))

    @property
    def n_faces_z(self):
        """Number of z-faces

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nFz`.
        """
        if self.dim < 3:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (0, 0, 1))))

    @property
    def n_faces_per_direction(self):
        """The number of faces in each direction

        Returns
        -------
        n_faces_per_direction : tuple
            [n_faces_x, n_faces_y, n_faces_z], (dim, )

        Notes
        -----
        Also accessible as `vnF`.

        Examples
        --------
        >>> import discretize
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> M.plot_grid(faces=True, show_it=True)
        """
        return tuple(x
                     for x in [self.n_faces_x, self.n_faces_y, self.n_faces_z]
                     if x is not None)

    @property
    def n_faces(self):
        """Total number of faces.

        Returns
        -------
        int
            sum([n_faces_x, n_faces_y, n_faces_z])

        Notes
        -----
        Also accessible as `nF`.

        """
        n = self.n_faces_x
        if self.dim > 1:
            n += self.n_faces_y
        if self.dim > 2:
            n += self.n_faces_z
        return n

    @property
    def face_normals(self):
        """Face Normals

        Returns
        -------
        numpy.ndarray
            normals, (n_faces, dim)
        """
        if self.dim == 2:
            nX = np.c_[np.ones(self.n_faces_x), np.zeros(self.n_faces_x)]
            nY = np.c_[np.zeros(self.n_faces_y), np.ones(self.n_faces_y)]
            return np.r_[nX, nY]
        elif self.dim == 3:
            nX = np.c_[np.ones(self.n_faces_x),
                       np.zeros(self.n_faces_x),
                       np.zeros(self.n_faces_x), ]
            nY = np.c_[np.zeros(self.n_faces_y),
                       np.ones(self.n_faces_y),
                       np.zeros(self.n_faces_y), ]
            nZ = np.c_[np.zeros(self.n_faces_z),
                       np.zeros(self.n_faces_z),
                       np.ones(self.n_faces_z), ]
            return np.r_[nX, nY, nZ]

    @property
    def edge_tangents(self):
        """Edge Tangents

        Returns
        -------
        numpy.ndarray
            normals, (n_edges, dim)
        """
        if self.dim == 2:
            tX = np.c_[np.ones(self.n_edges_x), np.zeros(self.n_edges_x)]
            tY = np.c_[np.zeros(self.n_edges_y), np.ones(self.n_edges_y)]
            return np.r_[tX, tY]
        elif self.dim == 3:
            tX = np.c_[np.ones(self.n_edges_x),
                       np.zeros(self.n_edges_x),
                       np.zeros(self.n_edges_x), ]
            tY = np.c_[np.zeros(self.n_edges_y),
                       np.ones(self.n_edges_y),
                       np.zeros(self.n_edges_y), ]
            tZ = np.c_[np.zeros(self.n_edges_z),
                       np.zeros(self.n_edges_z),
                       np.ones(self.n_edges_z), ]
            return np.r_[tX, tY, tZ]

    def project_face_vector(self, face_vector):
        """Project vectors onto the faces of the mesh.

        Given a vector, face_vector, in cartesian coordinates, this will project
        it onto the mesh using the normals

        Parameters
        ----------
        face_vector : numpy.ndarray
            face vector with shape (n_faces, dim)

        Returns
        -------
        numpy.ndarray
            projected face vector, (n_faces, )

        """
        if not isinstance(face_vector, np.ndarray):
            raise Exception("face_vector must be an ndarray")
        if not (len(face_vector.shape) == 2 and face_vector.shape[0]
                == self.n_faces and face_vector.shape[1] == self.dim):
            raise Exception(
                "face_vector must be an ndarray of shape (n_faces x dim)")
        return np.sum(face_vector * self.face_normals, 1)

    def project_edge_vector(self, edge_vector):
        """Project vectors onto the edges of the mesh

        Given a vector, edge_vector, in cartesian coordinates, this will project
        it onto the mesh using the tangents

        Parameters
        ----------
        edge_vector : numpy.ndarray
            edge vector with shape (n_edges, dim)

        Returns
        -------
        numpy.ndarray
            projected edge vector, (n_edges, )

        """
        if not isinstance(edge_vector, np.ndarray):
            raise Exception("edge_vector must be an ndarray")
        if not (len(edge_vector.shape) == 2 and edge_vector.shape[0]
                == self.n_edges and edge_vector.shape[1] == self.dim):
            raise Exception(
                "edge_vector must be an ndarray of shape (nE x dim)")
        return np.sum(edge_vector * self.edge_tangents, 1)

    def save(self, file_name="mesh.json", verbose=False, **kwargs):
        """
        Save the mesh to json
        :param str file: file_name for saving the casing properties
        :param str directory: working directory for saving the file
        """

        if 'filename' in kwargs:
            file_name = kwargs['filename']
            warnings.warn(
                "The filename keyword argument has been deprecated, please use file_name. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
        f = os.path.abspath(
            file_name)  # make sure we are working with abs path
        with open(f, "w") as outfile:
            json.dump(self.serialize(), outfile)

        if verbose:
            print("Saved {}".format(f))

        return f

    def copy(self):
        """
        Make a copy of the current mesh
        """
        return properties.copy(self)

    axis_u = properties.Vector3(
        "Vector orientation of u-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.",
        default="X",
        length=1,
    )
    axis_v = properties.Vector3(
        "Vector orientation of v-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.",
        default="Y",
        length=1,
    )
    axis_w = properties.Vector3(
        "Vector orientation of w-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.",
        default="Z",
        length=1,
    )

    @properties.validator
    def _validate_orientation(self):
        """Check if axes are orthogonal"""
        tol = 1E-6
        if not (np.abs(self.axis_u.dot(self.axis_v) < tol)
                and np.abs(self.axis_v.dot(self.axis_w) < tol)
                and np.abs(self.axis_w.dot(self.axis_u) < tol)):
            raise ValueError("axis_u, axis_v, and axis_w must be orthogonal")
        return True

    @property
    def reference_is_rotated(self):
        """True if the axes are rotated from the traditional <X,Y,Z> system
        with vectors of :math:`(1,0,0)`, :math:`(0,1,0)`, and :math:`(0,0,1)`
        """
        if (np.allclose(self.axis_u,
                        (1, 0, 0)) and np.allclose(self.axis_v, (0, 1, 0))
                and np.allclose(self.axis_w, (0, 0, 1))):
            return False
        return True

    @property
    def rotation_matrix(self):
        """Builds a rotation matrix to transform coordinates from their coordinate
        system into a conventional cartesian system. This is built off of the
        three `axis_u`, `axis_v`, and `axis_w` properties; these mapping
        coordinates use the letters U, V, and W (the three letters preceding X,
        Y, and Z in the alphabet) to define the projection of the X, Y, and Z
        durections. These UVW vectors describe the placement and transformation
        of the mesh's coordinate sytem assuming at most 3 directions.

        Why would you want to use these UVW mapping vectors the this
        `rotation_matrix` property? They allow us to define the realationship
        between local and global coordinate systems and provide a tool for
        switching between the two while still maintaing the connectivity of the
        mesh's cells. For a visual example of this, please see the figure in the
        docs for the :class:`~discretize.mixins.vtk_mod.InterfaceVTK`.
        """
        return np.array([self.axis_u, self.axis_v, self.axis_w])

    reference_system = properties.String(
        "The type of coordinate reference frame. Can take on the values " +
        "cartesian, cylindrical, or spherical. Abbreviations of these are allowed.",
        default="cartesian",
        change_case="lower",
    )

    @properties.validator
    def _validate_reference_system(self):
        """Check if the reference system is of a known type."""
        choices = ["cartesian", "cylindrical", "spherical"]
        # Here are a few abbreviations that users can harnes
        abrevs = {
            "car": choices[0],
            "cart": choices[0],
            "cy": choices[1],
            "cyl": choices[1],
            "sph": choices[2],
        }
        # Get the name and fix it if it is abbreviated
        self.reference_system = abrevs.get(self.reference_system,
                                           self.reference_system)
        if self.reference_system not in choices:
            raise ValueError("Coordinate system ({}) unknown.".format(
                self.reference_system))
        return True

    def _parse_location_type(self, location_type):
        if len(location_type) == 0:
            return location_type
        elif location_type[0] == "F":
            if len(location_type) > 1:
                return "faces_" + location_type[-1]
            else:
                return "faces"
        elif location_type[0] == "E":
            if len(location_type) > 1:
                return "edges_" + location_type[-1]
            else:
                return "edges"
        elif location_type[0] == "N":
            return "nodes"
        elif location_type[0] == "C":
            if len(location_type) > 2:
                return "cell_centers_" + location_type[-1]
            else:
                return "cell_centers"
        else:
            return location_type

    # DEPRECATED
    normals = deprecate_property("face_normals",
                                 "normals",
                                 removal_version="1.0.0")
    tangents = deprecate_property("edge_tangents",
                                  "tangents",
                                  removal_version="1.0.0")
    projectEdgeVector = deprecate_method("project_edge_vector",
                                         "projectEdgeVector",
                                         removal_version="1.0.0")
    projectFaceVector = deprecate_method("project_face_vector",
                                         "projectFaceVector",
                                         removal_version="1.0.0")
示例#7
0
class BaseTensorMesh(BaseMesh):
    """
    Base class for tensor-product style meshes

    This class contains properites and methods that are common to cartesian
    and cylindrical meshes defined by tensor-produts of vectors describing
    cell spacings.

    Do not use this class directly, instead, inherit it if you plan to develop
    a tensor-style mesh (e.g. a spherical mesh) or use the
    :meth:`discretize.TensorMesh` class to create a cartesian tensor mesh.

    """

    _meshType = "BASETENSOR"
    _aliases = {
        **BaseMesh._aliases,
        **{
            "gridCC": "cell_centers",
            "gridN": "nodes",
            "gridFx": "faces_x",
            "gridFy": "faces_y",
            "gridFz": "faces_z",
            "gridEx": "edges_x",
            "gridEy": "edges_y",
            "gridEz": "edges_z",
        },
    }

    _unitDimensions = [1, 1, 1]

    # properties
    h = properties.Tuple(
        "h is a list containing the cell widths of the tensor mesh in each "
        "dimension.",
        properties.Array(
            "widths of the tensor mesh in a single dimension",
            dtype=float,
            shape=("*", ),
        ),
        min_length=1,
        max_length=3,
        coerce=True,
        required=True,
    )

    def __init__(self, h=None, origin=None, **kwargs):

        h_in = h
        if "x0" in kwargs:
            origin = kwargs.pop('x0')
        origin_in = origin

        # Sanity Checks
        if not isinstance(h_in, (list, tuple)):
            raise TypeError("h_in must be a list, not {}".format(type(h_in)))
        if len(h_in) not in [1, 2, 3]:
            raise ValueError(
                "h_in must be of dimension 1, 2, or 3 not {}".format(
                    len(h_in)))

        # build h
        h = list(range(len(h_in)))
        for i, h_i in enumerate(h_in):
            if is_scalar(h_i) and not isinstance(h_i, np.ndarray):
                # This gives you something over the unit cube.
                h_i = self._unitDimensions[i] * np.ones(int(h_i)) / int(h_i)
            elif isinstance(h_i, (list, tuple)):
                h_i = unpack_widths(h_i)
            if not isinstance(h_i, np.ndarray):
                raise TypeError("h[{0:d}] is not a numpy array.".format(i))
            if len(h_i.shape) != 1:
                raise ValueError(
                    "h[{0:d}] must be a 1D numpy array.".format(i))
            h[i] = h_i[:]  # make a copy.

        # Origin of the mesh
        origin = np.zeros(len(h))

        if origin_in is not None:
            if len(h) != len(origin_in):
                raise ValueError("Dimension mismatch. origin != len(h)")
            for i in range(len(h)):
                x_i, h_i = origin_in[i], h[i]
                if is_scalar(x_i):
                    origin[i] = x_i
                elif x_i == "0":
                    origin[i] = 0.0
                elif x_i == "C":
                    origin[i] = -h_i.sum() * 0.5
                elif x_i == "N":
                    origin[i] = -h_i.sum()
                else:
                    raise Exception(
                        "origin[{0:d}] must be a scalar or '0' to be zero, "
                        "'C' to center, or 'N' to be negative. The input value"
                        " {1} {2} is invalid".format(i, x_i, type(x_i)))

        if "n" in kwargs.keys():
            n = kwargs.pop("n")
            if np.any(n != np.array([x.size for x in h])):
                raise ValueError(
                    "Dimension mismatch. The provided n doesn't h")
        else:
            n = np.array([x.size for x in h])

        super(BaseTensorMesh, self).__init__(n, origin=origin, **kwargs)

        # Ensure h contains 1D vectors
        self.h = [mkvc(x.astype(float)) for x in h]

    @property
    def nodes_x(self):
        """Nodal grid vector (1D) in the x direction."""
        return np.r_[self.origin[0], self.h[0]].cumsum()

    @property
    def nodes_y(self):
        """Nodal grid vector (1D) in the y direction."""
        return None if self.dim < 2 else np.r_[self.origin[1],
                                               self.h[1]].cumsum()

    @property
    def nodes_z(self):
        """Nodal grid vector (1D) in the z direction."""
        return None if self.dim < 3 else np.r_[self.origin[2],
                                               self.h[2]].cumsum()

    @property
    def cell_centers_x(self):
        """Cell-centered grid vector (1D) in the x direction."""
        nodes = self.nodes_x
        return (nodes[1:] + nodes[:-1]) / 2

    @property
    def cell_centers_y(self):
        """Cell-centered grid vector (1D) in the y direction."""
        if self.dim < 2:
            return None
        nodes = self.nodes_y
        return (nodes[1:] + nodes[:-1]) / 2

    @property
    def cell_centers_z(self):
        """Cell-centered grid vector (1D) in the z direction."""
        if self.dim < 3:
            return None
        nodes = self.nodes_z
        return (nodes[1:] + nodes[:-1]) / 2

    @property
    def cell_centers(self):
        """Cell-centered grid."""
        return self._getTensorGrid("cell_centers")

    @property
    def nodes(self):
        """Nodal grid."""
        return self._getTensorGrid("nodes")

    @property
    def h_gridded(self):
        """
        Returns an (nC, dim) numpy array with the widths of all cells in order
        """
        if self.dim == 1:
            return self.h[0][:, None]
        return ndgrid(*self.h)

    @property
    def faces_x(self):
        """Face staggered grid in the x direction."""
        if self.nFx == 0:
            return
        return self._getTensorGrid("faces_x")

    @property
    def faces_y(self):
        """Face staggered grid in the y direction."""
        if self.nFy == 0 or self.dim < 2:
            return
        return self._getTensorGrid("faces_y")

    @property
    def faces_z(self):
        """Face staggered grid in the z direction."""
        if self.nFz == 0 or self.dim < 3:
            return
        return self._getTensorGrid("faces_z")

    @property
    def edges_x(self):
        """Edge staggered grid in the x direction."""
        if self.nEx == 0:
            return
        return self._getTensorGrid("edges_x")

    @property
    def edges_y(self):
        """Edge staggered grid in the y direction."""
        if self.nEy == 0 or self.dim < 2:
            return
        return self._getTensorGrid("edges_y")

    @property
    def edges_z(self):
        """Edge staggered grid in the z direction."""
        if self.nEz == 0 or self.dim < 3:
            return
        return self._getTensorGrid("edges_z")

    def _getTensorGrid(self, key):
        if getattr(self, "_" + key, None) is None:
            setattr(self, "_" + key, ndgrid(self.get_tensor(key)))
        return getattr(self, "_" + key)

    def get_tensor(self, key):
        """Returns a tensor list.

        Parameters
        ----------
        key : str
            Which tensor (see below)

            key can be::

                'CC', 'cell_centers' -> location of cell centers
                'N', 'nodes'         -> location of nodes
                'Fx', 'faces_x'      -> location of faces with an x normal
                'Fy', 'faces_y'      -> location of faces with an y normal
                'Fz', 'faces_z'      -> location of faces with an z normal
                'Ex', 'edges_x'      -> location of edges with an x tangent
                'Ey', 'edges_y'      -> location of edges with an y tangent
                'Ez', 'edges_z'      -> location of edges with an z tangent

        Returns
        -------
        list
            list of the tensors that make up the mesh.

        """
        key = self._parse_location_type(key)

        if key == "faces_x":
            ten = [
                self.nodes_x,
                self.cell_centers_y,
                self.cell_centers_z,
            ]
        elif key == "faces_y":
            ten = [
                self.cell_centers_x,
                self.nodes_y,
                self.cell_centers_z,
            ]
        elif key == "faces_z":
            ten = [
                self.cell_centers_x,
                self.cell_centers_y,
                self.nodes_z,
            ]
        elif key == "edges_x":
            ten = [self.cell_centers_x, self.nodes_y, self.nodes_z]
        elif key == "edges_y":
            ten = [self.nodes_x, self.cell_centers_y, self.nodes_z]
        elif key == "edges_z":
            ten = [self.nodes_x, self.nodes_y, self.cell_centers_z]
        elif key == "cell_centers":
            ten = [
                self.cell_centers_x,
                self.cell_centers_y,
                self.cell_centers_z,
            ]
        elif key == "nodes":
            ten = [self.nodes_x, self.nodes_y, self.nodes_z]
        else:
            raise KeyError(r"Unrecognized key {key}")

        return [t for t in ten if t is not None]

    # --------------- Methods ---------------------

    def is_inside(self, pts, location_type="nodes", **kwargs):
        """
        Determines if a set of points are inside a mesh.

        :param numpy.ndarray pts: Location of points to test
        :rtype: numpy.ndarray
        :return: inside, numpy array of booleans
        """
        if "locType" in kwargs:
            warnings.warn(
                "The locType keyword argument has been deprecated, please use location_type. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            location_type = kwargs["locType"]
        pts = as_array_n_by_dim(pts, self.dim)

        tensors = self.get_tensor(location_type)

        if location_type[0].lower() == "n" and self._meshType == "CYL":
            # NOTE: for a CYL mesh we add a node to check if we are inside in
            # the radial direction!
            tensors[0] = np.r_[0.0, tensors[0]]
            tensors[1] = np.r_[tensors[1], 2.0 * np.pi]

        inside = np.ones(pts.shape[0], dtype=bool)
        for i, tensor in enumerate(tensors):
            TOL = np.diff(tensor).min() * 1.0e-10
            inside = (inside
                      & (pts[:, i] >= tensor.min() - TOL)
                      & (pts[:, i] <= tensor.max() + TOL))
        return inside

    def _getInterpolationMat(self,
                             loc,
                             location_type="cell_centers",
                             zeros_outside=False):
        """Produces interpolation matrix

        Parameters
        ----------
        loc : numpy.ndarray
            Location of points to interpolate to

        location_type: stc
            What to interpolate

            location_type can be::

                'Ex', 'edges_x'           -> x-component of field defined on x edges
                'Ey', 'edges_y'           -> y-component of field defined on y edges
                'Ez', 'edges_z'           -> z-component of field defined on z edges
                'Fx', 'faces_x'           -> x-component of field defined on x faces
                'Fy', 'faces_y'           -> y-component of field defined on y faces
                'Fz', 'faces_z'           -> z-component of field defined on z faces
                'N', 'nodes'              -> scalar field defined on nodes
                'CC', 'cell_centers'      -> scalar field defined on cell centers
                'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers
                'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers
                'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers

        Returns
        -------
        scipy.sparse.csr_matrix
            M, the interpolation matrix

        """

        loc = as_array_n_by_dim(loc, self.dim)

        if not zeros_outside:
            if not np.all(self.is_inside(loc)):
                raise ValueError("Points outside of mesh")
        else:
            indZeros = np.logical_not(self.is_inside(loc))
            loc[indZeros, :] = np.array(
                [v.mean() for v in self.get_tensor("CC")])

        location_type = self._parse_location_type(location_type)

        if location_type in [
                "faces_x", "faces_y", "faces_z", "edges_x", "edges_y",
                "edges_z"
        ]:
            ind = {"x": 0, "y": 1, "z": 2}[location_type[-1]]
            if self.dim < ind:
                raise ValueError("mesh is not high enough dimension.")
            if "f" in location_type.lower():
                items = (self.nFx, self.nFy, self.nFz)[:self.dim]
            else:
                items = (self.nEx, self.nEy, self.nEz)[:self.dim]
            components = [spzeros(loc.shape[0], n) for n in items]
            components[ind] = interpolation_matrix(
                loc, *self.get_tensor(location_type))
            # remove any zero blocks (hstack complains)
            components = [comp for comp in components if comp.shape[1] > 0]
            Q = sp.hstack(components)

        elif location_type in ["cell_centers", "nodes"]:
            Q = interpolation_matrix(loc, *self.get_tensor(location_type))

        elif location_type in [
                "cell_centers_x", "cell_centers_y", "cell_centers_z"
        ]:
            Q = interpolation_matrix(loc, *self.get_tensor("CC"))
            Z = spzeros(loc.shape[0], self.nC)
            if location_type[-1] == "x":
                Q = sp.hstack([Q, Z, Z])
            elif location_type[-1] == "y":
                Q = sp.hstack([Z, Q, Z])
            elif location_type[-1] == "z":
                Q = sp.hstack([Z, Z, Q])

        else:
            raise NotImplementedError("getInterpolationMat: location_type==" +
                                      location_type + " and mesh.dim==" +
                                      str(self.dim))

        if zeros_outside:
            Q[indZeros, :] = 0

        return Q.tocsr()

    def get_interpolation_matrix(self,
                                 loc,
                                 location_type="cell_centers",
                                 zeros_outside=False,
                                 **kwargs):
        """Produces linear interpolation matrix

        Parameters
        ----------
        loc : numpy.ndarray
            Location of points to interpolate to

        location_type : str
            What to interpolate (see below)

            location_type can be::

                'Ex', 'edges_x'           -> x-component of field defined on x edges
                'Ey', 'edges_y'           -> y-component of field defined on y edges
                'Ez', 'edges_z'           -> z-component of field defined on z edges
                'Fx', 'faces_x'           -> x-component of field defined on x faces
                'Fy', 'faces_y'           -> y-component of field defined on y faces
                'Fz', 'faces_z'           -> z-component of field defined on z faces
                'N', 'nodes'              -> scalar field defined on nodes
                'CC', 'cell_centers'      -> scalar field defined on cell centers
                'CCVx', 'cell_centers_x'  -> x-component of vector field defined on cell centers
                'CCVy', 'cell_centers_y'  -> y-component of vector field defined on cell centers
                'CCVz', 'cell_centers_z'  -> z-component of vector field defined on cell centers

        Returns
        -------

        scipy.sparse.csr_matrix
            M, the interpolation matrix

        """
        if "locType" in kwargs:
            warnings.warn(
                "The locType keyword argument has been deprecated, please use location_type. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            location_type = kwargs["locType"]
        if "zerosOutside" in kwargs:
            warnings.warn(
                "The zerosOutside keyword argument has been deprecated, please use zeros_outside. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
            zeros_outside = kwargs["zerosOutside"]
        return self._getInterpolationMat(loc, location_type, zeros_outside)

    def _fastInnerProduct(self,
                          projection_type,
                          model=None,
                          invert_model=False,
                          invert_matrix=False):
        """Fast version of getFaceInnerProduct.
            This does not handle the case of a full tensor property.

        Parameters
        ----------

        model : numpy.array
            material property (tensor properties are possible) at each cell center (nC, (1, 3, or 6))

        projection_type : str
            'edges' or 'faces'

        returnP : bool
            returns the projection matrices

        invert_model : bool
            inverts the material property

        invert_matrix : bool
            inverts the matrix

        Returns
        -------
        scipy.sparse.csr_matrix
            M, the inner product matrix (nF, nF)

        """
        projection_type = projection_type[0].upper()
        if projection_type not in ["F", "E"]:
            raise ValueError(
                "projection_type must be 'F' for faces or 'E' for edges")

        if model is None:
            model = np.ones(self.nC)

        if invert_model:
            model = 1.0 / model

        if is_scalar(model):
            model = model * np.ones(self.nC)

        # number of elements we are averaging (equals dim for regular
        # meshes, but for cyl, where we use symmetry, it is 1 for edge
        # variables and 2 for face variables)
        if self._meshType == "CYL":
            shape = getattr(self, "vn" + projection_type)
            n_elements = sum([1 if x != 0 else 0 for x in shape])
        else:
            n_elements = self.dim

        # Isotropic? or anisotropic?
        if model.size == self.nC:
            Av = getattr(self, "ave" + projection_type + "2CC")
            Vprop = self.cell_volumes * mkvc(model)
            M = n_elements * sdiag(Av.T * Vprop)

        elif model.size == self.nC * self.dim:
            Av = getattr(self, "ave" + projection_type + "2CCV")

            # if cyl, then only certain components are relevant due to symmetry
            # for faces, x, z matters, for edges, y (which is theta) matters
            if self._meshType == "CYL":
                if projection_type == "E":
                    model = model[:,
                                  1]  # this is the action of a projection mat
                elif projection_type == "F":
                    model = model[:, [0, 2]]

            V = sp.kron(sp.identity(n_elements), sdiag(self.cell_volumes))
            M = sdiag(Av.T * V * mkvc(model))
        else:
            return None

        if invert_matrix:
            return sdinv(M)
        else:
            return M

    def _fastInnerProductDeriv(self,
                               projection_type,
                               model,
                               invert_model=False,
                               invert_matrix=False):
        """

        Parameters
        ----------

        projection_type : str
            'E' or 'F'

        tensorType : TensorType
            type of the tensor

        invert_model : bool
            inverts the material property

        invert_matrix : bool
            inverts the matrix


        Returns
        -------
        function
            dMdmu, the derivative of the inner product matrix

        """

        projection_type = projection_type[0].upper()
        if projection_type not in ["F", "E"]:
            raise ValueError(
                "projection_type must be 'F' for faces or 'E' for edges")

        tensorType = TensorType(self, model)

        dMdprop = None

        if invert_matrix or invert_model:
            MI = self._fastInnerProduct(projection_type,
                                        model,
                                        invert_model=invert_model,
                                        invert_matrix=invert_matrix)

        # number of elements we are averaging (equals dim for regular
        # meshes, but for cyl, where we use symmetry, it is 1 for edge
        # variables and 2 for face variables)
        if self._meshType == "CYL":
            shape = getattr(self, "vn" + projection_type)
            n_elements = sum([1 if x != 0 else 0 for x in shape])
        else:
            n_elements = self.dim

        if tensorType == 0:  # isotropic, constant
            Av = getattr(self, "ave" + projection_type + "2CC")
            V = sdiag(self.cell_volumes)
            ones = sp.csr_matrix(
                (np.ones(self.nC), (range(self.nC), np.zeros(self.nC))),
                shape=(self.nC, 1),
            )
            if not invert_matrix and not invert_model:
                dMdprop = n_elements * Av.T * V * ones
            elif invert_matrix and invert_model:
                dMdprop = n_elements * (sdiag(MI.diagonal()**2) * Av.T * V *
                                        ones * sdiag(1.0 / model**2))
            elif invert_model:
                dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model**2)
            elif invert_matrix:
                dMdprop = n_elements * (sdiag(-MI.diagonal()**2) * Av.T * V)

        elif tensorType == 1:  # isotropic, variable in space
            Av = getattr(self, "ave" + projection_type + "2CC")
            V = sdiag(self.cell_volumes)
            if not invert_matrix and not invert_model:
                dMdprop = n_elements * Av.T * V
            elif invert_matrix and invert_model:
                dMdprop = n_elements * (sdiag(MI.diagonal()**2) * Av.T * V *
                                        sdiag(1.0 / model**2))
            elif invert_model:
                dMdprop = n_elements * Av.T * V * sdiag(-1.0 / model**2)
            elif invert_matrix:
                dMdprop = n_elements * (sdiag(-MI.diagonal()**2) * Av.T * V)

        elif tensorType == 2:  # anisotropic
            Av = getattr(self, "ave" + projection_type + "2CCV")
            V = sp.kron(sp.identity(self.dim), sdiag(self.cell_volumes))

            if self._meshType == "CYL":
                Zero = sp.csr_matrix((self.nC, self.nC))
                Eye = sp.eye(self.nC)
                if projection_type == "E":
                    P = sp.hstack([Zero, Eye, Zero])
                    # print(P.todense())
                elif projection_type == "F":
                    P = sp.vstack([
                        sp.hstack([Eye, Zero, Zero]),
                        sp.hstack([Zero, Zero, Eye])
                    ])
                    # print(P.todense())
            else:
                P = sp.eye(self.nC * self.dim)

            if not invert_matrix and not invert_model:
                dMdprop = Av.T * P * V
            elif invert_matrix and invert_model:
                dMdprop = (sdiag(MI.diagonal()**2) * Av.T * P * V *
                           sdiag(1.0 / model**2))
            elif invert_model:
                dMdprop = Av.T * P * V * sdiag(-1.0 / model**2)
            elif invert_matrix:
                dMdprop = sdiag(-MI.diagonal()**2) * Av.T * P * V

        if dMdprop is not None:

            def innerProductDeriv(v=None):
                if v is None:
                    warnings.warn(
                        "Depreciation Warning: TensorMesh.innerProductDeriv."
                        " You should be supplying a vector. "
                        "Use: sdiag(u)*dMdprop",
                        DeprecationWarning,
                    )
                    return dMdprop
                return sdiag(v) * dMdprop

            return innerProductDeriv
        else:
            return None

    # DEPRECATED
    @property
    def hx(self):
        """Width of cells in the x direction

        Returns
        -------
        numpy.ndarray

        .. deprecated:: 0.5.0
          `hx` will be removed in discretize 1.0.0 to reduce namespace clutter,
          please use `mesh.h[0]`.
        """
        warnings.warn("hx has been deprecated, please access as mesh.h[0]",
                      DeprecationWarning)
        return self.h[0]

    @property
    def hy(self):
        """Width of cells in the y direction

        Returns
        -------
        numpy.ndarray or None

        .. deprecated:: 0.5.0
          `hy` will be removed in discretize 1.0.0 to reduce namespace clutter,
          please use `mesh.h[1]`.
        """
        warnings.warn("hy has been deprecated, please access as mesh.h[1]",
                      DeprecationWarning)
        return None if self.dim < 2 else self.h[1]

    @property
    def hz(self):
        """Width of cells in the z direction

        Returns
        -------
        numpy.ndarray or None

        .. deprecated:: 0.5.0
          `hz` will be removed in discretize 1.0.0 to reduce namespace clutter,
          please use `mesh.h[2]`.
        """
        warnings.warn("hz has been deprecated, please access as mesh.h[2]",
                      DeprecationWarning)
        return None if self.dim < 3 else self.h[2]

    vectorNx = deprecate_property("nodes_x",
                                  "vectorNx",
                                  removal_version="1.0.0")
    vectorNy = deprecate_property("nodes_y",
                                  "vectorNy",
                                  removal_version="1.0.0")
    vectorNz = deprecate_property("nodes_z",
                                  "vectorNz",
                                  removal_version="1.0.0")
    vectorCCx = deprecate_property("cell_centers_x",
                                   "vectorCCx",
                                   removal_version="1.0.0")
    vectorCCy = deprecate_property("cell_centers_y",
                                   "vectorCCy",
                                   removal_version="1.0.0")
    vectorCCz = deprecate_property("cell_centers_z",
                                   "vectorCCz",
                                   removal_version="1.0.0")
    getInterpolationMat = deprecate_method("get_interpolation_matrix",
                                           "getInterpolationMat",
                                           removal_version="1.0.0")
    isInside = deprecate_method("is_inside",
                                "isInside",
                                removal_version="1.0.0")
    getTensor = deprecate_method("get_tensor",
                                 "getTensor",
                                 removal_version="1.0.0")
示例#8
0
class BaseRectangularMesh(BaseRegularMesh):
    """
    Base rectangular mesh class for the ``discretize`` package.

    The ``BaseRectangularMesh`` class acts as an extension of the
    :class:`~discretize.base.BaseRegularMesh` classes with a regular structure.
    """

    _aliases = {
        **BaseRegularMesh._aliases,
        **{
            "vnN": "shape_nodes",
            "vnEx": "shape_edges_x",
            "vnEy": "shape_edges_y",
            "vnEz": "shape_edges_z",
            "vnFx": "shape_faces_x",
            "vnFy": "shape_faces_y",
            "vnFz": "shape_faces_z",
        },
    }

    @property
    def shape_nodes(self):
        """Returns the number of nodes along each axis direction

        This property returns a tuple containing the number of nodes along
        each axis direction. The length of the tuple is equal to the
        dimension of the mesh; i.e. 1, 2 or 3.

        Returns
        -------
        (dim) tuple of int
            Number of nodes along each axis direction

        Notes
        -----
        Property also accessible as using the shorthand **vnN**
        """
        return tuple(x + 1 for x in self.shape_cells)

    @property
    def shape_edges_x(self):
        """Number of x-edges along each axis direction

        This property returns a tuple containing the number of x-edges
        along each axis direction. The length of the tuple is equal to the
        dimension of the mesh; i.e. 1, 2 or 3.

        Returns
        -------
        (dim) tuple of int
            Number of x-edges along each axis direction

            - *1D mesh:* `(n_cells_x)`
            - *2D mesh:* `(n_cells_x, n_nodes_y)`
            - *3D mesh:* `(n_cells_x, n_nodes_y, n_nodes_z)`

        Notes
        -----
        Property also accessible as using the shorthand **vnEx**
        """
        return self.shape_cells[:1] + self.shape_nodes[1:]

    @property
    def shape_edges_y(self):
        """Number of y-edges along each axis direction

        This property returns a tuple containing the number of y-edges
        along each axis direction. If `dim` is 1, there are no y-edges.

        Returns
        -------
        None or (dim) tuple of int
            Number of y-edges along each axis direction

            - *1D mesh: None*
            - *2D mesh:* `(n_nodes_x, n_cells_y)`
            - *3D mesh:* `(n_nodes_x, n_cells_y, n_nodes_z)`

        Notes
        -----
        Property also accessible as using the shorthand **vnEy**
        """
        if self.dim < 2:
            return None
        sc = self.shape_cells
        sn = self.shape_nodes
        return (sn[0], sc[1]) + sn[2:]  # conditionally added if dim == 3!

    @property
    def shape_edges_z(self):
        """Number of z-edges along each axis direction

        This property returns a tuple containing the number of z-edges
        along each axis direction. There are only z-edges if `dim` is 3.

        Returns
        -------
        None or (dim) tuple of int
            Number of z-edges along each axis direction.

            - *1D mesh: None*
            - *2D mesh: None*
            - *3D mesh:* `(n_nodes_x, n_nodes_y, n_cells_z)`

        Notes
        -----
        Property also accessible as using the shorthand **vnEz**
        """
        if self.dim < 3:
            return None
        return self.shape_nodes[:2] + self.shape_cells[2:]

    @property
    def shape_faces_x(self):
        """Number of x-faces along each axis direction

        This property returns a tuple containing the number of x-faces
        along each axis direction.

        Returns
        -------
        (dim) tuple of int
            Number of x-faces along each axis direction

            - *1D mesh:* `(n_nodes_x)`
            - *2D mesh:* `(n_nodes_x, n_cells_y)`
            - *3D mesh:* `(n_nodes_x, n_cells_y, n_cells_z)`

        Notes
        -----
        Property also accessible as using the shorthand **vnFx**
        """
        return self.shape_nodes[:1] + self.shape_cells[1:]

    @property
    def shape_faces_y(self):
        """Number of y-faces along each axis direction

        This property returns a tuple containing the number of y-faces
        along each axis direction. If `dim` is 1, there are no y-edges.

        Returns
        -------
        None or (dim) tuple of int
            Number of y-faces along each axis direction

            - *1D mesh: None*
            - *2D mesh:* `(n_cells_x, n_nodes_y)`
            - *3D mesh:* `(n_cells_x, n_nodes_y, n_cells_z)`

        Notes
        -----
        Property also accessible as using the shorthand **vnFy**
        """
        if self.dim < 2:
            return None
        sc = self.shape_cells
        sn = self.shape_nodes
        return (sc[0], sn[1]) + sc[2:]

    @property
    def shape_faces_z(self):
        """Number of z-faces along each axis direction

        This property returns a tuple containing the number of z-faces
        along each axis direction. There are only z-faces if `dim` is 3.

        Returns
        -------
        None or (dim) tuple of int
            Number of z-faces along each axis direction.

                - *1D mesh: None*
                - *2D mesh: None*
                - *3D mesh:* (n_cells_x, n_cells_y, n_nodes_z)

        Notes
        -----
        Property also accessible as using the shorthand **vnFz**
        """
        if self.dim < 3:
            return None
        return self.shape_cells[:2] + self.shape_nodes[2:]

    ##################################
    # Redo the numbering so they are dependent of the shape tuples
    # these should all inherit the parent's docstrings
    ##################################

    @property
    def n_cells(self):
        return int(np.prod(self.shape_cells))

    @property
    def n_nodes(self):
        return int(np.prod(self.shape_nodes))

    @property
    def n_edges_x(self):
        return int(np.prod(self.shape_edges_x))

    @property
    def n_edges_y(self):
        if self.dim < 2:
            return
        return int(np.prod(self.shape_edges_y))

    @property
    def n_edges_z(self):
        if self.dim < 3:
            return
        return int(np.prod(self.shape_edges_z))

    @property
    def n_faces_x(self):
        return int(np.prod(self.shape_faces_x))

    @property
    def n_faces_y(self):
        if self.dim < 2:
            return
        return int(np.prod(self.shape_faces_y))

    @property
    def n_faces_z(self):
        if self.dim < 3:
            return
        return int(np.prod(self.shape_faces_z))

    def reshape(
        self, x, x_type="cell_centers", out_type="cell_centers", format="V", **kwargs
    ):
        """General reshape method for tensor quantities

        **Reshape** is a quick command that will do its best to reshape discrete
        quantities living on meshes than inherit the :class:`discretize.base_mesh.RectangularMesh`
        class. For example, you may have a 1D array defining a vector on mesh faces, and you would
        like to extract the x-component and reshaped it to a 3D matrix.

        Parameters
        ----------
        x : numpy.ndarray or list of numpy.ndarray
            The input quantity. , ndarray (tensor) or a list
        x_type : {'CC', 'N', 'F', 'Fx', 'Fy', 'Fz', 'E', 'Ex', 'Ey', 'Ez'}
            Defines the locations on the mesh where input parameter *x* lives.
        out_type : str
            Defines the output quantity. Choice depends on your input for *x_type*:

                - *x_type* = 'CC' ---> *out_type* = 'CC'
                - *x_type* = 'N' ---> *out_type* = 'N'
                - *x_type* = 'F' ---> *out_type* = {'F', 'Fx', 'Fy', 'Fz'}
                - *x_type* = 'E' ---> *out_type* = {'E', 'Ex', 'Ey', 'Ez'}
        format : str
            The dimensions of quantity being returned

                - *V:* return a vector (1D array) or a list of vectors
                - *M:* return matrix (nD array) or a list of matrices

        """
        if "xType" in kwargs:
            warnings.warn(
                "The xType keyword argument has been deprecated, please use x_type. "
                "This will be removed in discretize 1.0.0",
                FutureWarning,
            )
            x_type = kwargs["xType"]
        if "outType" in kwargs:
            warnings.warn(
                "The outType keyword argument has been deprecated, please use out_type. "
                "This will be removed in discretize 1.0.0",
                FutureWarning,
            )
            out_type = kwargs["outType"]

        x_type = self._parse_location_type(x_type)
        out_type = self._parse_location_type(out_type)

        allowed_x_type = [
            "cell_centers",
            "nodes",
            "faces",
            "faces_x",
            "faces_y",
            "faces_z",
            "edges",
            "edges_x",
            "edges_y",
            "edges_z",
        ]
        if not (isinstance(x, list) or isinstance(x, np.ndarray)):
            raise Exception("x must be either a list or a ndarray")
        if x_type not in allowed_x_type:
            raise Exception(
                "x_type must be either '" + "', '".join(allowed_x_type) + "'"
            )
        if out_type not in allowed_x_type:
            raise Exception(
                "out_type must be either '" + "', '".join(allowed_x_type) + "'"
            )
        if format not in ["M", "V"]:
            raise Exception("format must be either 'M' or 'V'")
        if out_type[: len(x_type)] != x_type:
            raise Exception("You cannot change types when reshaping.")
        if x_type not in out_type:
            raise Exception("You cannot change type of components.")

        if isinstance(x, list):
            for i, xi in enumerate(x):
                if not isinstance(x, np.ndarray):
                    raise Exception("x[{0:d}] must be a numpy array".format(i))
                if xi.size != x[0].size:
                    raise Exception("Number of elements in list must not change.")

            x_array = np.ones((x.size, len(x)))
            # Unwrap it and put it in a np array
            for i, xi in enumerate(x):
                x_array[:, i] = mkvc(xi)
            x = x_array

        if not isinstance(x, np.ndarray):
            raise Exception("x must be a numpy array")

        x = x[:]  # make a copy.
        x_type_is_FE_xyz = (
            len(x_type) > 1
            and x_type[0] in ["f", "e"]
            and x_type[-1] in ["x", "y", "z"]
        )

        def outKernal(xx, nn):
            """Returns xx as either a matrix (shape == nn) or a vector."""
            if format == "M":
                return xx.reshape(nn, order="F")
            elif format == "V":
                return mkvc(xx)

        def switchKernal(xx):
            """Switches over the different options."""
            if x_type in ["cell_centers", "nodes"]:
                nn = self.shape_cells if x_type == "cell_centers" else self.shape_nodes
                if xx.size != np.prod(nn):
                    raise Exception("Number of elements must not change.")
                return outKernal(xx, nn)
            elif x_type in ["faces", "edges"]:
                # This will only deal with components of fields,
                # not full 'F' or 'E'
                xx = mkvc(xx)  # unwrap it in case it is a matrix
                if x_type == "faces":
                    nn = (self.nFx, self.nFy, self.nFz)[: self.dim]
                else:
                    nn = (self.nEx, self.nEy, self.nEz)[: self.dim]
                nn = np.r_[0, nn]

                nx = [0, 0, 0]
                nx[0] = self.shape_faces_x if x_type == "faces" else self.shape_edges_x
                nx[1] = self.shape_faces_y if x_type == "faces" else self.shape_edges_y
                nx[2] = self.shape_faces_z if x_type == "faces" else self.shape_edges_z

                for dim, dimName in enumerate(["x", "y", "z"]):
                    if dimName in out_type:
                        if self.dim <= dim:
                            raise Exception(
                                "Dimensions of mesh not great enough for "
                                "{}_{}".format(x_type, dimName)
                            )
                        if xx.size != np.sum(nn):
                            raise Exception("Vector is not the right size.")
                        start = np.sum(nn[: dim + 1])
                        end = np.sum(nn[: dim + 2])
                        return outKernal(xx[start:end], nx[dim])

            elif x_type_is_FE_xyz:
                # This will deal with partial components (x, y or z)
                # lying on edges or faces
                if "x" in x_type:
                    nn = self.shape_faces_x if "f" in x_type else self.shape_edges_x
                elif "y" in x_type:
                    nn = self.shape_faces_y if "f" in x_type else self.shape_edges_y
                elif "z" in x_type:
                    nn = self.shape_faces_z if "f" in x_type else self.shape_edges_z
                if xx.size != np.prod(nn):
                    raise Exception(
                        f"Vector is not the right size. Expected {np.prod(nn)}, got {xx.size}"
                    )
                return outKernal(xx, nn)

        # Check if we are dealing with a vector quantity
        isVectorQuantity = len(x.shape) == 2 and x.shape[1] == self.dim

        if out_type in ["faces", "edges"]:
            if isVectorQuantity:
                raise Exception("Not sure what to do with a vector vector quantity..")
            outTypeCopy = out_type
            out = ()
            for ii, dirName in enumerate(["x", "y", "z"][: self.dim]):
                out_type = outTypeCopy + "_" + dirName
                out += (switchKernal(x),)
            return out
        elif isVectorQuantity:
            out = ()
            for ii in range(x.shape[1]):
                out += (switchKernal(x[:, ii]),)
            return out
        else:
            return switchKernal(x)

    # DEPRECATED
    r = deprecate_method("reshape", "r", removal_version="1.0.0", future_warn=True)

    @property
    def nCx(self):
        """Number of cells in the x direction

        Returns
        -------
        int

        .. deprecated:: 0.5.0
          `nCx` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_cells[0]` to reduce namespace clutter.
        """

        warnings.warn(
            "nCx has been deprecated, please access as mesh.shape_cells[0]",
            FutureWarning,
        )
        return self.shape_cells[0]

    @property
    def nCy(self):
        """Number of cells in the y direction

        Returns
        -------
        int or None
            None if dim < 2

        .. deprecated:: 0.5.0
          `nCy` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_cells[1]` to reduce namespace clutter.
        """

        warnings.warn(
            "nCy has been deprecated, please access as mesh.shape_cells[1]",
            FutureWarning,
        )
        if self.dim < 2:
            return None
        return self.shape_cells[1]

    @property
    def nCz(self):
        """Number of cells in the z direction

        Returns
        -------
        int or None
            None if dim < 3

        .. deprecated:: 0.5.0
          `nCz` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_cells[2]` to reduce namespace clutter.
        """

        warnings.warn(
            "nCz has been deprecated, please access as mesh.shape_cells[2]",
            FutureWarning,
        )
        if self.dim < 3:
            return None
        return self.shape_cells[2]

    @property
    def nNx(self):
        """Number of nodes in the x-direction

        Returns
        -------
        int

        .. deprecated:: 0.5.0
          `nNx` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_nodes[0]` to reduce namespace clutter.
        """

        warnings.warn(
            "nNx has been deprecated, please access as mesh.shape_nodes[0]",
            FutureWarning,
        )
        return self.shape_nodes[0]

    @property
    def nNy(self):
        """Number of nodes in the y-direction

        Returns
        -------
        int or None
            None if dim < 2

        .. deprecated:: 0.5.0
          `nNy` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_nodes[1]` to reduce namespace clutter.
        """

        warnings.warn(
            "nNy has been deprecated, please access as mesh.shape_nodes[1]",
            FutureWarning,
        )
        if self.dim < 2:
            return None
        return self.shape_nodes[1]

    @property
    def nNz(self):
        """Number of nodes in the z-direction

        Returns
        -------
        int or None
            None if dim < 3

        .. deprecated:: 0.5.0
          `nNz` will be removed in discretize 1.0.0, it is replaced by
          `mesh.shape_nodes[2]` to reduce namespace clutter.
        """

        warnings.warn(
            "nNz has been deprecated, please access as mesh.shape_nodes[2]",
            FutureWarning,
        )
        if self.dim < 3:
            return None
        return self.shape_nodes[2]