示例#1
0
def test_coordinates_never_cellwise_constant_vertex():
    # The only exception here:
    domains = Mesh(Cell("vertex", 3))
    assert domains.ufl_cell().cellname() == "vertex"
    e = SpatialCoordinate(domains)
    assert is_cellwise_constant(e)
    e = CellCoordinate(domains)
    assert is_cellwise_constant(e)
示例#2
0
def test_coordinates_never_cellwise_constant_vertex():
    # The only exception here:
    domains = Mesh(Cell("vertex", 3))
    assert domains.ufl_cell().cellname() == "vertex"
    e = SpatialCoordinate(domains)
    assert is_cellwise_constant(e)
    e = CellCoordinate(domains)
    assert is_cellwise_constant(e)
示例#3
0
def test_coefficient_sometimes_cellwise_constant(domains_not_linear):
    e = Constant(domains_not_linear)
    assert is_cellwise_constant(e)

    V = FiniteElement("DG", domains_not_linear.ufl_cell(), 0)
    e = Coefficient(V)
    assert is_cellwise_constant(e)
    V = FiniteElement("R", domains_not_linear.ufl_cell(), 0)
    e = Coefficient(V)
    assert is_cellwise_constant(e)
示例#4
0
def test_coefficient_sometimes_cellwise_constant(domains_not_linear):
    e = Constant(domains_not_linear)
    assert is_cellwise_constant(e)

    V = FiniteElement("DG", domains_not_linear.ufl_cell(), 0)
    e = Coefficient(V)
    assert is_cellwise_constant(e)
    V = FiniteElement("R", domains_not_linear.ufl_cell(), 0)
    e = Coefficient(V)
    assert is_cellwise_constant(e)
示例#5
0
def test_always_cellwise_constant_geometric_quantities(domains):
    "Test geometric quantities that are always constant over a cell."
    e = CellVolume(domains)
    assert is_cellwise_constant(e)
    e = Circumradius(domains)
    assert is_cellwise_constant(e)
    e = FacetArea(domains)
    assert is_cellwise_constant(e)
    e = MinFacetEdgeLength(domains)
    assert is_cellwise_constant(e)
    e = MaxFacetEdgeLength(domains)
    assert is_cellwise_constant(e)
示例#6
0
def construct_modified_terminal(mt, terminal):
    """Construct a modified terminal given terminal modifiers from an
    analysed modified terminal and a terminal."""
    expr = terminal

    if mt.reference_value:
        expr = ReferenceValue(expr)

    dim = expr.ufl_domain().topological_dimension()
    for n in range(mt.local_derivatives):
        # Return zero if expression is trivially constant. This has to
        # happen here because ReferenceGrad has no access to the
        # topological dimension of a literal zero.
        if is_cellwise_constant(expr):
            expr = Zero(expr.ufl_shape + (dim, ), expr.ufl_free_indices,
                        expr.ufl_index_dimensions)
        else:
            expr = ReferenceGrad(expr)

    # No need to apply restrictions to ConstantValue terminals
    if not isinstance(expr, ConstantValue):
        if mt.restriction == '+':
            expr = PositiveRestricted(expr)
        elif mt.restriction == '-':
            expr = NegativeRestricted(expr)

    return expr
示例#7
0
def Dn(f):
    """UFL operator: Take the directional derivative of *f* in the
    facet normal direction, Dn(f) := dot(grad(f), n)."""
    f = as_ufl(f)
    if is_cellwise_constant(f):
        return Zero(f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions)
    return dot(grad(f), FacetNormal(f.ufl_domain()))
示例#8
0
 def geometric_quantity(self, v):
     "Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate."
     if is_cellwise_constant(v):
         return 0
     else:
         # As a heuristic, just returning domain degree to bump up degree somewhat
         return v.ufl_domain().ufl_coordinate_element().degree()
示例#9
0
def construct_modified_terminal(mt, terminal):
    """Construct a modified terminal given terminal modifiers from an
    analysed modified terminal and a terminal."""
    expr = terminal

    if mt.reference_value:
        expr = ReferenceValue(expr)

    dim = expr.ufl_domain().topological_dimension()
    for n in range(mt.local_derivatives):
        # Return zero if expression is trivially constant. This has to
        # happen here because ReferenceGrad has no access to the
        # topological dimension of a literal zero.
        if is_cellwise_constant(expr):
            expr = Zero(expr.ufl_shape + (dim,), expr.ufl_free_indices,
                        expr.ufl_index_dimensions)
        else:
            expr = ReferenceGrad(expr)

    if mt.averaged == "cell":
        expr = CellAvg(expr)
    elif mt.averaged == "facet":
        expr = FacetAvg(expr)

    # No need to apply restrictions to ConstantValue terminals
    if not isinstance(expr, ConstantValue):
        if mt.restriction == '+':
            expr = PositiveRestricted(expr)
        elif mt.restriction == '-':
            expr = NegativeRestricted(expr)

    return expr
示例#10
0
 def geometric_quantity(self, v):
     "Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate."
     if is_cellwise_constant(v):
         return 0
     else:
         # As a heuristic, just returning domain degree to bump up degree somewhat
         return v.ufl_domain().ufl_coordinate_element().degree()
示例#11
0
def Dn(f):
    """UFL operator: Take the directional derivative of *f* in the
    facet normal direction, Dn(f) := dot(grad(f), n)."""
    f = as_ufl(f)
    if is_cellwise_constant(f):
        return Zero(f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions)
    return dot(grad(f), FacetNormal(f.ufl_domain()))
示例#12
0
 def __new__(cls, f):
     # Return zero if expression is trivially constant
     if is_cellwise_constant(f):
         dim = f.ufl_domain().topological_dimension()
         return Zero(f.ufl_shape + (dim,), f.ufl_free_indices,
                     f.ufl_index_dimensions)
     return CompoundDerivative.__new__(cls)
示例#13
0
 def __new__(cls, f):
     # Return zero if expression is trivially constant
     if is_cellwise_constant(f):
         dim = find_geometric_dimension(f)
         return Zero((dim,) + f.ufl_shape, f.ufl_free_indices,
                     f.ufl_index_dimensions)
     return CompoundDerivative.__new__(cls)
示例#14
0
 def geometric_quantity(self, o):
     "dg/dX = 0 if piecewise constant, otherwise ReferenceGrad(g)"
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     else:
         # TODO: Which types does this involve? I don't think the
         # form compilers will handle this.
         return ReferenceGrad(o)
示例#15
0
 def geometric_quantity(self, o):
     "dg/dX = 0 if piecewise constant, otherwise ReferenceGrad(g)"
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     else:
         # TODO: Which types does this involve? I don't think the
         # form compilers will handle this.
         return ReferenceGrad(o)
示例#16
0
 def jacobian_inverse(self, o):
     # grad(K) == K_ji rgrad(K)_rj
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     if not o._ufl_is_terminal_:
         error("ReferenceValue can only wrap a terminal")
     Do = grad_to_reference_grad(o, o)
     return Do
示例#17
0
 def geometric_quantity(self, o):
     """Default for geometric quantities is dg/dx = 0 if piecewise constant, otherwise keep Grad(g).
     Override for specific types if other behaviour is needed."""
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     else:
         # TODO: Which types does this involve? I don't think the
         # form compilers will handle this.
         return Grad(o)
示例#18
0
 def geometric_quantity(self, o):
     """Default for geometric quantities is dg/dx = 0 if piecewise constant, otherwise keep Grad(g).
     Override for specific types if other behaviour is needed."""
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     else:
         # TODO: Which types does this involve? I don't think the
         # form compilers will handle this.
         return Grad(o)
示例#19
0
    def __new__(cls, f):
        if f.ufl_free_indices:
            error("Free indices in the divergence argument is not allowed.")

        # Return zero if expression is trivially constant
        if is_cellwise_constant(f):
            return Zero(f.ufl_shape[1:])  # No free indices asserted above

        return CompoundDerivative.__new__(cls)
示例#20
0
 def jacobian_inverse(self, o):
     # grad(K) == K_ji rgrad(K)_rj
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     if not o._ufl_is_terminal_:
         error("ReferenceValue can only wrap a terminal")
     r = indices(len(o.ufl_shape))
     i, j = indices(2)
     Do = as_tensor(o[j, i] * ReferenceGrad(o)[r + (j, )], r + (i, ))
     return Do
示例#21
0
 def jacobian_inverse(self, o):
     # grad(K) == K_ji rgrad(K)_rj
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     if not o._ufl_is_terminal_:
         error("ReferenceValue can only wrap a terminal")
     r = indices(len(o.ufl_shape))
     i, j = indices(2)
     Do = as_tensor(o[j, i]*ReferenceGrad(o)[r + (j,)], r + (i,))
     return Do
示例#22
0
 def _ufl_expr_reconstruct_(self, op):
     "Return a new object of the same type with new operands."
     if is_cellwise_constant(op):
         if op.ufl_shape != self.ufl_operands[0].ufl_shape:
             error("Operand shape mismatch in NablaGrad reconstruct.")
         if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices:
             error("Free index mismatch in NablaGrad reconstruct.")
         return Zero(self.ufl_shape, self.ufl_free_indices,
                     self.ufl_index_dimensions)
     return self._ufl_class_(op)
示例#23
0
def analyse_dependencies(F, mt_unique_table_reference):
    # Sets 'status' of all nodes to either: 'inactive', 'piecewise' or 'varying'
    # Children of 'target' nodes are either 'piecewise' or 'varying'.
    # All other nodes are 'inactive'.
    # Varying nodes are identified by their tables ('tr'). All their parent
    # nodes are also set to 'varying' - any remaining active nodes are 'piecewise'.

    # Set targets, and dependencies to 'active'
    targets = [i for i, v in F.nodes.items() if v.get('target')]
    for i, v in F.nodes.items():
        v['status'] = 'inactive'

    while targets:
        s = targets.pop()
        F.nodes[s]['status'] = 'active'
        for j in F.out_edges[s]:
            if F.nodes[j]['status'] == 'inactive':
                targets.append(j)

    # Build piecewise/varying markers for factorized_vertices
    varying_ttypes = ("varying", "uniform", "quadrature")
    varying_indices = []
    for i, v in F.nodes.items():
        if v.get('mt') is None:
            continue
        tr = v.get('tr')
        if tr is not None:
            ttype = tr.ttype
            # Check if table computations have revealed values varying over points
            # Note: uniform means entity-wise uniform, varying over points
            if ttype in varying_ttypes:
                varying_indices.append(i)
            else:
                if ttype not in ("fixed", "piecewise", "ones", "zeros"):
                    raise RuntimeError("Invalid ttype %s" % (ttype, ))

        elif not is_cellwise_constant(v['expression']):
            raise RuntimeError("Error")
            # Keeping this check to be on the safe side,
            # not sure which cases this will cover (if any)
            # varying_indices.append(i)

    # Set all parents of active varying nodes to 'varying'
    while varying_indices:
        s = varying_indices.pop()
        if F.nodes[s]['status'] == 'active':
            F.nodes[s]['status'] = 'varying'
            for j in F.in_edges[s]:
                varying_indices.append(j)

    # Any remaining active nodes must be 'piecewise'
    for i, v in F.nodes.items():
        if v['status'] == 'active':
            v['status'] = 'piecewise'
示例#24
0
 def geometric_quantity(self, o):
     """Default for geometric quantities is do/dx = 0 if piecewise constant,
     otherwise transform derivatives to reference derivatives.
     Override for specific types if other behaviour is needed."""
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     else:
         domain = o.ufl_domain()
         K = JacobianInverse(domain)
         Do = grad_to_reference_grad(o, K)
         return Do
示例#25
0
def default_partition_seed(expr, rank):
    """
    Partition 0: Piecewise constant on each cell (including Real and DG0 coefficients)
    Partition 1: Depends on x
    Partition 2: Depends on x and coefficients
    Partitions [3,3+rank): depend on argument with count partition-3
    """
    # TODO: Use named constants for the partition numbers here

    modifiers = (Grad, Restricted, Indexed
                 )  # FIXME: Add CellAvg, FacetAvg types here, others?
    if isinstance(expr, modifiers):
        return default_partition_seed(expr.ufl_operands[0], rank)

    elif isinstance(expr, Argument):
        ac = expr.number()
        assert 0 <= ac < rank
        poffset = 3
        p = poffset + ac
        return p

    elif isinstance(expr, Coefficient):
        if is_cellwise_constant(
                expr):  # This is crap, doesn't include grad modifier
            return 0
        else:
            return 2

    elif isinstance(expr, GeometricQuantity):
        if is_cellwise_constant(
                expr):  # This is crap, doesn't include grad modifier
            return 0
        else:
            return 1

    elif isinstance(expr, ConstantValue):
        return 0

    else:
        error("Don't know how to handle %s" % expr)
示例#26
0
    def __new__(cls, f):
        # Validate input
        sh = f.ufl_shape
        if f.ufl_shape not in ((), (2,), (3,)):
            error("Expecting a scalar, 2D vector or 3D vector.")
        if f.ufl_free_indices:
            error("Free indices in the curl argument is not allowed.")

        # Return zero if expression is trivially constant
        if is_cellwise_constant(f):
            sh = {(): (2,), (2,): (), (3,): (3,)}[sh]
            return Zero(sh)  # No free indices asserted above
        return CompoundDerivative.__new__(cls)
示例#27
0
def analyse_dependencies(V, V_deps, V_targets,
                         modified_terminal_indices,
                         modified_terminals,
                         mt_unique_table_reference):
    # Count the number of dependencies every subexpr has
    V_depcount = compute_dependency_count(V_deps)

    # Build the 'inverse' of the sparse dependency matrix
    inv_deps = invert_dependencies(V_deps, V_depcount)

    # Mark subexpressions of V that are actually needed for final result
    active, num_active = mark_active(V_deps, V_targets)

    # Build piecewise/varying markers for factorized_vertices
    varying_ttypes = ("varying", "uniform", "quadrature")
    varying_indices = []
    for i, mt in zip(modified_terminal_indices, modified_terminals):
        tr = mt_unique_table_reference.get(mt)
        if tr is not None:
            ttype = tr.ttype
            # Check if table computations have revealed values varying over points
            # Note: uniform means entity-wise uniform, varying over points
            if ttype in varying_ttypes:
                varying_indices.append(i)
            else:
                if ttype not in ("fixed", "piecewise", "ones", "zeros"):
                    error("Invalid ttype %s" % (ttype,))

        elif not is_cellwise_constant(V[i]):
            # Keeping this check to be on the safe side,
            # not sure which cases this will cover (if any)
            varying_indices.append(i)

    # Mark every subexpression that is computed
    # from the spatially dependent terminals
    varying, num_varying = mark_image(inv_deps, varying_indices)

    # The rest of the subexpressions are piecewise constant (1-1=0, 1-0=1)
    piecewise = 1 - varying

    # Unmark non-active subexpressions
    varying *= active
    piecewise *= active

    # TODO: Skip literals in both varying and piecewise
    # nonliteral = ...
    # varying *= nonliteral
    # piecewise *= nonliteral

    return inv_deps, active, piecewise, varying
示例#28
0
文件: graph_ssa.py 项目: FEniCS/ffc
def default_partition_seed(expr, rank):
    """
    Partition 0: Piecewise constant on each cell (including Real and DG0 coefficients)
    Partition 1: Depends on x
    Partition 2: Depends on x and coefficients
    Partitions [3,3+rank): depend on argument with count partition-3
    """
    # TODO: Use named constants for the partition numbers here

    modifiers = (Grad, Restricted, Indexed)  # FIXME: Add CellAvg, FacetAvg types here, others?
    if isinstance(expr, modifiers):
        return default_partition_seed(expr.ufl_operands[0], rank)

    elif isinstance(expr, Argument):
        ac = expr.number()
        assert 0 <= ac < rank
        poffset = 3
        p = poffset + ac
        return p

    elif isinstance(expr, Coefficient):
        if is_cellwise_constant(expr):  # This is crap, doesn't include grad modifier
            return 0
        else:
            return 2

    elif isinstance(expr, GeometricQuantity):
        if is_cellwise_constant(expr):  # This is crap, doesn't include grad modifier
            return 0
        else:
            return 1

    elif isinstance(expr, ConstantValue):
        return 0

    else:
        error("Don't know how to handle %s" % expr)
示例#29
0
def analyse_dependencies(V, V_deps, V_targets, modified_terminal_indices,
                         modified_terminals, mt_unique_table_reference):
    # Count the number of dependencies every subexpr has
    V_depcount = compute_dependency_count(V_deps)

    # Build the 'inverse' of the sparse dependency matrix
    inv_deps = invert_dependencies(V_deps, V_depcount)

    # Mark subexpressions of V that are actually needed for final result
    active, num_active = mark_active(V_deps, V_targets)

    # Build piecewise/varying markers for factorized_vertices
    varying_ttypes = ("varying", "uniform", "quadrature")
    varying_indices = []
    for i, mt in zip(modified_terminal_indices, modified_terminals):
        tr = mt_unique_table_reference.get(mt)
        if tr is not None:
            ttype = tr.ttype
            # Check if table computations have revealed values varying over points
            # Note: uniform means entity-wise uniform, varying over points
            if ttype in varying_ttypes:
                varying_indices.append(i)
            else:
                if ttype not in ("fixed", "piecewise", "ones", "zeros"):
                    error("Invalid ttype %s" % (ttype, ))

        elif not is_cellwise_constant(V[i]):
            # Keeping this check to be on the safe side,
            # not sure which cases this will cover (if any)
            varying_indices.append(i)

    # Mark every subexpression that is computed
    # from the spatially dependent terminals
    varying, num_varying = mark_image(inv_deps, varying_indices)

    # The rest of the subexpressions are piecewise constant (1-1=0, 1-0=1)
    piecewise = 1 - varying

    # Unmark non-active subexpressions
    varying *= active
    piecewise *= active

    # TODO: Skip literals in both varying and piecewise
    # nonliteral = ...
    # varying *= nonliteral
    # piecewise *= nonliteral

    return inv_deps, active, piecewise, varying
示例#30
0
def test_always_cellwise_constant_geometric_quantities(domains):
    "Test geometric quantities that are always constant over a cell."
    e = CellVolume(domains)
    assert is_cellwise_constant(e)
    e = CellDiameter(domains)
    assert is_cellwise_constant(e)
    e = Circumradius(domains)
    assert is_cellwise_constant(e)
    e = FacetArea(domains)
    assert is_cellwise_constant(e)
    e = MinFacetEdgeLength(domains)
    assert is_cellwise_constant(e)
    e = MaxFacetEdgeLength(domains)
    assert is_cellwise_constant(e)
示例#31
0
def mappings_are_cellwise_constant(domain, test):
    e = Jacobian(domain)
    assert is_cellwise_constant(e) == test
    e = JacobianDeterminant(domain)
    assert is_cellwise_constant(e) == test
    e = JacobianInverse(domain)
    assert is_cellwise_constant(e) == test
    if domain.topological_dimension() != 1:
        e = FacetJacobian(domain)
        assert is_cellwise_constant(e) == test
        e = FacetJacobianDeterminant(domain)
        assert is_cellwise_constant(e) == test
        e = FacetJacobianInverse(domain)
        assert is_cellwise_constant(e) == test
示例#32
0
def mappings_are_cellwise_constant(domain, test):
    e = Jacobian(domain)
    assert is_cellwise_constant(e) == test
    e = JacobianDeterminant(domain)
    assert is_cellwise_constant(e) == test
    e = JacobianInverse(domain)
    assert is_cellwise_constant(e) == test
    if domain.topological_dimension() != 1:
        e = FacetJacobian(domain)
        assert is_cellwise_constant(e) == test
        e = FacetJacobianDeterminant(domain)
        assert is_cellwise_constant(e) == test
        e = FacetJacobianInverse(domain)
        assert is_cellwise_constant(e) == test
示例#33
0
def test_coordinates_never_cellwise_constant(domains):
    e = SpatialCoordinate(domains)
    assert not is_cellwise_constant(e)
    e = CellCoordinate(domains)
    assert not is_cellwise_constant(e)
示例#34
0
def facetnormal_cellwise_constant(domain, test):
    e = FacetNormal(domain)
    assert is_cellwise_constant(e) == test
示例#35
0
def test_coefficient_mostly_not_cellwise_constant(domains_not_linear):
    V = FiniteElement("DG", domains_not_linear.ufl_cell(), 1)
    e = Coefficient(V)
    assert not is_cellwise_constant(e)
    e = TestFunction(V)
    assert not is_cellwise_constant(e)
示例#36
0
    def grad(self, o):
        # Peel off the Grads and count them, and get restriction if
        # it's between the grad and the terminal
        ngrads = 0
        restricted = ''
        rv = False
        while not o._ufl_is_terminal_:
            if isinstance(o, Grad):
                o, = o.ufl_operands
                ngrads += 1
            elif isinstance(o, Restricted):
                restricted = o.side()
                o, = o.ufl_operands
            elif isinstance(o, ReferenceValue):
                rv = True
                o, = o.ufl_operands
            else:
                error("Invalid type %s" % o._ufl_class_.__name__)
        f = o
        if rv:
            f = ReferenceValue(f)

        # Get domain and create Jacobian inverse object
        domain = o.ufl_domain()
        Jinv = JacobianInverse(domain)

        if is_cellwise_constant(Jinv):
            # Optimise slightly by turning Grad(Grad(...)) into
            # J^(-T)J^(-T)RefGrad(RefGrad(...))
            # rather than J^(-T)RefGrad(J^(-T)RefGrad(...))

            # Create some new indices
            ii = indices(len(f.ufl_shape))  # Indices to get to the scalar component of f
            jj = indices(ngrads)  # Indices to sum over the local gradient axes with the inverse Jacobian
            kk = indices(ngrads)  # Indices for the leftover inverse Jacobian axes

            # Preserve restricted property
            if restricted:
                Jinv = Jinv(restricted)
                f = f(restricted)

            # Apply the same number of ReferenceGrad without mappings
            lgrad = f
            for i in range(ngrads):
                lgrad = ReferenceGrad(lgrad)

            # Apply mappings with scalar indexing operations (assumes
            # ReferenceGrad(Jinv) is zero)
            jinv_lgrad_f = lgrad[ii + jj]
            for j, k in zip(jj, kk):
                jinv_lgrad_f = Jinv[j, k] * jinv_lgrad_f

            # Wrap back in tensor shape, derivative axes at the end
            jinv_lgrad_f = as_tensor(jinv_lgrad_f, ii + kk)

        else:
            # J^(-T)RefGrad(J^(-T)RefGrad(...))

            # Preserve restricted property
            if restricted:
                Jinv = Jinv(restricted)
                f = f(restricted)

            jinv_lgrad_f = f
            for foo in range(ngrads):
                ii = indices(len(jinv_lgrad_f.ufl_shape))  # Indices to get to the scalar component of f
                j, k = indices(2)

                lgrad = ReferenceGrad(jinv_lgrad_f)
                jinv_lgrad_f = Jinv[j, k] * lgrad[ii + (j,)]

                # Wrap back in tensor shape, derivative axes at the end
                jinv_lgrad_f = as_tensor(jinv_lgrad_f, ii + (k,))

        return jinv_lgrad_f
示例#37
0
 def coefficient(self, o):
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     return Grad(o)
示例#38
0
 def coefficient(self, o):
     if is_cellwise_constant(o):
         return self.independent_terminal(o)
     return Grad(o)
示例#39
0
def test_coefficient_mostly_not_cellwise_constant(domains_not_linear):
    V = FiniteElement("DG", domains_not_linear.ufl_cell(), 1)
    e = Coefficient(V)
    assert not is_cellwise_constant(e)
    e = TestFunction(V)
    assert not is_cellwise_constant(e)
示例#40
0
def test_coordinates_never_cellwise_constant(domains):
    e = SpatialCoordinate(domains)
    assert not is_cellwise_constant(e)
    e = CellCoordinate(domains)
    assert not is_cellwise_constant(e)
示例#41
0
def facetnormal_cellwise_constant(domain, test):
    e = FacetNormal(domain)
    assert is_cellwise_constant(e) == test