From 5cc7906f9c8e03d084c3373d99517fcd65a6c9db Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 28 Jul 2026 18:20:45 +0100 Subject: [PATCH 1/2] Inherit entity permutations in RestrictedDualSet An entity whose dofs are all kept by the restriction keeps their relative order, so it permutes under orientation exactly as it did before, and 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 ones it drops may be where the parent permutation sends the ones it keeps, so no permutations are reported at all in that case. Without this a restricted element reports no entity permutations, and consumers that orient dofs entity by entity cannot lay out a restricted space consistently with the space it restricts. Co-Authored-By: Claude Opus 5 --- FIAT/restricted.py | 34 +++++++++++++++++++++++++++++- test/FIAT/unit/test_orientation.py | 20 ++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/FIAT/restricted.py b/FIAT/restricted.py index f72a731a7..55215fa10 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 7f9da514b..76cb43ba5 100644 --- a/test/FIAT/unit/test_orientation.py +++ b/test/FIAT/unit/test_orientation.py @@ -69,3 +69,23 @@ def test_orientation_cell_orientation_reflection_map(cell): (1, 0, 1): 0, (1, 1, 0): 0, (1, 1, 1): 1} + + +@pytest.mark.parametrize("degree", [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. + from FIAT import Lagrange, RestrictedElement + + 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 == [] From 4b1df8c60d12b2784c14a38206916857a15f8030 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 5 Aug 2026 11:58:05 +0100 Subject: [PATCH 2/2] Extend RestrictedDualSet entity permutation tests Cover degree 1,2 Lagrange facet restrictions, and add a case where restricting to a single dof on a shared entity drops permutations entirely. Co-Authored-By: Claude Sonnet 5 --- test/FIAT/unit/test_orientation.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/FIAT/unit/test_orientation.py b/test/FIAT/unit/test_orientation.py index 76cb43ba5..b83b07981 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 @@ -71,12 +72,10 @@ def test_orientation_cell_orientation_reflection_map(cell): (1, 1, 1): 1} -@pytest.mark.parametrize("degree", [3, 4, 5]) +@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. - from FIAT import Lagrange, RestrictedElement - element = Lagrange(UFCTriangle(), degree) restricted = RestrictedElement(element, restriction_domain="facet") @@ -89,3 +88,15 @@ def test_orientation_restricted_element(degree): # 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()