diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 47dbf597e..1af30db70 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -45,7 +45,7 @@ jobs: with: path: ./ffcx repository: FEniCS/ffcx - ref: main + ref: mscroggs/ufl_coordinate_elements - name: Install FFCx run: | @@ -76,14 +76,14 @@ jobs: - name: Install Basix and FFCx run: | python3 -m pip install --break-system-packages git+https://github.com/FEniCS/basix.git - python3 -m pip install --break-system-packages git+https://github.com/FEniCS/ffcx.git + python3 -m pip install --break-system-packages git+https://github.com/FEniCS/ffcx.git@mscroggs/ufl_coordinate_elements - name: Clone DOLFINx uses: actions/checkout@v4 with: path: ./dolfinx repository: FEniCS/dolfinx - ref: main + ref: mscroggs/ufl_coordinate_elements - name: Install DOLFINx run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S dolfinx/cpp/ diff --git a/test/test_derivative.py b/test/test_derivative.py index 84e755dcb..9c3e64d67 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -107,11 +107,11 @@ def make_value(c): acell = adomain.ufl_cell() bcell = bdomain.ufl_cell() assert acell == bcell - if adomain.geometric_dimension() == 1: + if adomain.geometric_dimension == 1: x = (0.3,) - elif adomain.geometric_dimension() == 2: + elif adomain.geometric_dimension == 2: x = (0.3, 0.4) - elif adomain.geometric_dimension() == 3: + elif adomain.geometric_dimension == 3: x = (0.3, 0.4, 0.5) av = a(x, amapping) bv = b(x, bmapping) diff --git a/test/test_domains.py b/test/test_domains.py index 9ad8dc75c..553bfef33 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -68,18 +68,19 @@ def test_domains_sort_by_name(): ) for cell in sorted(all_cells) ] - sdomains = sorted(domains1, key=lambda D: (D.topological_dimension(), D.ufl_cell(), D.ufl_id())) + sdomains = sorted( + domains1, key=lambda D: (D.ufl_cell().topological_dimension(), D.ufl_cell(), D.ufl_id()) + ) assert sdomains != domains1 assert sdomains == domains2 def test_topdomain_creation(): D = Mesh(LagrangeElement(interval, 1, (1,))) - assert D.geometric_dimension() == 1 + assert D.geometric_dimension == 1 D = Mesh(LagrangeElement(triangle, 1, (2,))) - assert D.geometric_dimension() == 2 - D = Mesh(LagrangeElement(tetrahedron, 1, (3,))) - assert D.geometric_dimension() == 3 + assert D.geometric_dimension == 2 + D = Mesh(LagrangeElement(triangle, 1, (3,))) def test_cell_legacy_case(): @@ -377,7 +378,7 @@ def test_merge_sort_integral_data(): def test_extract_domains(): - "Test that the domains are extracted properly from a mixed-domain expression" + """Test that the domains are extracted properly from a mixed-domain expression.""" # Create domains of different topological dimensions gdim = 2 @@ -404,5 +405,29 @@ def test_extract_domains(): domains = extract_domains(expr) - assert domains[0] == dom_1 - assert domains[1] == dom_0 + assert domains[0] == dom_0 + assert domains[1] == dom_1 + + +def test_hybrid_mesh(): + """Test meshes with multiple cell types.""" + domain = Mesh( + [ + LagrangeElement(triangle, 1, (2,)), + LagrangeElement(quadrilateral, 1, (2,)), + ] + ) + + elements = [LagrangeElement(triangle, 1), LagrangeElement(quadrilateral, 1)] + + space = FunctionSpace(domain, elements) + + # Create test and trial functions + u = TrialFunction(space) + v = TestFunction(space) + + # Create an expression + expr = u * v + + # Create an integral + expr * dx diff --git a/test/test_functionspace.py b/test/test_functionspace.py new file mode 100644 index 000000000..d1c530096 --- /dev/null +++ b/test/test_functionspace.py @@ -0,0 +1,27 @@ +"""Tests of function spaces.""" + +import pytest +from utils import LagrangeElement + +from ufl import FunctionSpace, Mesh, quadrilateral, triangle + + +def test_cell_mismatch(): + domain = Mesh(LagrangeElement(triangle, 1, (2,))) + elements = LagrangeElement(quadrilateral, 1) + + with pytest.raises(ValueError): + FunctionSpace(domain, elements) + + +def test_wrong_order(): + domain = Mesh( + [ + LagrangeElement(quadrilateral, 1, (2,)), + LagrangeElement(triangle, 1, (2,)), + ] + ) + elements = [LagrangeElement(triangle, 1), LagrangeElement(quadrilateral, 1)] + + with pytest.raises(ValueError): + FunctionSpace(domain, elements) diff --git a/test/test_measures.py b/test/test_measures.py index 61528a3b1..c628a601e 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -75,8 +75,8 @@ def test_foo(): assert cell.topological_dimension() == tdim assert cell.cellname() == "triangle" - assert mydomain.topological_dimension() == tdim - assert mydomain.geometric_dimension() == gdim + assert mydomain.ufl_cell().topological_dimension() == tdim + assert mydomain.geometric_dimension == gdim assert mydomain.ufl_cell() == cell assert mydomain.ufl_id() == 9 assert mydomain.ufl_cargo() == mymesh diff --git a/test/test_piecewise_checks.py b/test/test_piecewise_checks.py index 0f27c8b28..1429d4df4 100755 --- a/test/test_piecewise_checks.py +++ b/test/test_piecewise_checks.py @@ -280,14 +280,14 @@ def mappings_are_cellwise_constant(domain, test): assert is_cellwise_constant(e) == test e = JacobianInverse(domain) assert is_cellwise_constant(e) == test - if domain.topological_dimension() != 1: + if domain.ufl_cell().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 - if domain.topological_dimension() > 2: + if domain.ufl_cell().topological_dimension() > 2: e = RidgeJacobian(domain) assert is_cellwise_constant(e) == test e = RidgeJacobianDeterminant(domain) diff --git a/ufl/__init__.py b/ufl/__init__.py index 0a48f8359..e20d43e06 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -63,7 +63,6 @@ -AbstractDomain -Mesh -MeshSequence - -MeshView * Sobolev spaces:: @@ -266,7 +265,7 @@ from ufl.core.external_operator import ExternalOperator from ufl.core.interpolate import Interpolate, interpolate from ufl.core.multiindex import Index, indices -from ufl.domain import AbstractDomain, Mesh, MeshSequence, MeshView +from ufl.domain import AbstractDomain, Mesh, MeshSequence from ufl.finiteelement import AbstractFiniteElement from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm from ufl.formoperators import ( @@ -494,7 +493,6 @@ "Measure", "Mesh", "MeshSequence", - "MeshView", "MinCellEdgeLength", "MinFacetEdgeLength", "MixedFunctionSpace", diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index 472c7b650..e54900291 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -118,7 +118,7 @@ def jacobian_determinant(self, o): # TODO: Is "signing" the determinant for manifolds the # cleanest approach? The alternative is to have a # specific type for the unsigned pseudo-determinant. - if domain.topological_dimension() < domain.geometric_dimension(): + if domain.ufl_cell().topological_dimension() < domain.geometric_dimension: co = CellOrientation(domain) detJ = co * detJ return detJ @@ -265,7 +265,7 @@ def facet_area(self, o): return o domain = extract_unique_domain(o) - tdim = domain.topological_dimension() + tdim = domain.ufl_cell().topological_dimension() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler @@ -420,8 +420,8 @@ def cell_normal(self, o): return o domain = extract_unique_domain(o) - gdim = domain.geometric_dimension() - tdim = domain.topological_dimension() + gdim = domain.geometric_dimension + tdim = domain.ufl_cell().topological_dimension() if tdim == gdim - 1: # n-manifold embedded in n-1 space i = Index() @@ -453,7 +453,7 @@ def facet_normal(self, o): return o domain = extract_unique_domain(o) - tdim = domain.topological_dimension() + tdim = domain.ufl_cell().topological_dimension() if tdim == 1: # Special-case 1D (possibly immersed), for which we say @@ -461,7 +461,7 @@ def facet_normal(self, o): J = self.jacobian(Jacobian(domain)) # dx/dX ndir = J[:, 0] - gdim = domain.geometric_dimension() + gdim = domain.geometric_dimension if gdim == 1: nlen = abs(ndir[0]) else: diff --git a/ufl/algorithms/apply_integral_scaling.py b/ufl/algorithms/apply_integral_scaling.py index a7e7bc2c1..f935732d8 100644 --- a/ufl/algorithms/apply_integral_scaling.py +++ b/ufl/algorithms/apply_integral_scaling.py @@ -24,10 +24,9 @@ def compute_integrand_scaling_factor(integral): """Change integrand geometry to the right representations.""" domain = integral.ufl_domain() integral_type = integral.integral_type() - # co = CellOrientation(domain) weight = QuadratureWeight(domain) - tdim = domain.topological_dimension() - # gdim = domain.geometric_dimension() + assert len(domain.ufl_coordinate_elements()) == 1 # TODO: remove this assumption + tdim = domain.ufl_coordinate_elements()[0].cell.topological_dimension() # Polynomial degree of integrand scaling degree = 0 diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index d2a4ace41..65c715492 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -175,8 +175,8 @@ def facet_normal(self, o): """Restrict a facet_normal.""" D = extract_unique_domain(o) e = D.ufl_coordinate_element() - gd = D.geometric_dimension() - td = D.topological_dimension() + gd = D.geometric_dimension + td = D.ufl_cell().topological_dimension() if e.embedded_superdegree <= 1 and e in H1 and gd == td: # For meshes with a continuous linear non-manifold diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index 09f98af4e..e867bd98e 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -397,7 +397,7 @@ def compute_form_data( self.rank = len(self.original_form.arguments()) # Extract common geometric dimension (topological is not common!) - self.geometric_dimension = self.original_form.integrals()[0].ufl_domain().geometric_dimension() + self.geometric_dimension = self.original_form.integrals()[0].ufl_domain().geometric_dimension # --- Build mapping from old incomplete element objects to new # well defined elements. This is to support the Expression diff --git a/ufl/algorithms/strip_terminal_data.py b/ufl/algorithms/strip_terminal_data.py index a270d3273..6ca01ceac 100644 --- a/ufl/algorithms/strip_terminal_data.py +++ b/ufl/algorithms/strip_terminal_data.py @@ -12,7 +12,6 @@ FunctionSpace, Integral, Mesh, - MeshView, MixedFunctionSpace, TensorProductFunctionSpace, ) @@ -126,9 +125,5 @@ def strip_domain(domain): """Return a new domain with all non-UFL information removed.""" if isinstance(domain, Mesh): return Mesh(domain.ufl_coordinate_element(), domain.ufl_id()) - elif isinstance(domain, MeshView): - return MeshView( - strip_domain(domain.ufl_mesh()), domain.topological_dimension(), domain.ufl_id() - ) else: raise NotImplementedError(f"{type(domain)} cannot be stripped") diff --git a/ufl/constant.py b/ufl/constant.py index dfc65aa06..ce0f0a2f5 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -78,12 +78,12 @@ def _ufl_signature_data_(self, renumbering): def VectorConstant(domain, count=None): """Vector constant.""" domain = as_domain(domain) - return Constant(domain, shape=(domain.geometric_dimension(),), count=count) + return Constant(domain, shape=(domain.geometric_dimension,), count=count) def TensorConstant(domain, count=None): """Tensor constant.""" domain = as_domain(domain) return Constant( - domain, shape=(domain.geometric_dimension(), domain.geometric_dimension()), count=count + domain, shape=(domain.geometric_dimension, domain.geometric_dimension), count=count ) diff --git a/ufl/differentiation.py b/ufl/differentiation.py index c319ac716..0392066f3 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -318,15 +318,23 @@ def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): # TODO: Use max topological dimension if there are multiple topological dimensions. - dim = extract_unique_domain(f, expand_mesh_sequence=False).topological_dimension() + domain_elements = extract_unique_domain( + f, expand_mesh_sequence=False + ).ufl_coordinate_elements() + dim = domain_elements[0].cell.topological_dimension() + for e in domain_elements: + # TODO: remove this assumption + assert e.cell.topological_dimension() == dim return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): """Initalise.""" CompoundDerivative.__init__(self, (f,)) - # TODO: Use max topological dimension if there are multiple topological dimensions. - self._dim = extract_unique_domain(f, expand_mesh_sequence=False).topological_dimension() + self._dim = max( + e.cell.topological_dimension() + for e in extract_unique_domain(f, expand_mesh_sequence=False).ufl_coordinate_elements() + ) def _ufl_expr_reconstruct_(self, op): """Return a new object of the same type with new operands.""" diff --git a/ufl/domain.py b/ufl/domain.py index a951ce15b..347c5dd0c 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -9,65 +9,59 @@ from __future__ import annotations # To avoid cyclic import when type-hinting. import numbers +from abc import ABC, abstractmethod from collections.abc import Iterable, Sequence -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: from ufl.core.expr import Expr - from ufl.finiteelement import AbstractFiniteElement # To avoid cyclic import when type-hinting. from ufl.form import Form -from ufl.cell import AbstractCell from ufl.core.ufl_id import attach_ufl_id from ufl.core.ufl_type import UFLObject from ufl.corealg.traversal import traverse_unique_terminals +from ufl.finiteelement import AbstractFiniteElement from ufl.sobolevspace import H1 # Export list for ufl.classes -__all_classes__ = ["AbstractDomain", "Mesh", "MeshView"] +__all_classes__ = ["AbstractDomain", "Mesh"] -class AbstractDomain: +class AbstractDomain(ABC): """Symbolic representation of a geometric domain. - Domain has only a geometric and a topological dimension. + Domain has only a geometric dimension. """ - def __init__(self, topological_dimension, geometric_dimension): + _geometric_dimension: int + _meshes: Sequence[AbstractDomain] + + def __init__(self, geometric_dimension): """Initialise.""" # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): raise ValueError( f"Expecting integer geometric dimension, not {geometric_dimension.__class__}" ) - if not isinstance(topological_dimension, numbers.Integral): - raise ValueError( - f"Expecting integer topological dimension, not {topological_dimension.__class__}" - ) - if topological_dimension > geometric_dimension: - raise ValueError("Topological dimension cannot be larger than geometric dimension.") # Store validated dimensions - self._topological_dimension = topological_dimension self._geometric_dimension = geometric_dimension + @property def geometric_dimension(self): """Return the dimension of the space this domain is embedded in.""" return self._geometric_dimension - def topological_dimension(self): - """Return the dimension of the topology of this domain.""" - return self._topological_dimension - @property def meshes(self): """Return the component meshes.""" - raise NotImplementedError("meshes() method not implemented") + assert self.meshes is not None + return self._meshes def __len__(self): """Return number of component meshes.""" return len(self.meshes) - def __getitem__(self, i): + def __getitem__(self, i: int) -> AbstractDomain: """Return i-th component mesh.""" if i >= len(self): raise ValueError(f"index ({i}) >= num. component meshes ({len(self)})") @@ -77,13 +71,15 @@ def __iter__(self): """Return iterable component meshes.""" return iter(self.meshes) + @abstractmethod def iterable_like(self, element: AbstractFiniteElement) -> Iterable[Mesh] | MeshSequence: """Return iterable object that is iterable like ``element``.""" - raise NotImplementedError("iterable_like() method not implemented") - def can_make_function_space(self, element: AbstractFiniteElement) -> bool: + @abstractmethod + def can_make_function_space( + self, element: Sequence[AbstractFiniteElement] | AbstractFiniteElement + ) -> bool: """Check whether this mesh can make a function space with ``element``.""" - raise NotImplementedError("can_make_function_space() method not implemented") # TODO: Would it be useful to have a domain representing R^d? E.g. for @@ -97,7 +93,15 @@ def can_make_function_space(self, element: AbstractFiniteElement) -> bool: class Mesh(AbstractDomain, UFLObject): """Symbolic representation of a mesh.""" - def __init__(self, coordinate_element, ufl_id=None, cargo=None): + _ufl_id: int + _ufl_coordinate_elements: tuple[AbstractFiniteElement, ...] + + def __init__( + self, + coordinate_element: Sequence[AbstractFiniteElement] | AbstractFiniteElement, + ufl_id: Optional[int] = None, + cargo: Any = None, + ): """Initialise.""" self._ufl_id = self._init_ufl_id(ufl_id) @@ -106,19 +110,17 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): if cargo is not None and cargo.ufl_id() != self._ufl_id: raise ValueError("Expecting cargo object (e.g. dolfin.Mesh) to have the same ufl_id.") - # No longer accepting coordinates provided as a Coefficient - from ufl.coefficient import Coefficient - - if isinstance(coordinate_element, (Coefficient, AbstractCell)): - raise ValueError("Expecting a coordinate element in the ufl.Mesh construct.") - - # Store coordinate element - self._ufl_coordinate_element = coordinate_element + if isinstance(coordinate_element, AbstractFiniteElement): + self._ufl_coordinate_elements = (coordinate_element,) + else: + self._ufl_coordinate_elements = tuple(coordinate_element) # Derive dimensions from element - (gdim,) = coordinate_element.reference_value_shape - tdim = coordinate_element.cell.topological_dimension() - AbstractDomain.__init__(self, tdim, gdim) + (gdim,) = self._ufl_coordinate_elements[0].reference_value_shape + for c_el in self._ufl_coordinate_elements: + if c_el.reference_value_shape != (gdim,): + raise ValueError("Coordinate elements must have the same reference value shape.") + AbstractDomain.__init__(self, gdim) def ufl_cargo(self): """Return carried object that will not be used by UFL.""" @@ -126,20 +128,37 @@ def ufl_cargo(self): def ufl_coordinate_element(self): """Get the coordinate element.""" - return self._ufl_coordinate_element + if len(self._ufl_coordinate_elements) != 1: + raise ValueError( + "Cannot determine coordinate element from multiple coordinate elements." + ) + return self._ufl_coordinate_elements[0] + + def ufl_coordinate_elements(self): + """Get the coordinate elements.""" + return self._ufl_coordinate_elements def ufl_cell(self): """Get the cell.""" - return self._ufl_coordinate_element.cell + if len(self._ufl_coordinate_elements) != 1: + raise ValueError("Cannot determine cell type from multiple coordinate elements.") + return self._ufl_coordinate_elements[0].cell + + def ufl_cells(self): + """Get the cell.""" + return tuple(c_el.cell for c_el in self._ufl_coordinate_elements) def is_piecewise_linear_simplex_domain(self): """Check if the domain is a piecewise linear simplex.""" - ce = self._ufl_coordinate_element - return ce.embedded_superdegree <= 1 and ce in H1 and self.ufl_cell().is_simplex() + + def simplex_check(c_el): + return c_el.embedded_superdegree <= 1 and c_el in H1 and c_el.cell.is_simplex() + + return all(simplex_check(c_el) for c_el in self._ufl_coordinate_elements) def __repr__(self): """Representation.""" - r = f"Mesh({self._ufl_coordinate_element!r}, {self._ufl_id!r})" + r = f"Mesh({self._ufl_coordinate_elements!r}, {self._ufl_id!r})" return r def __str__(self): @@ -148,18 +167,18 @@ def __str__(self): def _ufl_hash_data_(self): """UFL hash data.""" - return (self._ufl_id, self._ufl_coordinate_element) + return (self._ufl_id, self._ufl_coordinate_elements) def _ufl_signature_data_(self, renumbering): """UFL signature data.""" - return ("Mesh", renumbering[self], self._ufl_coordinate_element) + return ("Mesh", renumbering[self], self._ufl_coordinate_elements) # NB! Dropped __lt__ here, don't want users to write 'mesh1 < # mesh2'. def _ufl_sort_key_(self): """UFL sort key.""" - typespecific = (self._ufl_id, self._ufl_coordinate_element) - return (self.geometric_dimension(), self.topological_dimension(), "Mesh", typespecific) + typespecific = (self._ufl_id, self._ufl_coordinate_elements) + return (self.geometric_dimension, "Mesh", typespecific) @property def meshes(self): @@ -170,9 +189,20 @@ def iterable_like(self, element: AbstractFiniteElement) -> Iterable[Mesh]: """Return iterable object that is iterable like ``element``.""" return iter(self for _ in range(element.num_sub_elements)) - def can_make_function_space(self, element: AbstractFiniteElement) -> bool: + def can_make_function_space( + self, + element: Sequence[AbstractFiniteElement] | AbstractFiniteElement, + ) -> bool: """Check whether this mesh can make a function space with ``element``.""" - # Can use with any element. + if isinstance(element, AbstractFiniteElement): + if len(self._ufl_coordinate_elements) != 1: + return False + if self._ufl_coordinate_elements[0].cell != element.cell: + return False + else: + for ce, e in zip(self._ufl_coordinate_elements, element): + if ce.cell != e.cell: + return False return True @@ -213,10 +243,8 @@ def __init__(self, meshes: Sequence[Mesh]): Currently component meshes can not include MeshSequence instances""") # currently only support single cell type. (self._ufl_cell,) = set(m.ufl_cell() for m in meshes) - (gdim,) = set(m.geometric_dimension() for m in meshes) - # TODO: Need to change for more general mixed meshes. - (tdim,) = set(m.topological_dimension() for m in meshes) - AbstractDomain.__init__(self, tdim, gdim) + (gdim,) = set(m.geometric_dimension for m in meshes) + AbstractDomain.__init__(self, gdim) self._meshes = tuple(meshes) def ufl_cell(self): @@ -256,70 +284,21 @@ def iterable_like(self, element: AbstractFiniteElement) -> MeshSequence: element.num_sub_elements ({element.num_sub_elements})""") return self - def can_make_function_space(self, element: AbstractFiniteElement) -> bool: + def can_make_function_space( + self, element: Sequence[AbstractFiniteElement] | AbstractFiniteElement + ) -> bool: """Check whether this mesh can make a function space with ``element``.""" + # TODO: remove this assumption + assert isinstance(element, AbstractFiniteElement) + if len(self) != element.num_sub_elements: return False else: return all(d.can_make_function_space(e) for d, e in zip(self, element.sub_elements)) - -@attach_ufl_id -class MeshView(AbstractDomain, UFLObject): - """Symbolic representation of a mesh.""" - - def __init__(self, mesh, topological_dimension, ufl_id=None): - """Initialise.""" - self._ufl_id = self._init_ufl_id(ufl_id) - - # Store mesh - self._ufl_mesh = mesh - - # Derive dimensions from element - coordinate_element = mesh.ufl_coordinate_element() - (gdim,) = coordinate_element.value_shape - tdim = coordinate_element.cell.topological_dimension() - AbstractDomain.__init__(self, tdim, gdim) - - def ufl_mesh(self): - """Get the mesh.""" - return self._ufl_mesh - - def ufl_cell(self): - """Get the cell.""" - return self._ufl_mesh.ufl_cell() - - def is_piecewise_linear_simplex_domain(self): - """Check if the domain is a piecewise linear simplex.""" - return self._ufl_mesh.is_piecewise_linear_simplex_domain() - - def __repr__(self): - """Representation.""" - tdim = self.topological_dimension() - r = f"MeshView({self._ufl_mesh!r}, {tdim!r}, {self._ufl_id!r})" - return r - - def __str__(self): - """Format as a string.""" - return ( - f"" - ) - - def _ufl_hash_data_(self): - """UFL hash data.""" - return (self._ufl_id,) + self._ufl_mesh._ufl_hash_data_() - - def _ufl_signature_data_(self, renumbering): - """UFL signature data.""" - return ("MeshView", renumbering[self], self._ufl_mesh._ufl_signature_data_(renumbering)) - - # NB! Dropped __lt__ here, don't want users to write 'mesh1 < - # mesh2'. - def _ufl_sort_key_(self): - """UFL sort key.""" - typespecific = (self._ufl_id, self._ufl_mesh) - return (self.geometric_dimension(), self.topological_dimension(), "MeshView", typespecific) + def ufl_coordinate_elements(self): + """Get the coordinate elements.""" + return [e for m in self.meshes for e in m.ufl_coordinate_elements()] def as_domain(domain): @@ -374,7 +353,7 @@ def join_domains(domains: Sequence[AbstractDomain], expand_mesh_sequence: bool = # Check geometric dimension compatibility gdims = set() for domain in joined_domains: - gdims.add(domain.geometric_dimension()) + gdims.add(domain.geometric_dimension) if len(gdims) != 1: raise ValueError("Found domains with different geometric dimensions.") @@ -437,7 +416,7 @@ def find_geometric_dimension(expr): # Can have multiple domains of the same cell type. domains = extract_domains(t) if len(domains) > 0: - (gdim,) = set(domain.geometric_dimension() for domain in domains) + (gdim,) = set(domain.geometric_dimension for domain in domains) gdims.add(gdim) if len(gdims) != 1: diff --git a/ufl/finiteelement.py b/ufl/finiteelement.py index 73fa8a3e9..7bc4ef9ad 100644 --- a/ufl/finiteelement.py +++ b/ufl/finiteelement.py @@ -16,9 +16,11 @@ import typing from collections.abc import Sequence -from ufl.cell import Cell as _Cell -from ufl.pullback import AbstractPullback as _AbstractPullback -from ufl.sobolevspace import SobolevSpace as _SobolevSpace +if typing.TYPE_CHECKING: + # To avoid cyclic import when type-hinting. + from ufl.cell import Cell + from ufl.pullback import AbstractPullback + from ufl.sobolevspace import SobolevSpace from ufl.utils.sequences import product __all_classes__ = ["AbstractFiniteElement"] @@ -55,11 +57,11 @@ def __eq__(self, other: object) -> bool: """Check if this element is equal to another element.""" @_abc.abstractproperty - def sobolev_space(self) -> _SobolevSpace: + def sobolev_space(self) -> SobolevSpace: """Return the underlying Sobolev space.""" @_abc.abstractproperty - def pullback(self) -> _AbstractPullback: + def pullback(self) -> AbstractPullback: """Return the pullback for this element.""" @_abc.abstractproperty @@ -95,7 +97,7 @@ def embedded_subdegree(self) -> int: """ @_abc.abstractproperty - def cell(self) -> _Cell: + def cell(self) -> Cell: """Return the cell of the finite element.""" @_abc.abstractproperty diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 95757046e..6552f2668 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -9,11 +9,15 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 +from collections.abc import Sequence +from typing import Union + import numpy as np from ufl.core.ufl_type import UFLObject -from ufl.domain import join_domains +from ufl.domain import AbstractDomain, join_domains from ufl.duals import is_dual, is_primal +from ufl.finiteelement import AbstractFiniteElement from ufl.utils.sequences import product # Export list for ufl.classes @@ -39,28 +43,24 @@ def ufl_sub_spaces(self): class BaseFunctionSpace(AbstractFunctionSpace, UFLObject): """Base function space.""" - def __init__(self, domain, element, label=""): + def __init__( + self, + domain: AbstractDomain, + element: Union[Sequence[AbstractFiniteElement], AbstractFiniteElement], + label: str = "", + ): """Initialise.""" - if domain is None: - # DOLFIN hack - # TODO: Is anything expected from element.cell in this case? - pass + if not domain.can_make_function_space(element): + raise ValueError( + f"Mismatching cell types in domain ({domain}) and element ({element})." + ) + if isinstance(element, AbstractFiniteElement): + self._ufl_elements: tuple[AbstractFiniteElement, ...] = (element,) else: - try: - domain_cell = domain.ufl_cell() - except AttributeError: - raise ValueError( - "Expected non-abstract domain for initalization of function space." - ) - else: - if element.cell != domain_cell: - raise ValueError("Non-matching cell of finite element and domain.") - if not domain.can_make_function_space(element): - raise ValueError(f"Mismatching domain ({domain}) and element ({element}).") + self._ufl_elements = tuple(element) AbstractFunctionSpace.__init__(self) - self._label = label self._ufl_domain = domain - self._ufl_element = element + self._label = label @property def components(self) -> dict[tuple[int, ...], int]: @@ -72,10 +72,11 @@ def components(self) -> dict[tuple[int, ...], int]: """ from ufl.pullback import SymmetricPullback - if isinstance(self._ufl_element.pullback, SymmetricPullback): - return self._ufl_element.pullback._symmetry + e = self._ufl_elements[0] + if isinstance(e.pullback, SymmetricPullback): + return e.pullback._symmetry - if len(self._ufl_element.sub_elements) == 0: + if len(e.sub_elements) == 0: return {(): 0} components: dict[tuple[int, ...], int] = {} @@ -102,7 +103,13 @@ def ufl_domain(self): def ufl_element(self): """Return ufl element.""" - return self._ufl_element + if len(self._ufl_elements) > 1: + raise ValueError("Cannot get the element of a function space with multiple elements.") + return self._ufl_elements[0] + + def ufl_elements(self): + """Return ufl elements.""" + return self._ufl_elements def ufl_domains(self): """Return ufl domains.""" @@ -144,12 +151,14 @@ def _ufl_signature_data_(self, renumbering, name=None): def __repr__(self): """Representation.""" - return f"BaseFunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})" + return f"BaseFunctionSpace({self._ufl_domain!r}, {self._ufl_elements!r})" @property def value_shape(self) -> tuple[int, ...]: """Return the shape of the value space on a physical domain.""" - return self._ufl_element.pullback.physical_value_shape(self._ufl_element, self._ufl_domain) + return self._ufl_elements[0].pullback.physical_value_shape( + self._ufl_elements[0], self._ufl_domain + ) @property def value_size(self) -> int: @@ -165,7 +174,7 @@ class FunctionSpace(BaseFunctionSpace, UFLObject): def dual(self): """Get the dual of the space.""" - return DualSpace(self._ufl_domain, self._ufl_element, label=self.label()) + return DualSpace(self._ufl_domain, self._ufl_elements, label=self.label()) def _ufl_hash_data_(self): """UFL hash data.""" @@ -177,11 +186,11 @@ def _ufl_signature_data_(self, renumbering): def __repr__(self): """Representation.""" - return f"FunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})" + return f"FunctionSpace({self._ufl_domain!r}, {self._ufl_elements!r})" def __str__(self): """String.""" - return f"FunctionSpace({self._ufl_domain}, {self._ufl_element})" + return f"FunctionSpace({self._ufl_domain}, {self._ufl_elements})" class DualSpace(BaseFunctionSpace, UFLObject): @@ -196,7 +205,7 @@ def __init__(self, domain, element, label=""): def dual(self): """Get the dual of the space.""" - return FunctionSpace(self._ufl_domain, self._ufl_element, label=self.label()) + return FunctionSpace(self._ufl_domain, self._ufl_elements, label=self.label()) def _ufl_hash_data_(self): """UFL hash data.""" @@ -208,11 +217,11 @@ def _ufl_signature_data_(self, renumbering): def __repr__(self): """Representation.""" - return f"DualSpace({self._ufl_domain!r}, {self._ufl_element!r})" + return f"DualSpace({self._ufl_domain!r}, {self._ufl_elements!r})" def __str__(self): """String.""" - return f"DualSpace({self._ufl_domain}, {self._ufl_element})" + return f"DualSpace({self._ufl_domain}, {self._ufl_elements})" class TensorProductFunctionSpace(AbstractFunctionSpace, UFLObject): diff --git a/ufl/geometry.py b/ufl/geometry.py index 06b9ce228..1f0d9f7cf 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -198,14 +198,16 @@ class SpatialCoordinate(GeometricCellQuantity): @property def ufl_shape(self): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" - g = self._domain.geometric_dimension() + g = self._domain.geometric_dimension return (g,) def is_cellwise_constant(self): """Return whether this expression is spatially constant over each cell.""" # Only case this is true is if the domain is a vertex cell. - t = self._domain.topological_dimension() - return t == 0 + for e in self._domain.ufl_coordinate_elements(): + if e.cell.topological_dimension() != 0: + return False + return True def evaluate(self, x, mapping, component, index_values): """Return the value of the coordinate.""" @@ -241,13 +243,13 @@ class CellCoordinate(GeometricCellQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t,) def is_cellwise_constant(self): """Return whether this expression is spatially constant over each cell.""" # Only case this is true is if the domain is a vertex cell. - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return t == 0 @@ -268,21 +270,21 @@ class FacetCoordinate(GeometricFacetQuantity): def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("FacetCoordinate is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 1,) def is_cellwise_constant(self): """Return whether this expression is spatially constant over each cell.""" # Only case this is true is if the domain is an interval cell # (with a vertex facet). - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return t <= 1 @@ -303,14 +305,14 @@ class RidgeCoordinate(GeometricRidgeQuantity): def __init__(self, domain): """Initialise.""" GeometricRidgeQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("RidgeCoordinate is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 2,) def is_cellwise_constant(self): @@ -331,7 +333,7 @@ class CellOrigin(GeometricCellQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() + g = self._domain.geometric_dimension return (g,) def is_cellwise_constant(self): @@ -349,7 +351,7 @@ class FacetOrigin(GeometricFacetQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() + g = self._domain.geometric_dimension return (g,) @@ -363,7 +365,7 @@ class RidgeOrigin(GeometricRidgeQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() + g = self._domain.geometric_dimension return (g,) @@ -377,7 +379,7 @@ class CellFacetOrigin(GeometricFacetQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t,) @@ -391,7 +393,7 @@ class CellRidgeOrigin(GeometricRidgeQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t,) @@ -411,8 +413,8 @@ class Jacobian(GeometricCellQuantity): @property def ufl_shape(self): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" - g = self._domain.geometric_dimension() - t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (g, t) def is_cellwise_constant(self): @@ -438,15 +440,15 @@ class FacetJacobian(GeometricFacetQuantity): def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("FacetJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() - t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (g, t - 1) def is_cellwise_constant(self): @@ -474,15 +476,15 @@ class RidgeJacobian(GeometricRidgeQuantity): def __init__(self, domain): """Initialise.""" GeometricRidgeQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("RidgeJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() - t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (g, t - 2) def is_cellwise_constant(self): @@ -505,14 +507,14 @@ class CellFacetJacobian(GeometricFacetQuantity): # dX/dXf def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("CellFacetJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t, t - 1) def is_cellwise_constant(self): @@ -535,14 +537,14 @@ class CellRidgeJacobian(GeometricRidgeQuantity): # dX/dXe def __init__(self, domain): """Initialise.""" GeometricRidgeQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("CellRidgeJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t, t - 2) def is_cellwise_constant(self): @@ -565,14 +567,14 @@ class FacetRidgeJacobian(GeometricRidgeQuantity): # dXf/dXe def __init__(self, domain): """Initialise.""" GeometricRidgeQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("FacetRidgeJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 1, t - 2) def is_cellwise_constant(self): @@ -592,7 +594,7 @@ class ReferenceCellEdgeVectors(GeometricCellQuantity): def __init__(self, domain): """Initialise.""" GeometricCellQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("CellEdgeVectors is only defined for topological dimensions >= 2.") @@ -620,7 +622,7 @@ class ReferenceFacetEdgeVectors(GeometricFacetQuantity): def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 3: raise ValueError("FacetEdgeVectors is only defined for topological dimensions >= 3.") @@ -661,7 +663,7 @@ def ufl_shape(self): domain = extract_unique_domain(self) cell = domain.ufl_cell() nv = cell.num_vertices() - g = domain.geometric_dimension() + g = domain.geometric_dimension return (nv, g) def is_cellwise_constant(self): @@ -680,7 +682,7 @@ class CellEdgeVectors(GeometricCellQuantity): def __init__(self, domain): """Initialise.""" GeometricCellQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError("CellEdgeVectors is only defined for topological dimensions >= 2.") @@ -690,7 +692,7 @@ def ufl_shape(self): domain = extract_unique_domain(self) cell = domain.ufl_cell() ne = cell.num_edges() - g = domain.geometric_dimension() + g = domain.geometric_dimension return (ne, g) def is_cellwise_constant(self): @@ -709,7 +711,7 @@ class FacetEdgeVectors(GeometricFacetQuantity): def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 3: raise ValueError("FacetEdgeVectors is only defined for topological dimensions >= 3.") @@ -725,7 +727,7 @@ def ufl_shape(self): raise Exception(f"Cell type {cell} not supported.") nfe = facet_types[0].num_edges() - g = domain.geometric_dimension() + g = domain.geometric_dimension return (nfe, g) def is_cellwise_constant(self): @@ -826,8 +828,8 @@ class JacobianInverse(GeometricCellQuantity): @property def ufl_shape(self): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" - g = self._domain.geometric_dimension() - t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t, g) def is_cellwise_constant(self): @@ -847,7 +849,7 @@ class FacetJacobianInverse(GeometricFacetQuantity): def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError( "FacetJacobianInverse is only defined for topological dimensions >= 2." @@ -856,8 +858,8 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() - t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 1, g) def is_cellwise_constant(self): @@ -877,7 +879,7 @@ class RidgeJacobianInverse(GeometricRidgeQuantity): def __init__(self, domain): """Initialise.""" GeometricRidgeQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError( "RidgeJacobianInverse is only defined for topological dimensions >= 2." @@ -886,8 +888,8 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - g = self._domain.geometric_dimension() - t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 2, g) def is_cellwise_constant(self): @@ -907,7 +909,7 @@ class CellFacetJacobianInverse(GeometricFacetQuantity): def __init__(self, domain): """Initialise.""" GeometricFacetQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError( "CellFacetJacobianInverse is only defined for topological dimensions >= 2." @@ -916,7 +918,7 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 1, t) def is_cellwise_constant(self): @@ -935,7 +937,7 @@ class CellRidgeJacobianInverse(GeometricRidgeQuantity): def __init__(self, domain): """Initialise.""" GeometricRidgeQuantity.__init__(self, domain) - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() if t < 2: raise ValueError( "CellRidgeJacobianInverse is only defined for topological dimensions >= 2." @@ -944,7 +946,7 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t - 2, t) def is_cellwise_constant(self): @@ -966,7 +968,7 @@ class FacetNormal(GeometricFacetQuantity): @property def ufl_shape(self): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" - g = self._domain.geometric_dimension() + g = self._domain.geometric_dimension return (g,) def is_cellwise_constant(self): @@ -990,8 +992,8 @@ class CellNormal(GeometricCellQuantity): @property def ufl_shape(self): """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" - g = self._domain.geometric_dimension() - # t = self._domain.topological_dimension() + g = self._domain.geometric_dimension + # t = self._domain.ufl_coordinate_element().cell.topological_dimension() # return (g-t,g) # TODO: Should it be CellNormals? For interval in 3D we have two! return (g,) @@ -1011,7 +1013,7 @@ class ReferenceNormal(GeometricFacetQuantity): @property def ufl_shape(self): """Get the UFL shape.""" - t = self._domain.topological_dimension() + t = self._domain.ufl_coordinate_element().cell.topological_dimension() return (t,) diff --git a/ufl/pullback.py b/ufl/pullback.py index 6314c458c..3ad7d1972 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -158,7 +158,7 @@ def physical_value_shape(self, element, domain) -> tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - gdim = domain.geometric_dimension() + gdim = domain.geometric_dimension return element.reference_value_shape[:-1] + (gdim,) @@ -202,7 +202,7 @@ def physical_value_shape(self, element, domain) -> tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - gdim = domain.geometric_dimension() + gdim = domain.geometric_dimension return element.reference_value_shape[:-1] + (gdim,) @@ -287,7 +287,7 @@ def physical_value_shape(self, element, domain) -> tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - gdim = domain.geometric_dimension() + gdim = domain.geometric_dimension return element.reference_value_shape[:-2] + (gdim, gdim) @@ -331,7 +331,7 @@ def physical_value_shape(self, element, domain) -> tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - gdim = domain.geometric_dimension() + gdim = domain.geometric_dimension return element.reference_value_shape[:-2] + (gdim, gdim) @@ -377,7 +377,7 @@ def physical_value_shape(self, element, domain) -> tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - gdim = domain.geometric_dimension() + gdim = domain.geometric_dimension return element.reference_value_shape[:-2] + (gdim, gdim)