diff --git a/FIAT/restricted.py b/FIAT/restricted.py index f72a731a..55215fa1 100644 --- a/FIAT/restricted.py +++ b/FIAT/restricted.py @@ -24,8 +24,40 @@ def __init__(self, dual, indices): for dof in dofs if dof in indices] nodes = [nodes_old[i] for i in indices] self._dual = dual + entity_permutations = self._restrict_entity_permutations(dual, entity_ids) - super().__init__(nodes, ref_el, entity_ids) + super().__init__(nodes, ref_el, entity_ids, entity_permutations) + + @staticmethod + def _restrict_entity_permutations(dual, entity_ids): + """Inherit the entity permutations of the dofs kept by the restriction. + + An entity whose dofs are all kept keeps their relative order, and so + permutes exactly as it did in ``dual``; an entity whose dofs are all + dropped permutes trivially. An entity that keeps only some of its dofs + has no permutation to inherit, since the dofs it drops may be the ones + the parent permutation maps the others onto, so no permutations are + given at all in that case. + + :arg dual: the DualSet being restricted. + :arg entity_ids: the entity ids of the restricted dual set. + :returns: the entity permutations, or None if they cannot be inherited. + """ + permutations_old = dual.entity_permutations + if permutations_old is None: + return None + entity_permutations = {} + for d, entities in dual.get_entity_ids().items(): + entity_permutations[d] = {} + for entity, dofs in entities.items(): + perms = permutations_old[d][entity] + if len(entity_ids[d][entity]) == len(dofs): + entity_permutations[d][entity] = {o: list(p) for o, p in perms.items()} + elif len(entity_ids[d][entity]) == 0: + entity_permutations[d][entity] = {o: [] for o in perms} + else: + return None + return entity_permutations def get_indices(self, restriction_domain, take_closure=True): """Return the list of dofs with support on a given restriction domain. diff --git a/test/FIAT/unit/test_orientation.py b/test/FIAT/unit/test_orientation.py index 7f9da514..b83b0798 100644 --- a/test/FIAT/unit/test_orientation.py +++ b/test/FIAT/unit/test_orientation.py @@ -1,4 +1,5 @@ import pytest +from FIAT import DiscontinuousLagrange, Lagrange, RestrictedElement from FIAT.reference_element import Point, UFCInterval, UFCTriangle, UFCQuadrilateral from FIAT.orientation_utils import make_entity_permutations_tensorproduct @@ -69,3 +70,33 @@ def test_orientation_cell_orientation_reflection_map(cell): (1, 0, 1): 0, (1, 1, 0): 0, (1, 1, 1): 1} + + +@pytest.mark.parametrize("degree", [1, 2, 3, 4, 5]) +def test_orientation_restricted_element(degree): + # A restriction keeps the relative order of the dofs on the entities it + # keeps, so those entities must permute exactly as they did before it. + element = Lagrange(UFCTriangle(), degree) + restricted = RestrictedElement(element, restriction_domain="facet") + + permutations = element.dual.get_entity_permutations() + restricted_permutations = restricted.dual.get_entity_permutations() + + for dim in (0, 1): + for entity in permutations[dim]: + assert restricted_permutations[dim][entity] == permutations[dim][entity] + # The cell is dropped, so it has no dofs left to permute. + for orientation, perm in restricted_permutations[2][0].items(): + assert perm == [] + + +def test_orientation_restricted_element_partial_entity(): + # An entity that keeps only some of its dofs has no permutation to + # inherit, since the dofs it drops may be where the parent permutation + # sends the ones it keeps, so no permutations are reported at all. + element = DiscontinuousLagrange(UFCTriangle(), 1) + restricted = RestrictedElement(element, indices=[0]) + + assert restricted.dual.entity_permutations is None + with pytest.raises(NotImplementedError): + restricted.dual.get_entity_permutations()