Source code for firedrake.output.paraview_reordering

from tsfc.finatinterface import create_base_element
import numpy as np
from pyop2.utils import as_tuple

try:
    import vtkmodules.vtkCommonDataModel
    del vtkmodules.vtkCommonDataModel
except ModuleNotFoundError as e:
    raise ModuleNotFoundError(
        "Error importing vtkmodules. Firedrake does not install VTK by default, "
        "you may need to install VTK by running\n\t"
        "pip install vtk"
    ) from e
from vtkmodules.vtkCommonDataModel import (
    vtkLagrangeTriangle, vtkLagrangeTetra,
    vtkLagrangeQuadrilateral, vtkLagrangeHexahedron, vtkLagrangeWedge
)


__all__ = (
    "vtk_lagrange_tet_reorder",
    "vtk_lagrange_hex_reorder",
    "vtk_lagrange_interval_reorder",
    "vtk_lagrange_triangle_reorder",
    "vtk_lagrange_quad_reorder",
    "vtk_lagrange_wedge_reorder",
)


def firedrake_local_to_cart(element):
    r"""Gets the list of nodes for an element (provided they exist.)
    :arg element: a ufl element.
    :returns: a list of arrays of floats where each array is a node.
    """
    finat_element = create_base_element(element)
    _, point_set = finat_element.dual_basis
    return point_set.points


def invert(list1, list2):
    r"""Given two maps (lists) from [0..N] to nodes, finds a permutations between them.
    :arg list1: a list of nodes.
    :arg list2: a second list of nodes.
    :returns: a list of integers, l, such that list1[x] = list2[l[x]]
    """
    if len(list1) != len(list2):
        raise ValueError("Dimension of Paraview basis and Element basis unequal.")

    def find_same(val, lst, tol=0.00000001):
        for (idx, x) in enumerate(lst):
            if np.linalg.norm(val - x) < tol:
                return idx
        raise ValueError("Unable to establish permutation between Paraview basis and given element's basis.")
    perm = [find_same(x, list2) for x in list1]
    if len(set(perm)) != len(perm):
        raise ValueError("Unable to establish permutation between Paraview basis and given element's basis.")
    return perm


# The following functions wrap around functions provided by vtk (pip install vtk).
# The vtk functions do one of two things:
#
# 1. They convert (i,j,k) indices, locations on a rect or hex, to a dof index.
#
# 2. They convert an index into a dofs into an index into some sort of indexing
# of the nodes typically barycentric indecies
#
# The follow functions call the two types of VTK functions.
# They use them to produce maps from dof indices to node locations.
# These functions will later be used to figure out reorderings of nodes.


def vtk_interval_local_coord(i, order):
    r"""
    See vtkLagrangeCurve::PointIndexFromIJK.
    """
    if i == 0:
        return 0.0
    elif i == order:
        return 1.0
    else:
        return i / order


def bary_to_cart(bar):
    N = len(bar) - 1
    mat = np.vstack([np.zeros(N), np.eye(N)])
    return np.dot(bar, mat)


def tet_barycentric_index(tet, index, order):
    """
    Wrapper for vtkLagrangeTetra::BarycentricIndex.
    """
    bindex = [-1, -1, -1, -1]
    tet.BarycentricIndex(index, bindex, order)
    return bary_to_cart(np.array(bindex) / order)


def vtk_tet_local_to_cart(order):
    r"""Produces a list of nodes for VTK's lagrange tet basis.
    :arg order: the order of the tet
    :return a list of arrays of floats
    """
    count = int((order + 1) * (order + 2) * (order + 3) // 6)
    tet = vtkLagrangeTetra()
    carts = [tet_barycentric_index(tet, i, order) for i in range(count)]
    return carts


def vtk_hex_local_to_cart(orders):
    r"""Produces a list of nodes for VTK's lagrange hex basis.
    :arg order: the three orders of the hex basis.
    :return a list of arrays of floats.
    """

    sizes = tuple([o + 1 for o in orders])
    size = np.prod(sizes)
    loc_to_cart = np.empty(size, dtype="object")
    hexa = vtkLagrangeHexahedron()
    for loc in np.ndindex(sizes):
        idx = hexa.PointIndexFromIJK(loc[0], loc[1], loc[2], orders)
        cart = np.array([c / o for (c, o) in zip(loc, orders)])
        loc_to_cart[idx] = cart
    return loc_to_cart


def vtk_triangle_index_cart(tri, index, order):
    """
    Wrapper for vtkLagrangeTriangle::BarycentricIndex
    """
    bindex = [-1, -1, -1]
    tri.BarycentricIndex(index, bindex, order)
    return bary_to_cart(bindex)


def vtk_triangle_local_to_cart(order):
    count = (order + 1) * (order + 2) // 2
    tri = vtkLagrangeTriangle()
    return [vtk_triangle_index_cart(tri, idx, order) / order
            for idx in range(count)]


def vtk_quad_local_to_cart(orders):
    r"""Produces a list of nodes for VTK's lagrange quad basis.
    :arg order: the order of the quad basis.
    :return a list of arrays of floats.
    """
    sizes = tuple([o + 1 for o in orders])
    size = np.prod(sizes)
    loc_to_cart = np.empty(size, dtype="object")
    quad = vtkLagrangeQuadrilateral()
    for loc in np.ndindex(sizes):
        idx = quad.PointIndexFromIJK(loc[0], loc[1], orders)
        cart = np.array([c / o for (c, o) in zip(loc, orders)])
        loc_to_cart[idx] = cart
    return loc_to_cart


def vtk_wedge_local_to_cart(ordersp):
    r"""Produces a list of nodes for VTK's lagrange wedge basis.
    :arg order: the orders of the wedge (triangle, interval)
    :return a list of arrays of floats
    """
    orders = [ordersp[0], ordersp[0], ordersp[1]]
    sizes = tuple([o + 1 for o in orders])
    triSize = (orders[0] + 1) * (orders[0] + 2) // 2
    totalSize = triSize * (orders[2] + 1)
    loc_to_cart = np.empty(totalSize, dtype="object")
    wedge = vtkLagrangeWedge()
    for loc in np.ndindex(sizes):
        if loc[0] + loc[1] > orders[0]:
            continue
        idx = wedge.PointIndexFromIJK(loc[0], loc[1], loc[2], orders)
        cart = np.array([c / o for (c, o) in zip(loc, orders)])
        loc_to_cart[idx] = cart
    return loc_to_cart


"""
The following functions take a given ufl_element, (indicated by the function name), and
produce a permutation of the element's basis that turns it into the basis that VTK/Paraview
uses.
"""


[docs] def vtk_lagrange_interval_reorder(ufl_element): degree = ufl_element.degree() vtk_local = [vtk_interval_local_coord(x, degree) for x in range(degree + 1)] firedrake_local = firedrake_local_to_cart(ufl_element) return invert(vtk_local, firedrake_local)
[docs] def vtk_lagrange_triangle_reorder(ufl_element): degree = ufl_element.degree() vtk_local = vtk_triangle_local_to_cart(degree) firedrake_local = firedrake_local_to_cart(ufl_element) return invert(vtk_local, firedrake_local)
[docs] def vtk_lagrange_quad_reorder(ufl_element): degree = as_tuple(ufl_element.degree())[0] # should be uniform vtk_local = vtk_quad_local_to_cart((degree, degree)) firedrake_local = firedrake_local_to_cart(ufl_element) return invert(vtk_local, firedrake_local)
[docs] def vtk_lagrange_tet_reorder(ufl_element): degree = ufl_element.degree() vtk_local = vtk_tet_local_to_cart(degree) firedrake_local = firedrake_local_to_cart(ufl_element) return invert(vtk_local, firedrake_local)
[docs] def vtk_lagrange_wedge_reorder(ufl_element): degree = ufl_element.degree() vtk_local = vtk_wedge_local_to_cart(degree) firedrake_local = firedrake_local_to_cart(ufl_element) return invert(vtk_local, firedrake_local)
[docs] def vtk_lagrange_hex_reorder(ufl_element): # tensor product elements have a degree tuple whereas # normal hexes use an integer degrees = as_tuple(ufl_element.degree()) degree = max(degrees) if any(d != degree for d in degrees): raise ValueError( "Degrees on hex tensor products must be uniform because VTK " "can't understand otherwise." ) vtk_local = vtk_hex_local_to_cart((degree, degree, degree)) firedrake_local = firedrake_local_to_cart(ufl_element) return invert(vtk_local, firedrake_local)