Source code for ddg.math.grids

"""This module provides functions to create quad and triangle grids."""

import numpy as np


def _quad_faces2D(array):
    """
    Create quad faces of a grid from a 2D array of grid vertex indices.

    Parameters
    ----------
    array : np.ndarray of shape (n, m)
        The vertex indices of the grid.

    Returns
    -------
    grid : np.ndarray of shape (k, 4)
        Faces of the quad grid, where k is the number of faces.

    Examples
    --------
    >>> import numpy as np
    >>> from ddg.math.grids import _quad_faces2D
    >>> a = np.arange(3 * 4).reshape(3, 4)
    >>> a
    array([[ 0,  1,  2,  3],
           [ 4,  5,  6,  7],
           [ 8,  9, 10, 11]])
    >>> _quad_faces2D(a)
    array([[ 0,  1,  5,  4],
           [ 1,  2,  6,  5],
           [ 2,  3,  7,  6],
           [ 4,  5,  9,  8],
           [ 5,  6, 10,  9],
           [ 6,  7, 11, 10]])

    """
    grid = np.column_stack((
        array[:-1, :-1].ravel(),
        array[:-1, 1:].ravel(),
        array[1:, 1:].ravel(),
        array[1:, :-1].ravel(),))
    return grid


def _grid3D_along_axis(array, axis):
    """
    Create quad faces from a 3D index array along a given axis.

    This is a helper function to create faces of a 3D grid
    in different axes of the index array.
  
    Parameters
    ----------
    array : np.ndarray of shape (n, m, o)
        The 3D vertex index array.

    axis : {0, 1, 2}
        Axis along which the 2D grid faces of the index array is to be created.

    Returns
    -------
    np.ndarray of shape (k, 4)
        Faces of the grid, where k is the number of faces.

    Examples
    --------
    >>> import numpy as np
    >>> from ddg.math.grids import _grid3D_along_axis
    >>> index_array = np.arange(2 * 3 * 2).reshape(2, 3, 2)
    >>> _grid3D_along_axis(index_array, 0)
    array([[ 0,  1,  3,  2],
           [ 2,  3,  5,  4],
           [ 6,  7,  9,  8],
           [ 8,  9, 11, 10]])

    This gives you the faces parallel to the x-y-plane (if default
    coordinates are used).

    >>> _grid3D_along_axis(index_array, 1)
    array([[ 0,  1,  7,  6],
           [ 2,  3,  9,  8],
           [ 4,  5, 11, 10]])

    This gives you the faces parallel to the x-z-plane (if default
    coordinates are used).

    >>> _grid3D_along_axis(index_array, 2)
    array([[ 0,  2,  8,  6],
           [ 2,  4, 10,  8],
           [ 1,  3,  9,  7],
           [ 3,  5, 11,  9]])

    This gives you the faces parallel to the y-z-plane (if default
    coordinates are used).

    """
    return np.vstack([_quad_faces2D(array.take(i, axis))
                      for i in range(array.shape[axis])])


def _quad_faces3D(array):
    """
    Create the face array of a 3D grid.
  
    Parameters
    ----------
    array : np.ndarray of shape (n, m, o)
        The vertex index array.

    Returns
    -------
    np.ndarray of shape (k, 4)
        Faces of the grid, where k is the number of faces.

    Examples
    --------
    >>> import numpy as np
    >>> from ddg.math.grids import _quad_faces3D
    >>> index_array = np.arange(2*3*2).reshape(2, 3, 2)
    >>> index_array
    array([[[ 0,  1],
            [ 2,  3],
            [ 4,  5]],
    <BLANKLINE>
           [[ 6,  7],
            [ 8,  9],
            [10, 11]]])
    >>> _quad_faces3D(index_array)
    array([[ 0,  1,  3,  2],
           [ 2,  3,  5,  4],
           [ 6,  7,  9,  8],
           [ 8,  9, 11, 10],
           [ 0,  1,  7,  6],
           [ 2,  3,  9,  8],
           [ 4,  5, 11, 10],
           [ 0,  2,  8,  6],
           [ 2,  4, 10,  8],
           [ 1,  3,  9,  7],
           [ 3,  5, 11,  9]])

    """
    return np.vstack([_grid3D_along_axis(array, axis) for axis in range(3)])


[docs]def quad_grid(shape): """ Create a rectangular grid of the input shape. Returns the quad faces and points of a box grid in Z^2 or Z^3. Parameters ---------- shape : tuple of length 2 or 3 Shape of the grid. First entry is the number of vertices in the x-direction, second the number of vertices in the y-direction and third (if given) the number of vertices in z-direction. Returns ------- faces : np.ndarray of shape (k, 4) The quad faces of the grid, where k is the number of faces. coordinates : np.ndarray Default coordinates which start at (0,0) (or (0,0,0)) and expand to the specified values of shape. The shape of the array is (n, 2) if the input shape has length 2, otherwise it is (n, 3). Examples -------- >>> from ddg.math.grids import quad_grid >>> faces, coords = quad_grid((2, 3)) >>> faces array([[0, 1, 3, 2], [2, 3, 5, 4]]) >>> coords array([[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2]]) In the 3D case we get >>> from ddg.math.grids import quad_grid >>> faces, coords = quad_grid((2, 2, 2)) >>> faces array([[0, 1, 3, 2], [4, 5, 7, 6], [0, 1, 5, 4], [2, 3, 7, 6], [0, 2, 6, 4], [1, 3, 7, 5]]) >>> coords array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]]) Raises ------ ValueError If the given shape does not have .ndim 2 or 3. """ # 2D if len(shape) == 2: shape = (shape[1], shape[0]) n, m = shape grid_indices = np.arange(m * n).reshape(shape) faces = _quad_faces2D(grid_indices) # Create xx and yy which define a complete unit index grid # with the shape (n, m). xx, yy = np.mgrid[0:n, 0:m] # Use the indices to create unit coordinates for the vertices. coordinates = np.vstack([yy.ravel(), xx.ravel()]).T # 3D elif len(shape) == 3: shape = (shape[2], shape[1], shape[0]) n, m, l = shape grid_indices = np.arange(m * n * l).reshape(shape) faces = _quad_faces3D(grid_indices) # Same as the 2D case just with another dimension. xx, yy, zz = np.mgrid[0:n, 0:m, 0:l] coordinates = np.vstack([zz.ravel(), yy.ravel(), xx.ravel()]).T else: raise ValueError("Quad grid must have .ndim 2 or 3") return faces, coordinates
[docs]def triangle_faces(quads): """ Subdivide quads into lower left and upper right triangles. Parameters ---------- quads : np.ndarray of shape (n, 4) The quad faces of the grid, where n is the number of faces. Returns ------- np.ndarray of shape (n*2, 3) The triangle faces of the given quad faces, where n*2 is the number of triangle faces. Examples -------- >>> import numpy as np >>> from ddg.math.grids import triangle_faces >>> quad_faces = np.array([ ... [0, 1, 4, 3], ... [1, 2, 5, 4]]) >>> triangle_faces(quad_faces) array([[0, 1, 4], [1, 2, 5], [0, 4, 3], [1, 5, 4]]) """ lower_left_triangles = quads[:, (0, 1, 2)] upper_right_triangles = quads[:, (0, 2, 3)] return np.vstack((lower_left_triangles, upper_right_triangles))
[docs]def triangle_grid(shape): """ Create a triangle grid of the input shape. The input shape defines the amount of vertices in each direction. Both 2D and 3D are supported. Returns the faces and default coordinates. Parameters ---------- shape : tuple of length 2 or 3 Shape of the grid. First entry is the number of vertices in the x-direction, second the number of vertices in the y-direction and third (if given) the number of vertices in z-direction. Returns ------- faces : np.ndarray of shape (k, 3) The faces of the triangle grid, where k is the number of faces. coordinates : np.ndarray The default coordinates which start at (0,0) (or (0,0,0)) and expand to the specified values of shape. The shape of the array is (n, 2) if the input shape has length 2, otherwise it is (n, 3). Examples -------- >>> from ddg.math.grids import triangle_grid >>> faces, coords = triangle_grid((2, 3)) >>> faces array([[0, 1, 3], [2, 3, 5], [0, 3, 2], [2, 5, 4]]) >>> coords array([[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2]]) In the 3D case we get >>> faces, coords = triangle_grid((2, 2, 2)) >>> faces array([[0, 1, 3], [4, 5, 7], [0, 1, 5], [2, 3, 7], [0, 2, 6], [1, 3, 7], [0, 3, 2], [4, 7, 6], [0, 5, 4], [2, 7, 6], [0, 6, 4], [1, 7, 5]]) >>> coords array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]]) """ quads, coordinates = quad_grid(shape) faces = triangle_faces(quads) return faces, coordinates