From 07b61c3d3baee3d07b0ec48b84a0961a48577f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Thu, 11 Aug 2022 05:53:22 +0100 Subject: [PATCH 1/7] Fix signature for forms with identitcal constants and identical meshes (up to ufl_id) (#113) * Add renumbering to constants to get caching when defined in loops. Reported at: https://fenicsproject.discourse.group/t/constant-in-dolfinx-and-ffcx-compilation/8968?u=dokken * Remove mesh index from signature --- ufl/constant.py | 6 ++++++ ufl/form.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/ufl/constant.py b/ufl/constant.py index a0970c182..83a0247fd 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -66,6 +66,12 @@ def __eq__(self, other): self._ufl_domain == other._ufl_domain and self._ufl_shape == self._ufl_shape) + def _ufl_signature_data_(self, renumbering): + "Signature data for constant depends on renumbering" + return "Constant({}, {}, {})".format( + self._ufl_domain._ufl_signature_data_(renumbering), repr(self._ufl_shape), + repr(renumbering[self])) + def VectorConstant(domain, count=None): domain = as_domain(domain) diff --git a/ufl/form.py b/ufl/form.py index 767689775..9f2bd17cb 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -81,6 +81,7 @@ class Form(object): "_arguments", "_coefficients", "_coefficient_numbering", + "_constant_numbering", "_constants", "_hash", "_signature", @@ -111,9 +112,12 @@ def __init__(self, integrals): self._arguments = None self._coefficients = None self._coefficient_numbering = None + self._constant_numbering = None from ufl.algorithms.analysis import extract_constants self._constants = extract_constants(self) + self._constant_numbering = dict( + (c, i) for i, c in enumerate(self._constants)) # Internal variables for caching of hash and signature after # first request @@ -237,6 +241,11 @@ def coefficient_numbering(self): def constants(self): return self._constants + def constant_numbering(self): + """Return a contiguous numbering of constants in a mapping + ``{constant:number}``.""" + return self._constant_numbering + def signature(self): "Signature for use with jit cache (independent of incidental numbering of indices etc.)" if self._signature is None: @@ -458,9 +467,11 @@ def _compute_renumbering(self): # Include integration domains and coefficients in renumbering dn = self.domain_numbering() cn = self.coefficient_numbering() + cnstn = self.constant_numbering() renumbering = {} renumbering.update(dn) renumbering.update(cn) + renumbering.update(cnstn) # Add domains of coefficients, these may include domains not # among integration domains @@ -479,6 +490,14 @@ def _compute_renumbering(self): renumbering[d] = k k += 1 + # Add domains of constants, these may include domains not + # among integration domains + for c in self._constants: + d = c.ufl_domain() + if d is not None and d not in renumbering: + renumbering[d] = k + k += 1 + return renumbering def _compute_signature(self): From 8fc633dc59f2291c2b0852ec487fb81378907f7a Mon Sep 17 00:00:00 2001 From: Chris Richardson Date: Thu, 11 Aug 2022 14:56:05 +0100 Subject: [PATCH 2/7] Update version (#115) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 675c08399..77beb85f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ # future [metadata] name = fenics-ufl -version = 2022.2.0.dev0 +version = 2022.3.0.dev0 author = FEniCS Project Contributors email = fenics-dev@googlegroups.com maintainer = FEniCS Project Steering Council From 3c951541ad1f9633427e72898d584dbcfae82293 Mon Sep 17 00:00:00 2001 From: Michal Habera Date: Tue, 23 Aug 2022 02:26:02 -0700 Subject: [PATCH 3/7] Override is_cellwise_constant for CellNormal (#119) --- ufl/geometry.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ufl/geometry.py b/ufl/geometry.py index b203b94cc..1d4741950 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -649,6 +649,11 @@ def ufl_shape(self): # return (g-t,g) # TODO: Should it be CellNormals? For interval in 3D we have two! return (g,) + def is_cellwise_constant(self): + "Return whether this expression is spatially constant over each cell." + # Only true for a piecewise linear coordinate field in simplex cells + return self._domain.is_piecewise_linear_simplex_domain() + @ufl_type() class ReferenceNormal(GeometricFacetQuantity): From 1951bee8d81cfef5f9c6e511944004d93283241b Mon Sep 17 00:00:00 2001 From: "David A. Ham" Date: Tue, 23 Aug 2022 21:07:21 +1000 Subject: [PATCH 4/7] H inf sobolev space (#117) * Add a Sobolev space spanning C infinity elements. * Put R space in HInf * Test Sobolev space additions. * Use Sobolev space as the restrictions check. * Add sobolev_space method to base finite element class (#118) * Add nano swp files to .gitignore * MAke finiteelementbase into an abstract base class and add sobolev_space method * _ * restricted element is L2 * s * make fenicsx tests run * branches * _ not - * remove branch from test config * max * main fenics and ffc Co-authored-by: Matthew Scroggs Co-authored-by: Garth N. Wells --- .gitignore | 2 ++ test/test_sobolevspace.py | 23 +++++++++++++-- ufl/__init__.py | 7 +++-- ufl/algorithms/apply_restrictions.py | 8 ++---- ufl/finiteelement/brokenelement.py | 10 ++++--- ufl/finiteelement/elementlist.py | 7 ++--- ufl/finiteelement/enrichedelement.py | 11 ++++--- ufl/finiteelement/finiteelement.py | 13 +++++---- ufl/finiteelement/finiteelementbase.py | 35 +++++++++++------------ ufl/finiteelement/hdivcurl.py | 30 +++++++++++-------- ufl/finiteelement/mixedelement.py | 26 +++++++++++------ ufl/finiteelement/restrictedelement.py | 8 ++++-- ufl/finiteelement/tensorproductelement.py | 7 +++-- ufl/sobolevspace.py | 12 ++++---- 14 files changed, 122 insertions(+), 77 deletions(-) diff --git a/.gitignore b/.gitignore index ead6e0068..31a5f5435 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ test/.pytest_cache *.egg-info dist/ release/ + +.*.swp diff --git a/test/test_sobolevspace.py b/test/test_sobolevspace.py index 6ceac5a49..fb579a2f7 100755 --- a/test/test_sobolevspace.py +++ b/test/test_sobolevspace.py @@ -9,7 +9,8 @@ FiniteElement, triangle, interval, quadrilateral, HDiv, HCurl) from ufl.sobolevspace import SobolevSpace, DirectionalSobolevSpace -from ufl import H2, H1, HDiv, HCurl, L2 +from ufl import H2, H1, HDiv, HCurl, L2, HInf +from math import inf # Construct directional Sobolev spaces, with varying smoothness in @@ -17,6 +18,7 @@ H0dx0dy = DirectionalSobolevSpace((0, 0)) H1dx1dy = DirectionalSobolevSpace((1, 1)) H2dx2dy = DirectionalSobolevSpace((2, 2)) +Hinfdxinfdy = DirectionalSobolevSpace((inf, inf)) H1dx = DirectionalSobolevSpace((1, 0)) H1dy = DirectionalSobolevSpace((0, 1)) H000 = DirectionalSobolevSpace((0, 0, 0)) @@ -44,6 +46,8 @@ def test_directional_space_relations(): assert H1dx1dy <= HCurl assert H2dx2dy <= H1dx1dy assert H2dhH1dz < H1 + assert Hinfdxinfdy <= HInf + assert Hinfdxinfdy < H2dx2dy assert H1dz > H2dhH1dz assert H1dh < L2 assert H1dz < L2 @@ -63,7 +67,6 @@ def xtest_contains_mixed(): def test_contains_l2(): l2_elements = [ - FiniteElement("Real", triangle, 0), FiniteElement("DG", triangle, 0), FiniteElement("DG", triangle, 1), FiniteElement("DG", triangle, 2), @@ -132,6 +135,22 @@ def test_contains_h2(): assert h2_element in H0dx0dy +def test_contains_hinf(): + hinf_elements = [ + FiniteElement("R", triangle, 0) + ] + for hinf_element in hinf_elements: + assert hinf_element in HInf + assert hinf_element in H2 + assert hinf_element in H2dx2dy + assert hinf_element in H1 + assert hinf_element in H1dx1dy + assert hinf_element in HDiv + assert hinf_element in HCurl + assert hinf_element in L2 + assert hinf_element in H0dx0dy + + def test_contains_hdiv(): hdiv_elements = [ FiniteElement("RT", triangle, 1), diff --git a/ufl/__init__.py b/ufl/__init__.py index fe97fb3f3..575417829 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -68,8 +68,11 @@ - L2 - H1 - H2 + - HInf - HDiv - HCurl + - HEin + - HDivDiv * Elements:: @@ -272,7 +275,7 @@ ) # Sobolev spaces -from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl +from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf # Finite elements classes from ufl.finiteelement import FiniteElementBase, FiniteElement, \ @@ -369,7 +372,7 @@ 'UFLException', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', 'as_domain', 'AbstractDomain', 'Mesh', 'MeshView', 'TensorProductMesh', - 'L2', 'H1', 'H2', 'HCurl', 'HDiv', + 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'HInf', 'HEin', 'HDivDiv', 'SpatialCoordinate', 'CellVolume', 'CellDiameter', 'Circumradius', 'MinCellEdgeLength', 'MaxCellEdgeLength', diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 1cdf2f3ea..1ab07d72d 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -14,6 +14,7 @@ from ufl.corealg.map_dag import map_expr_dag from ufl.algorithms.map_integrands import map_integrand_dags from ufl.measure import integral_type_to_measure_name +from ufl.sobolevspace import H1 class RestrictionPropagator(MultiFunction): @@ -128,12 +129,7 @@ def reference_value(self, o): def coefficient(self, o): "Allow coefficients to be unrestricted (apply default if so) if the values are fully continuous across the facet." - e = o.ufl_element() - d = e.degree() - f = e.family() - # TODO: Move this choice to the element class? - continuous_families = ["Lagrange", "Q", "S"] - if (f in continuous_families and d > 0) or f == "Real": + if o.ufl_element() in H1: # If the coefficient _value_ is _fully_ continuous return self._default_restricted(o) # Must still be computed from one of the sides, we just don't care which else: diff --git a/ufl/finiteelement/brokenelement.py b/ufl/finiteelement/brokenelement.py index 6249f1fb1..98ca1812a 100644 --- a/ufl/finiteelement/brokenelement.py +++ b/ufl/finiteelement/brokenelement.py @@ -14,7 +14,6 @@ class BrokenElement(FiniteElementBase): """The discontinuous version of an existing Finite Element space.""" def __init__(self, element): self._element = element - self._repr = "BrokenElement(%s)" % repr(element) family = "BrokenElement" cell = element.cell() @@ -25,6 +24,9 @@ def __init__(self, element): FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) + def __repr__(self): + return f"BrokenElement({repr(self._element)})" + def mapping(self): return self._element.mapping() @@ -32,8 +34,8 @@ def reconstruct(self, **kwargs): return BrokenElement(self._element.reconstruct(**kwargs)) def __str__(self): - return "BrokenElement(%s)" % str(self._element) + return f"BrokenElement({repr(self._element)})" def shortstr(self): - "Format as string for pretty printing." - return "BrokenElement(%s)" % str(self._element.shortstr()) + """Format as string for pretty printing.""" + return f"BrokenElement({repr(self._element)})" diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index eaf3e7722..e45fc7813 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -16,7 +16,7 @@ from numpy import asarray from ufl.log import warning, error -from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv +from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf from ufl.utils.formatting import istr from ufl.cell import Cell, TensorProductCell @@ -137,7 +137,7 @@ def show_elements(): register_element("FacetBubble", "FB", 0, H1, "identity", (2, None), simplices) register_element("Quadrature", "Quadrature", 0, L2, "identity", (0, None), any_cell) -register_element("Real", "R", 0, L2, "identity", (0, 0), +register_element("Real", "R", 0, HInf, "identity", (0, 0), any_cell + ("TensorProductCell",)) register_element("Undefined", "U", 0, L2, "identity", (0, None), any_cell) register_element("Radau", "Rad", 0, L2, "identity", (0, None), ("interval",)) @@ -460,9 +460,6 @@ def canonical_element_description(family, cell, order, form_degree): error('Order "%s" invalid for "%s" finite element.' % (istr(order), family)) - # Override sobolev_space for piecewise constants (TODO: necessary?) - if order == 0: - sobolev_space = L2 if value_rank == 2: # Tensor valued fundamental elements in HEin have this shape if gdim is None or tdim is None: diff --git a/ufl/finiteelement/enrichedelement.py b/ufl/finiteelement/enrichedelement.py index ac2697841..221fc0648 100644 --- a/ufl/finiteelement/enrichedelement.py +++ b/ufl/finiteelement/enrichedelement.py @@ -61,14 +61,11 @@ def __init__(self, *elements): quad_scheme, value_shape, reference_value_shape) - # Cache repr string - self._repr = "%s(%s)" % (class_name, ", ".join(repr(e) for e in self._elements)) - def mapping(self): return self._elements[0].mapping() def sobolev_space(self): - "Return the underlying Sobolev space." + """Return the underlying Sobolev space.""" elements = [e for e in self._elements] if all(e.sobolev_space() == elements[0].sobolev_space() for e in elements): @@ -104,6 +101,9 @@ def is_cellwise_constant(self): element is spatially constant over each cell.""" return all(e.is_cellwise_constant() for e in self._elements) + def __repr__(self): + return "EnrichedElement(" + ", ".join(repr(e) for e in self._elements) + ")" + def __str__(self): "Format as string for pretty printing." return "<%s>" % " + ".join(str(e) for e in self._elements) @@ -127,6 +127,9 @@ def is_cellwise_constant(self): element is spatially constant over each cell.""" return False + def __repr__(self): + return "NodalEnrichedElement(" + ", ".join(repr(e) for e in self._elements) + ")" + def __str__(self): "Format as string for pretty printing." return "" % ", ".join(str(e) for e in self._elements) diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index 6b42d0593..d9fd533a2 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -24,10 +24,8 @@ class FiniteElement(FiniteElementBase): "The basic finite element class for all simple finite elements." # TODO: Move these to base? - __slots__ = ("_short_name", - "_sobolev_space", - "_mapping", - "_variant") + __slots__ = ("_short_name", "_sobolev_space", + "_mapping", "_variant", "_repr") def __new__(cls, family, @@ -188,11 +186,16 @@ def __init__(self, repr(self.family()), repr(self.cell()), repr(self.degree()), quad_str, var_str) assert '"' not in self._repr + def __repr__(self): + """Format as string for evaluation as Python object.""" + return self._repr + def mapping(self): + """Return the mapping type for this element .""" return self._mapping def sobolev_space(self): - "Return the underlying Sobolev space." + """Return the underlying Sobolev space.""" return self._sobolev_space def variant(self): diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py index 846a7995f..17f687bb4 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/finiteelement/finiteelementbase.py @@ -15,24 +15,19 @@ from ufl.utils.dicts import EmptyDict from ufl.log import error from ufl.cell import AbstractCell, as_cell +from abc import ABC, abstractmethod -class FiniteElementBase(object): +class FiniteElementBase(ABC): "Base class for all finite elements." - __slots__ = ("_family", - "_cell", - "_degree", - "_quad_scheme", - "_value_shape", - "_reference_value_shape", - "_repr", - "__weakref__") + __slots__ = ("_family", "_cell", "_degree", "_quad_scheme", + "_value_shape", "_reference_value_shape", "__weakref__") # TODO: Not all these should be in the base class! In particular # family, degree, and quad_scheme do not belong here. def __init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape): - "Initialize basic finite element data." + """Initialize basic finite element data.""" if not isinstance(family, str): error("Invalid family type.") if not (degree is None or isinstance(degree, (int, tuple))): @@ -54,12 +49,20 @@ def __init__(self, family, cell, degree, quad_scheme, value_shape, self._reference_value_shape = reference_value_shape self._quad_scheme = quad_scheme + @abstractmethod def __repr__(self): - """Format as string for evaluation as Python object. + """Format as string for evaluation as Python object.""" + pass - NB! Assumes subclass has assigned its repr string to self._repr. - """ - return self._repr + @abstractmethod + def sobolev_space(self): + """Return the underlying Sobolev space.""" + pass + + @abstractmethod + def mapping(self): + """Return the mapping type for this element.""" + pass def _ufl_hash_data_(self): return repr(self) @@ -101,10 +104,6 @@ def quadrature_scheme(self): "Return quadrature scheme of finite element." return self._quad_scheme - def mapping(self): - "Not implemented." - error("Missing implementation of mapping().") - def cell(self): "Return cell of finite element." return self._cell diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/finiteelement/hdivcurl.py index 7cdc45cae..5e50ded2a 100644 --- a/ufl/finiteelement/hdivcurl.py +++ b/ufl/finiteelement/hdivcurl.py @@ -14,11 +14,10 @@ class HDivElement(FiniteElementBase): """A div-conforming version of an outer product element, assuming this makes mathematical sense.""" - __slots__ = ("_element",) + __slots__ = ("_element", ) def __init__(self, element): self._element = element - self._repr = "HDivElement(%s)" % repr(element) family = "TensorProductElement" cell = element.cell() @@ -31,22 +30,25 @@ def __init__(self, element): FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) + def __repr__(self): + return f"HDivElement({repr(self._element)})" + def mapping(self): return "contravariant Piola" def sobolev_space(self): - "Return the underlying Sobolev space." + """Return the underlying Sobolev space.""" return HDiv def reconstruct(self, **kwargs): return HDivElement(self._element.reconstruct(**kwargs)) def __str__(self): - return "HDivElement(%s)" % str(self._element) + return f"HDivElement({repr(self._element)})" def shortstr(self): - "Format as string for pretty printing." - return "HDivElement(%s)" % str(self._element.shortstr()) + """Format as string for pretty printing.""" + return f"HDivElement({self._element.shortstr()})" class HCurlElement(FiniteElementBase): @@ -56,7 +58,6 @@ class HCurlElement(FiniteElementBase): def __init__(self, element): self._element = element - self._repr = "HCurlElement(%s)" % repr(element) family = "TensorProductElement" cell = element.cell() @@ -70,6 +71,9 @@ def __init__(self, element): FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) + def __repr__(self): + return f"HCurlElement({repr(self._element)})" + def mapping(self): return "covariant Piola" @@ -81,11 +85,11 @@ def reconstruct(self, **kwargs): return HCurlElement(self._element.reconstruct(**kwargs)) def __str__(self): - return "HCurlElement(%s)" % str(self._element) + return f"HCurlElement({repr(self._element)})" def shortstr(self): "Format as string for pretty printing." - return "HCurlElement(%s)" % str(self._element.shortstr()) + return f"HCurlElement({self._element.shortstr()})" class WithMapping(FiniteElementBase): @@ -95,7 +99,6 @@ class WithMapping(FiniteElementBase): remapped = WithMapping(E, "identity") """ def __init__(self, wrapee, mapping): - self._repr = "WithMapping(%s, %s)" % (repr(wrapee), mapping) if mapping == "symmetries": raise ValueError("Can't change mapping to 'symmetries'") self._mapping = mapping @@ -108,6 +111,9 @@ def __getattr__(self, attr): raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, attr)) + def __repr__(self): + return f"WithMapping({repr(self.wrapee)}, {self._mapping})" + def value_shape(self): gdim = self.cell().geometric_dimension() mapping = self.mapping() @@ -137,7 +143,7 @@ def reconstruct(self, **kwargs): return type(self)(wrapee, mapping) def __str__(self): - return "WithMapping(%s, mapping=%s)" % (self.wrapee, self._mapping) + return f"WithMapping({repr(self.wrapee)}, {self._mapping})" def shortstr(self): - return "WithMapping(%s, %s)" % (self.wrapee.shortstr(), self._mapping) + return f"WithMapping({self.wrapee.shortstr()}, {self._mapping})" diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py index 720776035..e80743fb9 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/finiteelement/mixedelement.py @@ -90,10 +90,8 @@ def __init__(self, *elements, **kwargs): FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme, value_shape, reference_value_shape) - # Cache repr string - if type(self) is MixedElement: - self._repr = "MixedElement(%s)" % ( - ", ".join(repr(e) for e in self._sub_elements),) + def __repr__(self): + return "MixedElement(" + ", ".join(repr(e) for e in self._sub_elements) + ")" def reconstruct_from_elements(self, *elements): "Reconstruct a mixed element from new subelements." @@ -125,6 +123,9 @@ def symmetry(self): error("Size mismatch in symmetry algorithm.") return sm or EmptyDict + def sobolev_space(self): + return max(e.sobolev_space() for e in self._sub_elements) + def mapping(self): if all(e.mapping() == "identity" for e in self._sub_elements): return "identity" @@ -247,6 +248,8 @@ def shortstr(self): class VectorElement(MixedElement): "A special case of a mixed finite element where all elements are equal." + __slots__ = ("_repr", "_mapping", "_sub_element") + def __init__(self, family, cell=None, degree=None, dim=None, form_degree=None, quad_scheme=None, variant=None): """ @@ -310,8 +313,10 @@ def __init__(self, family, cell=None, degree=None, dim=None, var_str = ", variant='" + variant + "'" # Cache repr string - self._repr = "VectorElement(%s, dim=%d%s)" % ( - repr(sub_element), len(self._sub_elements), var_str) + self._repr = f"VectorElement({repr(sub_element)}, dim={dim}{var_str})" + + def __repr__(self): + return self._repr def reconstruct(self, **kwargs): sub_element = self._sub_element.reconstruct(**kwargs) @@ -343,7 +348,7 @@ class TensorElement(MixedElement): __slots__ = ("_sub_element", "_shape", "_symmetry", "_sub_element_mapping", "_flattened_sub_element_mapping", - "_mapping") + "_mapping", "_repr") def __init__(self, family, cell=None, degree=None, shape=None, symmetry=None, quad_scheme=None, variant=None): @@ -447,8 +452,11 @@ def __init__(self, family, cell=None, degree=None, shape=None, var_str = ", variant='" + variant + "'" # Cache repr string - self._repr = "TensorElement(%s, shape=%s, symmetry=%s%s)" % ( - repr(sub_element), repr(self._shape), repr(self._symmetry), var_str) + self._repr = (f"TensorElement({repr(sub_element)}, shape={shape}, " + f"symmetry={symmetry}{var_str})") + + def __repr__(self): + return self._repr def variant(self): """Return the variant used to initialise the element.""" diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py index b5746b27c..1d17e8bc4 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/finiteelement/restrictedelement.py @@ -13,6 +13,7 @@ from ufl.finiteelement.finiteelementbase import FiniteElementBase from ufl.log import error +from ufl.sobolevspace import L2 valid_restriction_domains = ("interior", "facet", "face", "edge", "vertex") @@ -35,8 +36,11 @@ def __init__(self, element, restriction_domain): self._restriction_domain = restriction_domain - self._repr = "RestrictedElement(%s, %s)" % ( - repr(self._element), repr(self._restriction_domain)) + def __repr__(self): + return f"RestrictedElement({repr(self._element)}, {repr(self._restriction_domain)})" + + def sobolev_space(self): + return L2 def is_cellwise_constant(self): """Return whether the basis functions of this element is spatially diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/finiteelement/tensorproductelement.py index 3ddec1321..54de4aa83 100644 --- a/ufl/finiteelement/tensorproductelement.py +++ b/ufl/finiteelement/tensorproductelement.py @@ -68,8 +68,11 @@ def __init__(self, *elements, **kwargs): reference_value_shape) self._sub_elements = elements self._cell = cell - self._repr = "TensorProductElement(%s, cell=%s)" % ( - ", ".join(repr(e) for e in elements), repr(cell)) + + def __repr__(self): + return "TensorProductElement(" + ", ".join( + repr(e) for e in self._sub_elements + ) + f", {repr(self._cell)})" def mapping(self): if all(e.mapping() == "identity" for e in self._sub_elements): diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index 3d76226ca..d54034459 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -15,6 +15,7 @@ # Modified by Thomas Gibson 2017 from functools import total_ordering +from math import inf, isinf @total_ordering @@ -39,6 +40,7 @@ def __init__(self, name, parents=None): "L2": 0, "H1": 1, "H2": 2, + "HInf": inf, # Order for the elements below is taken from # its parent Sobolev space "HDiv": 0, @@ -114,11 +116,8 @@ def __init__(self, orders): the position denotes in what spatial variable the smoothness requirement is enforced. """ - assert all( - isinstance(x, int) for x in orders), ("Order must be an integer.") - assert all( - x < 3 - for x in orders), ("Not implemented for orders greater than 2") + assert all(isinstance(x, int) or isinf(x) for x in orders), \ + ("Order must be an integer or infinity.") name = "DirectionalH" parents = [L2] super(DirectionalSobolevSpace, self).__init__(name, parents) @@ -131,7 +130,7 @@ def __getitem__(self, spatial_index): """ if spatial_index not in range(len(self._orders)): raise IndexError("Spatial index out of range.") - spaces = {0: L2, 1: H1, 2: H2} + spaces = {0: L2, 1: H1, 2: H2, inf: HInf} return spaces[self._orders[spatial_index]] def __contains__(self, other): @@ -179,5 +178,6 @@ def __str__(self): HCurl = SobolevSpace("HCurl", [L2]) H1 = SobolevSpace("H1", [HDiv, HCurl, L2]) H2 = SobolevSpace("H2", [H1, HDiv, HCurl, L2]) +HInf = SobolevSpace("HInf", [H2, H1, HDiv, HCurl, L2]) HEin = SobolevSpace("HEin", [L2]) HDivDiv = SobolevSpace("HDivDiv", [L2]) From 50d2a4863ed6bc46e35c03a97193ee97fd09ceaf Mon Sep 17 00:00:00 2001 From: "David A. Ham" Date: Tue, 23 Aug 2022 21:07:58 +1000 Subject: [PATCH 5/7] Sanitise unicode input (#111) * Fix lint * Allow ufl2unicode to handle e.g. scalars Co-authored-by: nbouziani --- ufl/formatting/ufl2unicode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 3a05cdf4d..9757b878e 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -324,7 +324,7 @@ def ufl2unicode(expression): preprocessed_form = form_data.preprocessed_form return form2unicode(preprocessed_form, form_data) else: - return expression2unicode(expression) + return expression2unicode(ufl.as_ufl(expression)) def expression2unicode(expression, argument_names=None, coefficient_names=None): From 530f30479473e945890779245d9e5854013290b7 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 10 Oct 2022 15:12:27 +0100 Subject: [PATCH 6/7] Add variant to EnrichedElement, TensorProductElement, etc (#121) * get variant for composite elements --- ufl/finiteelement/enrichedelement.py | 7 +++++++ ufl/finiteelement/hdivcurl.py | 9 +++++++++ ufl/finiteelement/restrictedelement.py | 3 +++ ufl/finiteelement/tensorproductelement.py | 7 +++++++ 4 files changed, 26 insertions(+) diff --git a/ufl/finiteelement/enrichedelement.py b/ufl/finiteelement/enrichedelement.py index 221fc0648..8f082ad5a 100644 --- a/ufl/finiteelement/enrichedelement.py +++ b/ufl/finiteelement/enrichedelement.py @@ -82,6 +82,13 @@ def sobolev_space(self): sobolev_space, = intersect return sobolev_space + def variant(self): + try: + variant, = {e.variant() for e in self._elements} + return variant + except ValueError: + return None + def reconstruct(self, **kwargs): return type(self)(*[e.reconstruct(**kwargs) for e in self._elements]) diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/finiteelement/hdivcurl.py index 5e50ded2a..cd3f76c51 100644 --- a/ufl/finiteelement/hdivcurl.py +++ b/ufl/finiteelement/hdivcurl.py @@ -43,6 +43,9 @@ def sobolev_space(self): def reconstruct(self, **kwargs): return HDivElement(self._element.reconstruct(**kwargs)) + def variant(self): + return self._element.variant() + def __str__(self): return f"HDivElement({repr(self._element)})" @@ -84,6 +87,9 @@ def sobolev_space(self): def reconstruct(self, **kwargs): return HCurlElement(self._element.reconstruct(**kwargs)) + def variant(self): + return self._element.variant() + def __str__(self): return f"HCurlElement({repr(self._element)})" @@ -142,6 +148,9 @@ def reconstruct(self, **kwargs): wrapee = self.wrapee.reconstruct(**kwargs) return type(self)(wrapee, mapping) + def variant(self): + return self.wrapee.variant() + def __str__(self): return f"WithMapping({repr(self.wrapee)}, {self._mapping})" diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py index 1d17e8bc4..2394f2703 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/finiteelement/restrictedelement.py @@ -100,3 +100,6 @@ def restricted_sub_elements(self): # w.r.t. different sub_elements meanings. "Return list of restricted sub elements." return (self._element,) + + def variant(self): + return self._element.variant() diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/finiteelement/tensorproductelement.py index 54de4aa83..93ed2f231 100644 --- a/ufl/finiteelement/tensorproductelement.py +++ b/ufl/finiteelement/tensorproductelement.py @@ -110,6 +110,13 @@ def reconstruct(self, **kwargs): cell = kwargs.pop("cell", self.cell()) return TensorProductElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()], cell=cell) + def variant(self): + try: + variant, = {e.variant() for e in self.sub_elements()} + return variant + except ValueError: + return None + def __str__(self): "Pretty-print." return "TensorProductElement(%s, cell=%s)" \ From 4e3868dee951504484b7f85e2c0e477ef7894c23 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 12 Oct 2022 10:08:01 +0100 Subject: [PATCH 7/7] Implement sobolev_space() for BrokenElement and WithMapping (#126) Implement sobolev_space() for BrokenElement and WithMapping --- ufl/finiteelement/brokenelement.py | 5 +++++ ufl/finiteelement/hdivcurl.py | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ufl/finiteelement/brokenelement.py b/ufl/finiteelement/brokenelement.py index 98ca1812a..4bd49155f 100644 --- a/ufl/finiteelement/brokenelement.py +++ b/ufl/finiteelement/brokenelement.py @@ -8,6 +8,7 @@ # Modified by Massimiliano Leoni, 2016 from ufl.finiteelement.finiteelementbase import FiniteElementBase +from ufl.sobolevspace import L2 class BrokenElement(FiniteElementBase): @@ -30,6 +31,10 @@ def __repr__(self): def mapping(self): return self._element.mapping() + def sobolev_space(self): + """Return the underlying Sobolev space.""" + return L2 + def reconstruct(self, **kwargs): return BrokenElement(self._element.reconstruct(**kwargs)) diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/finiteelement/hdivcurl.py index cd3f76c51..6dda827bc 100644 --- a/ufl/finiteelement/hdivcurl.py +++ b/ufl/finiteelement/hdivcurl.py @@ -8,7 +8,7 @@ # Modified by Massimiliano Leoni, 2016 from ufl.finiteelement.finiteelementbase import FiniteElementBase -from ufl.sobolevspace import HDiv, HCurl +from ufl.sobolevspace import HDiv, HCurl, L2 class HDivElement(FiniteElementBase): @@ -81,7 +81,7 @@ def mapping(self): return "covariant Piola" def sobolev_space(self): - "Return the underlying Sobolev space." + """Return the underlying Sobolev space.""" return HCurl def reconstruct(self, **kwargs): @@ -94,7 +94,7 @@ def __str__(self): return f"HCurlElement({repr(self._element)})" def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return f"HCurlElement({self._element.shortstr()})" @@ -143,6 +143,13 @@ def reference_value_shape(self): def mapping(self): return self._mapping + def sobolev_space(self): + """Return the underlying Sobolev space.""" + if self.wrapee.mapping() == self.mapping(): + return self.wrapee.sobolev_space() + else: + return L2 + def reconstruct(self, **kwargs): mapping = kwargs.pop("mapping", self._mapping) wrapee = self.wrapee.reconstruct(**kwargs)